目录
ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
AnnotationConfigApplicationContext
AnnotationConfigServletWebServerApplicationContext
容器接口
BeanFactory 能做哪些事
先创建一个 Maven 工程,导入以下依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shuttle.spring5</groupId>
<artifactId>Spring5_Principle</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>Spring5_01</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
</project>
主启动类
@SpringBootApplication
public class A01Application {
private static final Logger LOGGER = LoggerFactory.getLogger(A01Application.class);
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(A01Application.class);
}
}
接收 SpringApplication.run 方法的返回值其实就是一个 ApplicationContext 对象,也就是所说的context 容器,由它来管理我们的对象。
再来看看它的类图
BeanFactory 是最原始的 context 容器,进入到它的类中我们可以看到只有以下几个基础方法:
public interface BeanFactory {
// 判断是否是工厂bean
String FACTORY_BEAN_PREFIX = "&";
// 各种方式获取bean对象
Object getBean(String var1) throws BeansException;
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
// 获取bean的提供者
<T> ObjectProvider<T> getBeanProvider(Class<T> var1);
<T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
// 判断是否包含bean对象
boolean containsBean(String var1);
// 判断bean对象是否单例
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
// 判断bean对象是否多例
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
// 判断bean对象是否类型匹配
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
// 获取bean对象类型
@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;
// 获取bean对象别名
String[] getAliases(String var1);
}
我们尝试用SpringBoot启动类中返回的 context 容器去拿到一个bean对象
进入getBean方法实际上是调用的BeanFactory类中的方法。
另外值得注意的是ApplicationContext中组合了BeanFactory对象作为它的成员变量,可以用Debug的方式看到在applicationContext对象中有beanFactory成员属性。
再来看看容器中常见的管理单例对象是用的哪个类来实现的
实际上是图中的 DefaultSingletonBeanRegistry 类来管理我们容器中的单例对象
是不是看着很眼熟,它内部实现了 Spring 的三级缓存,也就对应了图中的三个 Map 对象。
我们尝试用反射或者 singletonObjects 属性,看看 Spring 内置了多少单例对象。
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
ConfigurableApplicationContext applicationContext = SpringApplication.run(A01Application.class);
System.out.println(applicationContext);
// 反射获取singletonObjects属性
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
// 因singletonObjects为private则需要设置下权限
singletonObjects.setAccessible(true);
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
Map<String, Object> singletonObjectMap = (Map<String, Object>) singletonObjects.get(beanFactory);
singletonObjectMap.forEach((k, v) -> {
System.out.println(k + " => " + v);
});
}
可以看到 Spring 中内置了非常多的单例对象。
我们自定义两个类并将其加入到容器中,看看是否能够找到它
@Component
public class Component01 {
}
@Component
public class Component02 {
}
为了演示方便,我们需要将遍历语句稍稍修改一下
singletonObjectMap.entrySet().stream()
// 过滤出容器中以component开头的对象
.filter(e -> e.getKey().startsWith("component"))
.forEach(e -> {
System.out.println(e.getKey() + "=> " + e.getValue());
});
可以看到结果中仅有我们自定义的两个 component 对象。
ApplicationContext 有哪些扩展功能
接下来我们看看 ApplicationContext 中究竟扩展了哪些功能呢?
实际上主要分为以上四个部分
MessageSource:支持国际化
ResourcePatternResolver:支持资源通配符查找
EnvironmentCapable:支持获取环境变量信息
ApplicationEventPublisher:支持事件发布
MessageSource:
首先在 resouces 目录下创建配置文件【名字一定不要错!血的教训】
System.out.println(applicationContext.getMessage("hi", null, Locale.CHINA));
System.out.println(applicationContext.getMessage("hi", null, Locale.ENGLISH));
结果如下:
ApplicationEventPublisher:
也是在 resouces 目录下创建配置文件
Resource[] resources = applicationContext.getResources("classpath:application.*");
for (Resource resource : resources) {
System.out.println(resource);
}
结果如下:
EnvironmentCapable:
尝试获取本地 JAVA_HOME 以及配置文件中的 server.port
System.out.println(applicationContext.getEnvironment().getProperty("JAVA_HOME"));
System.out.println(applicationContext.getEnvironment().getProperty("server.port"));
结果如下:
ApplicationEventPublisher:
首先我们需要自定义一个事件对象并且继承 ApplicationEvent
public class UserRegisterEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public UserRegisterEvent(Object source) {
super(source);
}
}
另外让一个 Component 组件成为监听事件对象,需要在监听方法上加 @EventListener 注解
@Component
public class Component02 {
@EventListener
public void listen(UserRegisterEvent event) {
System.out.println("监听到事件..." + event);
}
}
启动类发布事件
applicationContext.publishEvent(new UserRegisterEvent(applicationContext));
结果如下:
现在来实现一下用户注册和发送短信功能的解耦合,我们让 Component01 组件发送用户注册事件,让 Component02 组件监听用户注册后发送短信。
@Component
public class Component01 {
@Autowired
private ApplicationContext applicationContext;
public void register() {
System.out.println("用户注册");
applicationContext.publishEvent(new UserRegisterEvent(this));
}
}
@Component
public class Component02 {
@EventListener
public void listen(UserRegisterEvent event) {
System.out.println("监听到用户注册事件..." + event);
System.out.println("发送短信...");
}
}
启动类添加:
Component01 component01 = applicationContext.getBean(Component01.class);
component01.register();
结果如下:
小结
BeanFactory是最原始的context容器,其中仅包含了一些容器最基本的方法,它是ApplicationContext的父接口。ApplicationContext 的实现组合了它功能并进行扩展【国际化、资源通配符查找、环境信息查找、事件发布等等】。
容器实现
BeanFactory 实现的特点
首先来看一段手工注册 Bean 对象的程序
定义 Bean01、Bean02与 Config 类
@Configuration
static class Config {
@Bean
public Bean01 bean01() {
return new Bean01();
}
@Bean
public Bean02 bean02() {
return new Bean02();
}
}
static class Bean01 {
private static final Logger LOGGER = LoggerFactory.getLogger(Bean01.class);
@Autowired
private Bean02 bean02;
public Bean02 getBean02() {
return bean02;
}
public Bean01() {
LOGGER.info("构造 Bean01 ...");
}
}
static class Bean02 {
private static final Logger LOGGER = LoggerFactory.getLogger(Bean02.class);
public Bean02() {
LOGGER.info("构造 Bean02 ...");
}
}
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// bean的定义: class, scope, 初始化, 销毁等.
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
// 注册beanDefinition
beanFactory.registerBeanDefinition("config", beanDefinition);
// 获取容器中bean对象
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
我们用手动注册的方式 register 自定义的 config 类。
输出结果如下:
很奇怪的是,在我们以前理解的配置类实现中,@Configuration 注解是能够解析出类中标注了 @Bean 注解的方法,但在输出中并没有看到容器中有 Bean01 和 Bean02 对象。这是因为最基本的 context 容器并不具备解析 @Configuration 注解的能力,我们需要给它添加具有扩展功能的xxxPostProcessor【后置处理器】。
解决办法如下
// 给BeanFactory中添加一些常用的后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
// 仅是添加不能起对应的作用,需要执行对应的postProcessBeanFactory方法
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {
beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
});
添加后我们再遍历容器中对象结果如下:
可以看到已经有 @Bean 注解标注的对象,其它解析出来的是工具类添加的常用后置处理器对象。
接下来我们尝试去拿到我们添加的 Bean01 对象以及对象的 bean02 成员属性
Bean01 bean01 = beanFactory.getBean(Bean01.class);
System.out.println(bean01);
System.out.println(bean01.bean02);
结果如下:
我们发现 bean01 对象被正常构造但是其中 bean02 属性却为null,这是因为 bean02 属性使用的是 @Autowired 注解注入方式,但基础的 BeanFactory 还不具备解析它的能力。有同学就奇怪了,上面明明已经添加了对应的后处理器但为什么还是解析不了呢?原因是后处理器分为 BeanFactory 后处理器和Bean后处理器,上面我们仅仅只是针对前者进行处理,而 @Autowired 的解析需要对 Bean 后处理器进行操作。
我们在获取 bean 对象前加上对 Bean 后处理器的操作
// Bean后处理器,针对 bean 的生命周期的各个阶段进行扩展,例如@Autowired @Resource等...
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);
再次遍历结果如下:
可以发现 Bean02 属性被正确注入
容器单例对象创建的时机?
关于上述内容我们发现只有我们在第一次调用的时候单例对象才会被初始化,但在我们日常开发中,单例对象应该在容器初始化的时候就创建好,这时如果要用到这个功能需要调用 beanFactory.preInstantiateSingletons 方法。
我们加上方法后将获取对象代码注释,看看是否符合预期
beanFactory.preInstantiateSingletons();
// 获取容器中bean对象
// for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
// System.out.println(beanDefinitionName);
// }
//
// Bean01 bean01 = beanFactory.getBean(Bean01.class);
// System.out.println(bean01);
// System.out.println(bean01.bean02);
可以发现在并没有获取bean对象的前提下单例对象还是正确被初始化。
小结
BeanFactory 不会做的事:
- 不会主动调用 BeanFactory 和 Bean 后处理器
- 不会主动初始化单例对象
- 不会解析 ${} 与 #{}
扩展:后处理器的排序
一个有意思的话题:如果我们在成员变量上即加了 @Autowired 注解,同时也加了 @Resource 注解,哪一个会优先生效呢,我们接着往下看。
补充一下前置知识:@Autowired 默认是按照类型匹配,@Resource 默认按照名字匹配。如果前者需要实现按照名字匹配则需要配合 @Qualifier 注解。
新增一个接口类与两个实现类,并在bean01对象中添加成员属性
interface Inter {}
static class Bean03 implements Inter {
public Bean03() {
}
}
static class Bean04 implements Inter {
public Bean04() {
}
}
// Config类中加入
@Bean
public Bean03 bean03() {
return new Bean03();
}
@Bean
public Bean04 bean04() {
return new Bean04();
}
// Bean01类中加入
@Autowired
@Qualifier("bean03")
@Resource
private Inter bean04;
我们目的是同时使用这两个注解来注入bean对象,@Autowired 是注入 bean03 对象,而 @Resource 是注入 bean04 对象,究竟结果如何呢?一起来看看吧。
System.out.println(beanFactory.getBean(Bean01.class).getBean());
结果如下:
结果是Bean04,说明默认情况下 @Autowired 优先级要高于 @Resource,也就是说明解析 @Autowired 注解的后处理器优先级默认高于解析 @Resource 注解的后处理器。
beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
.forEach(beanPostProcessor -> {
System.out.println(">>>>" + beanPostProcessor);
beanFactory.addBeanPostProcessor(beanPostProcessor);
});
将后处理器顺序打印出来结果如下:
可以看到解析 @Autowired 注解的 AutowiredAnnotationBeanPostProcessor 确实是排在解析 @Resource 注解的 CommonAnnotationBeanPostProcessor 前面,下面我们来看看这个顺序是由什么来控制的呢?
beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
.sorted(beanFactory.getDependencyComparator())
.forEach(beanPostProcessor -> {
System.out.println(">>>>" + beanPostProcessor);
beanFactory.addBeanPostProcessor(beanPostProcessor);
});
加上BeanFactory比较器后再来看看结果
结果显示两个后处理器顺序调换且注入Bean03对象,符合预期。
我们在它们类中找到以下关于排序的字段
AutowiredAnnotationBeanPostProcessor:
private int order = Ordered.LOWEST_PRECEDENCE - 2;
前者数值小于后者,order数值越小优先级越高,故CommonAnnotationBeanPostProcessor后处理器优先级高于AutowiredAnnotationBeanPostProcessor。
ApplicationContext的常见实现和用法
ClassPathXmlApplicationContext
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bean02" class="com.shuttle.spring5.A02Application.Bean02"/>
<bean id="bean01" class="com.shuttle.spring5.A02Application.Bean01">
<property name="bean02" ref="bean02"/>
</bean>
</beans>
private static void testClassPathXmlApplicationContext() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("b01.xml");
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
结果如下:
原理:
// 基础的context容器
DefaultListableBeanFactory beanFactory1 = new DefaultListableBeanFactory();
System.out.println("读取之前...");
for (String beanDefinitionName : beanFactory1.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
System.out.println("读取之后...");
// 需要靠Xml解析器来读取对应资源文件
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory1);
// 将读取内容注入到beanFactory
xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource("b01.xml"));
for (String beanDefinitionName : beanFactory1.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
FileSystemXmlApplicationContext
和上述方式类似,仅是将文件获取路径方式更改,此处不再赘述。
AnnotationConfigApplicationContext
创建一个配置类
@Configuration
static class Config {
@Bean
public Bean01 bean01(Bean02 bean02) {
Bean01 bean01 = new Bean01();
bean01.setBean02(bean02);
return bean01;
}
@Bean
public Bean02 bean02() {
return new Bean02();
}
}
创建容器时将配置类对象当参数传入
private static void testAnnotationConfigApplicationContext() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
运行结果如下:
值得一提的是此实现不需要手动添加常见的后处理器,前两种方式也可以在xml配置中手动添加 <context:annotation-config/> 标签实现。
AnnotationConfigServletWebServerApplicationContext
编写配置类
@Configuration
static class WebConfig {
// Tomcat服务器
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
// 核心DispatchServlet
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
// 注册DispatcherServlet
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
@Bean("/hello")
public Controller controller1() {
return new Controller() {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().print("hello world~");
return null;
}
};
}
}
private static void testAnnotationConfigServletWebServerApplicationContext() {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
结果如下:
总结
本篇内容主要对 BeanFactory 与 ApplicationContext 的实现与使用做了简单概述,其中还有很多内容值得大家去探究,有心的读者也可以尝试阅读一下有关内容的源码。