封面画师:T5-茨舞(微博) 封面ID:92893443
参考视频:黑马程序员Spring视频教程,全面深度讲解spring5底层原理
源码仓库:mofan212/advanced-spring (github.com)
1. 容器接口
以 SpringBoot 的启动类为例:
1 2 3 4 5 6 7
| @Slf4j @SpringBootApplication public class A01Application { public static void main(String[] args) { SpringApplication.run(A01Application.class, args); } }
|
其中的 run()
方法是有返回值的:
1
| ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
|
在 IDEA 中使用快捷键 Ctrl + Alt + U 查看 ConfigurableApplicationContext
类的类图:
ConfigurableApplicationContext
接口继承了 ApplicationContext
接口,而 ApplicationContext
接口又间接地继承了 BeanFactory
接口,除此之外还继承了其他很多接口,相当于对 BeanFactory
进行了拓展。
1.1 什么是 BeanFactory
- 它是
ApplicationContext
的父接口
- 它才是 Spring 的核心容器,主要的
ApplicationContext
实现 组合 了它的功能,也就是说,BeanFactory
是 ApplicationContext
中的一个成员变量。
常用的 context.getBean("xxx")
方法,其实是调用了 BeanFactory
的 getBean()
方法。
1.2 BeanFactory 能做什么?
进入 BeanFactory
接口,在 IDEA 中使用快捷键 Ctrl + F12 查看这个接口中所有的方法定义:
通过这些方法定义可知,BeanFactory
表面上只有 getBean()
方法,但实际上 Spring 中的控制反转、基本的依赖注入、乃至 Bean 的生命周期中的各个功能都是由它的实现类提供。
查看 DefaultListableBeanFactory
类的类图:
DefaultListableBeanFactory
实现了 BeanFactory
接口,它能管理 Spring 中所有的 Bean,当然也包含 Spring 容器中的那些单例对象。
DefaultListableBeanFactory
还继承了 DefaultSingletonBeanRegistry
类,这个类就是用来管理 Spring 容器中的单例对象。
在 IDEA 提供的类图中选中 DefaultSingletonBeanRegistry
,然后按下 F4 进入这个类。它有一个 Map
类型的成员变量 singletonObjects
:
1
| private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
|
Map
的 key 就是 Bean 的名字,而 value 是对应的 Bean,即单例对象。
现有如下两个 Bean:
1 2 3 4 5 6 7
| @Component public class Component1 { }
@Component public class Component2 { }
|
查看 singletonObjects
中是否存在这两个 Bean 的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Slf4j @SpringBootApplication public class A01Application { @SneakyThrows @SuppressWarnings("unchecked") public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects"); singletonObjects.setAccessible(true); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory); map.entrySet().stream().filter(e -> e.getKey().startsWith("component")) .forEach(e -> System.out.println(e.getKey() + "=" + e.getValue()));
context.close(); } }
|
运行 main()
方法后,控制台打印出:
component1=indi.mofan.bean.a01.Component1@25a5c7db
component2=indi.mofan.bean.a01.Component2@4d27d9d
1.3 ApplicationContext 的功能
回顾 ConfigurableApplicationContext
类的类图:
ApplicationContext
除了继承 BeanFactory
外,还继承了:
- MessageSource:使其具备处理国际化资源的能力
- ResourcePatternResolver:使其具备使用通配符进行资源匹配的能力
- EnvironmentCapable:使其具备读取 Spring 环境信息、配置文件信息的能力
- ApplicationEventPublisher:使其具备发布事件的能力
MessageSource
的使用
在 SpringBoot 项目的 resources 目录下创建 messages.properties、messages_en.properties、messages_zh_CN.properties、messages_zh_TW.properties 四个国际化文件,除 messages.properties 外,其余三个文件内容如下:
测试 MessageSource
接口中 getMessage()
方法的使用:
1 2 3 4 5 6 7 8 9 10 11
| @SneakyThrows @SuppressWarnings("unchecked") public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
System.out.println(context.getMessage("thanks", null, Locale.ENGLISH)); System.out.println(context.getMessage("thanks", null, Locale.SIMPLIFIED_CHINESE)); System.out.println(context.getMessage("thanks", null, Locale.TRADITIONAL_CHINESE)); context.close(); }
|
运行 main()
方法后,控制台打印出:
Thank you
谢谢
謝謝
国际化资源由 ResourceBundleMessageSource
进行处理,使用“干净”的 Spring 容器 GenericApplicationContext
,并添加对应的 Bean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("messageSource", MessageSource.class, () -> { ResourceBundleMessageSource ms = new ResourceBundleMessageSource(); ms.setDefaultEncoding("utf-8"); ms.setBasename("messages"); return ms; });
context.refresh();
System.out.println(context.getMessage("thanks", null, Locale.ENGLISH)); System.out.println(context.getMessage("thanks", null, Locale.SIMPLIFIED_CHINESE)); System.out.println(context.getMessage("thanks", null, Locale.TRADITIONAL_CHINESE)); }
|
ResourcePatternResolver
的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @SneakyThrows @SuppressWarnings("unchecked") public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
Resource[] resources = context.getResources("classpath:application.properties"); Assert.isTrue(resources.length > 0, "加载类路径下的 application.properties 文件失败");
resources = context.getResources("classpath*:META-INF/spring.factories"); Assert.isTrue(resources.length > 0, "加载类路径下的 META-INF/spring.factories 文件失败"); context.close(); }
|
EnvironmentCapable
的使用
1 2 3 4 5 6 7 8 9 10
| @SneakyThrows @SuppressWarnings("unchecked") public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
System.out.println(context.getEnvironment().getProperty("java_home")); System.out.println(context.getEnvironment().getProperty("properties.name")); context.close(); }
|
java_home
是从环境变量中读取,properties.name
则是从 application.yml 配置文件中读取。
1 2 3 4 5 6
| properties: name: "mofan" age: 20 person: gender: "man"
|
运行 main()
方法后,控制台打印出:
D:\environment\JDK1.8
mofan
ApplicationEventPublisher
的使用
定义事件类 UserRegisteredEvent
:
1 2 3 4 5 6 7
| public class UserRegisteredEvent extends ApplicationEvent { private static final long serialVersionUID = 6319117283222183184L;
public UserRegisteredEvent(Object source) { super(source); } }
|
将 Component1
作为发送事件的 Bean:
1 2 3 4 5 6 7 8 9 10 11
| @Slf4j @Component public class Component1 { @Autowired private ApplicationEventPublisher context;
public void register() { log.debug("用户注册"); context.publishEvent(new UserRegisteredEvent(this)); } }
|
将 Component2
作为事件监听器:
1 2 3 4 5 6 7 8 9
| @Slf4j @Component public class Component2 { @EventListener public void aaa(UserRegisteredEvent event) { log.debug("{}", event); log.debug("发送短信"); } }
|
在 main()
方法中使用 Component1
发送事件:
1 2 3 4 5 6 7 8 9
| @SneakyThrows @SuppressWarnings("unchecked") public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
context.getBean(Component1.class).register(); context.close(); }
|
运行 main()
方法后,控制台打印出:
indi.mofan.bean.a01.Component1 - 用户注册
indi.mofan.bean.a01.Component2 - indi.mofan.bean.a01.UserRegisteredEvent[source=indi.mofan.bean.a01.Component1@25a5c7db]
indi.mofan.bean.a01.Component2 - 发送短信
2. 容器实现
2.1 BeanFactory 的实现
现有如下类,尝试将 Config
添加到 Bean 工厂中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Configuration static class Config { @Bean public Bean1 bean1() { return new Bean1(); }
@Bean public Bean2 bean2() { return new Bean2(); } }
@Slf4j static class Bean1 { public Bean1() { log.debug("构造 Bean1()"); }
@Autowired private Bean2 bean2;
public Bean2 getBean2() { return bean2; } }
@Slf4j static class Bean2 { public Bean2() { log.debug("构造 Bean2()"); } }
|
需要使用到 BeanFactory
的一个实现类: DefaultListableBeanFactory
。有了 Bean 工厂,还需要定义 Bean,之后再把定义的 Bean 注册到工厂即可。
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class) .setScope("singleton") .getBeanDefinition(); beanFactory.registerBeanDefinition("config", beanDefinition);
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println); }
|
config
现在 Bean 工厂中 有且仅有一个 名为 config
的 Bean。
解析配置类
根据对 @Configuration
和 @Bean
两个注解的认识可知,Bean 工厂中应该还存在 bean1
和 bean2
,那为什么现在没有呢?
很明显是现在的 BeanFactory
缺少了解析 @Configuration
和 @Bean
两个注解的能力。
1 2 3 4 5 6 7
| public static void main(String[] args) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory); Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println); }
|
config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
根据打印出的信息,可以看到有一个名为 org.springframework.context.annotation.internalConfigurationAnnotationProcessor
的 Bean,根据其所含的 ConfigurationAnnotationProcessor
字样,可以知道这个 Bean 就是用来处理 @Configuration
和 @Bean
注解的,将配置类中定义的 Bean 信息补充到 BeanFactory
中。
那为什么在 Bean 工厂中依旧没有 bean1
和 bean2
呢?
现在仅仅是将处理器添加到了 Bean 工厂,还没有使用处理器。
使用处理器很简单,先获取到处理器,然后再使用即可。像 internalConfigurationAnnotationProcessor
这样的 Bean,都有一个共同的类型,名为 BeanFactoryPostProcessor
,因此可以:
1 2 3 4 5 6 7 8
| public static void main(String[] args) {
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(i -> i.postProcessBeanFactory(beanFactory)); Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println); }
|
config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean1
bean2
依赖注入
bean1
和 bean2
已经被添加到 Bean 工厂中,尝试获取 bean1
中的 bean2
,查看 bean2
是否成功注入到 bean1
中:
1 2 3 4
| public static void main(String[] args) { System.out.println(beanFactory.getBean(Bean1.class).getBean2()); }
|
null
bean2
没有成功被注入到 bean1
中。
在先前添加到 BeanFactory
中的后置处理器里,有名为 internalAutowiredAnnotationProcessor
和 internalCommonAnnotationProcessor
的两个后置处理器。前者用于解析 @Autowired
注解,后者用于解析 @Resource
注解,它们都有一个共同的类型 BeanPostProcessor
,因此可以:
1 2 3 4 5 6 7
| public static void main(String[] args) { System.out.println("---------------------------------------------"); beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor); System.out.println(beanFactory.getBean(Bean1.class).getBean2()); }
|
----------------------------------------
[main] DEBUG indi.mofan.bean.a02.TestBeanFactory$Bean1 - 构造 Bean1()
[main] DEBUG indi.mofan.bean.a02.TestBeanFactory$Bean2 - 构造 Bean2()
indi.mofan.bean.a02.TestBeanFactory$Bean2@6ee12bac
建立 BeanPostProcessor
和 BeanFactory
的关系后,bean2
被成功注入到 bean1
中了。
除此之外还可以发现:当需要使用 Bean 时,Bean 才会被创建,即按需加载。那有没有什么办法预先就初始化好单例对象呢?
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) {
beanFactory.preInstantiateSingletons(); System.out.println("---------------------------------------------"); beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor); System.out.println(beanFactory.getBean(Bean1.class).getBean2()); }
|
[main] DEBUG indi.mofan.bean.a02.TestBeanFactory$Bean1 - 构造 Bean1()
[main] DEBUG indi.mofan.bean.a02.TestBeanFactory$Bean2 - 构造 Bean2()
----------------------------------------
indi.mofan.bean.a02.TestBeanFactory$Bean2@6ee12bac
目前可以知道,BeanFactory
不会 :
- 主动调用
BeanFactory
后置处理器;
- 主动添加
Bean
后置处理器;
- 主动初始化单例对象;
- 解析
${}
和 #{}
后置处理器的排序
在最初给出的类信息中进行补充:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @Configuration static class Config {
@Bean public Bean3 bean3() { return new Bean3(); }
@Bean public Bean4 bean4() { return new Bean4(); } }
interface Inter {
}
@Slf4j static class Bean3 implements Inter { public Bean3() { log.debug("构造 Bean3()"); } }
@Slf4j static class Bean4 implements Inter { public Bean4() { log.debug("构造 Bean4()"); } }
@Slf4j static class Bean1 {
@Autowired @Resource(name = "bean4") private Inter bean3;
private Inter getInter() { return bean3; } }
|
向 Bean 工厂中添加了 bean3
和 bean4
,并且计划在 bean1
中注入 Inter
类型的 Bean。
现在 Bean 工厂中 Inter
类型的 Bean 有两个,分别是 bean3
、bean4
,那么会注入哪一个呢?
如果只使用 @Autowired
,首先会按照类型注入,如果同种类型的 Bean 有多个,再按照变量名称注入,如果再注入失败,就报错;如果只使用 @Resource
,也会采取与 @Autowired
一样的注入策略,只不过 @Resource
注解还可以指定需要注入 Bean 的 id(使用 name
属性进行指定),如果指定了需要注入 Bean 的 id,就直接按照指定的 id 进行注入,如果失败就报错。
那如果即使用 @Autowired
又使用 @Resource(name = "bean4")
呢?
1 2 3 4
| public static void main(String[] args) { System.out.println(beanFactory.getBean(Bean1.class).getInter()); }
|
indi.mofan.bean.a02.TestBeanFactory$Bean3@8e0379d
根据打印的结果可知,@Autowired
先生效了,这是因为 internalAutowiredAnnotationProcessor
排在 internalCommonAnnotationProcessor
之前。可以查看它们的先后关系:
1 2 3 4 5 6 7
| public static void main(String[] args) { beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(i -> { System.out.println(">>>> " + i); beanFactory.addBeanPostProcessor(i); }); }
|
>>>> org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@6385cb26
>>>> org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@38364841
也可以改变它们的顺序,然后再查看注入的是 bean3
还是 bean4
:
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) { beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream() .sorted(Objects.requireNonNull(beanFactory.getDependencyComparator())) .forEach(i -> { System.out.println(">>>> " + i); beanFactory.addBeanPostProcessor(i); }); System.out.println(beanFactory.getBean(Bean1.class).getInter()); }
|
>>>> org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@6385cb26
>>>> org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@38364841
indi.mofan.bean.a02.TestBeanFactory$Bean4@52e677af
改变 BeanPostProcessor
的先后顺序后,@Resource(name = "bean4")
生效了,成功注入了 bean4
。
为什么使用 beanFactory.getDependencyComparator()
后就改变了 BeanPostProcessor
的先后顺序呢?
在调用的 AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
方法源码中有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) { registerAnnotationConfigProcessors(registry, null); }
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, @Nullable Object source) {
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); if (beanFactory != null) { if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); } if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); } } }
|
设置的 AnnotationAwareOrderComparator
比较器会根据设置的 order
信息进行比较。
AutowiredAnnotationBeanPostProcessor
设置的 order
是:
1
| private int order = Ordered.LOWEST_PRECEDENCE - 2;
|
CommonAnnotationBeanPostProcessor
设置的 order
是:
1 2 3 4
| public CommonAnnotationBeanPostProcessor() { setOrder(Ordered.LOWEST_PRECEDENCE - 3); }
|
值越小,优先级越大,就排在更前面,因此当设置了 AnnotationAwareOrderComparator
比较器后,CommonAnnotationBeanPostProcessor
排在更前面,@Resource
就先生效。
2.2 ApplicationContext 的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@Slf4j public class A02Application {
static class Bean1 {
}
static class Bean2 { @Getter @Setter private Bean1 bean1; } }
|
ClassPathXmlApplicationContext
较为经典的容器,基于 classpath 下的 xml 格式的配置文件来创建。
1 2 3 4 5 6 7 8 9 10
| <?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="bean1" class="indi.mofan.bean.a02.A02Application.Bean1"/>
<bean id="bean2" class="indi.mofan.bean.a02.A02Application.Bean2"> <property name="bean1" ref="bean1" /> </bean> </beans>
|
1 2 3 4 5
| private static void testClassPathXmlApplicationContext() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("b01.xml"); Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println); System.out.println(context.getBean(Bean2.class).getBean1()); }
|
bean1
bean2
indi.mofan.bean.a02.A02Application$Bean1@2db7a79b
FileSystemXmlApplicationContext
与 ClassPathXmlApplicationContext
相比,FileSystemXmlApplicationContext
是基于磁盘路径下 xml 格式的配置文件来创建。
1 2 3 4 5 6
| private static void testFileSystemXmlApplicationContext() { FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("bean\\src\\main\\resources\\b01.xml"); Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println); System.out.println(context.getBean(Bean2.class).getBean1()); }
|
bean1
bean2
indi.mofan.bean.a02.A02Application$Bean1@2db7a79b
从 XML 文件中读取 Bean 的定义
ClassPathXmlApplicationContext
和 FileSystemXmlApplicationContext
都依赖于从 XML 文件中读取 Bean 的信息,而这都利用了 XmlBeanDefinitionReader
进行读取。
1 2 3 4 5 6 7 8 9 10
| private static void testXmlBeanDefinitionReader() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); System.out.println("读取之前..."); Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println); System.out.println("读取之后..."); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new FileSystemResource("bean\\src\\main\\resources\\b01.xml")); Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println); }
|
读取之前...
读取之后...
bean1
bean2
AnnotationConfigApplicationContext
基于 Java 配置类来创建。首先定义配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Configuration static class Config { @Bean public Bean1 bean1() { return new Bean1(); }
@Bean public Bean2 bean2(Bean1 bean1) { Bean2 bean2 = new Bean2(); bean2.setBean1(bean1); return bean2; } }
|
1 2 3 4 5 6
| private static void testAnnotationConfigApplicationContext() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println); System.out.println(context.getBean(Bean2.class).getBean1()); }
|
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
a02Application.Config
bean1
bean2
indi.mofan.bean.a02.A02Application$Bean1@1f0f1111
与前面两种基于 XML 创建 ApplicationContext
的方式相比,使用 AnnotationConfigApplicationContext
后,使得容器中多了一些后置处理器相关的 Bean。
如果要在先前的两种方式中也添加上这些 Bean,可以在 XML 进行配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <bean id="bean1" class="indi.mofan.bean.a02.A02Application.Bean1"/>
<bean id="bean2" class="indi.mofan.bean.a02.A02Application.Bean2"> <property name="bean1" ref="bean1" /> </bean>
<context:annotation-config /> </beans>
|
AnnotationConfigServletWebServerApplicationContext
基于 Java 配置类来创建,用于 web 环境。首先定义配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Configuration static class WebConfig { @Bean public ServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(); }
@Bean public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); }
@Bean public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) { return new DispatcherServletRegistrationBean(dispatcherServlet, "/"); }
@Bean("/hello") public Controller controller1() { return (request, response) -> { response.getWriter().println("hello"); return null; }; } }
|
1 2 3 4
| private static void testAnnotationConfigServletWebServerApplicationContext() { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); }
|
运行代码,在浏览器中访问 http://localhost:8080/hello
路径则会显示出 hello
字样:
2.3 【补充】BeanFactory 接口体系
BeanFactory
其实就是 Spring IoC 容器,它本身是一个接口,提供了一系列获取 Bean 的方式。
基于它也有众多子接口:
ListableBeanFactory
:提供获取 Bean 集合的能力,比如一个接口可能有多个实现,通过该接口下的方法就能获取某种类型的所有 Bean;
HierarchicalBeanFactory
:Hierarchical
意为“层次化”,通常表示一种具有层级结构的概念或组织方式,这种层次化结构可以通过父子关系来表示对象之间的关联,比如树、图、文件系统、组织架构等。根据该接口下的方法可知,能够获取到父容器,说明 BeanFactory
有父子容器概念;
AutowireCapableBeanFactory
:提供了创建 Bean、自动装配 Bean、属性填充、Bean 初始化、依赖注入等能力,比如 @Autowired
注解的底层实现就依赖于该接口的 resolveDependency()
方法;
ConfigurableBeanFactory
:该接口并未直接继承至 BeanFactory
,而是继承了 HierarchicalBeanFactory
。Configurable
意为“可配置的”,就是说该接口用于对 BeanFactory
进行一些配置,比如设置类型转换器。
2.4 【补充】读取 BeanDefinition
BeanDefinition
也是一个接口,它封装了 Bean 的定义,Spring 根据 Bean 的定义,就能创建出符合要求的 Bean。
读取 BeanDefinition
可以通过下列两种类完成:
BeanDefinitionReader
ClassPathBeanDefinitionScanner
BeanDefinitionReader
该接口中对 loadBeanDefinitions()
方法进行了多种重载,支持传入一个或多个 Resource
对象、资源位置来加载 BeanDefinition
。
它有一系列相关实现,比如:
XmlBeanDefinitionReader
:通过读取 XML 文件来加载;
PropertiesBeanDefinitionReader
:通过读取 properties 文件来加载,此类已经被 @Deprecated
注解标记;
除此之外,还有一个 AnnotatedBeanDefinitionReader
,尽管它并不是 BeanDefinition
的子类,但它们俩长得很像,根据其类注释可知:它能够通过编程的方式对 Bean 进行注册,是 ClassPathBeanDefinitionScanner
的替代方案,能读取通过注解定义的 Bean。
ClassPathBeanDefinitionScanner
通过扫描指定包路径下的 @Component
及其派生注解来注册 Bean,是 @ComponentScan
注解的底层实现。
比如 MyBatis 通过继承 ClassPathBeanDefinitionScanner
实现通过 @MapperScan
注解来扫描指定包下的 Mapper 接口。
BeanDefinitionRegistry
AnnotatedBeanDefinitionReader
和 ClassPathBeanDefinitionScanner
中都有一个 BeanDefinitionRegistry
类型的成员变量,它是一个接口,提供了 BeanDefinition
的增加、删除和查找功能。
注册与获取 Bean
根据前面的补充,现在可以这样注册并获取 Bean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class DefaultListableBeanFactoryTest { static class MyBean { }
public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(beanFactory); reader.registerBean(MyBean.class); MyBean bean = beanFactory.getBean(MyBean.class); System.out.println(bean); } }
|
2.5 【补充】ApplicationContext 接口体系
1 2 3
| public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { }
|
ApplicationContext
接口继承了许多接口,可谓是接口的集大成者,其中:
EnvironmentCapable
:提供获取 Environment
的能力
ListableBeanFactory
:提供了获取某种类型的 Bean 集合的能力
HierarchicalBeanFactory
:提供了获取父容器的能力
MessageSource
:提供了对国际化信息进行处理的能力
ApplicationEventPublisher
:提供了事件发布能力
ResourcePatternResolver
:提供了通过通配符获取多个资源的能力
虽然 ApplicationContext
继承了很多接口,但这些能力的实现是通过一种委派(Delegate)的方式实现的,这种方式也被叫做委派模式,但它并不属于 GoF 的 23 种设计模式中的一种,是一种面向对象的设计模式。什么是委派呢?
1 2 3 4 5 6 7 8 9 10 11 12
| public class MyApplicationContext implements ApplicationContext {
private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
@Override public Resource[] getResources(String locationPattern) throws IOException { return resourcePatternResolver.getResources(locationPattern); }
}
|
实现获取资源的方式并不是由实现类自身完成,而是交给其内部的一个成员变量完成,这样的方式就是委派(这和对象适配器模式很相似)。
在日常编码遇到这样的实现逻辑时,类名可以以 Delegate
结尾。
ConfigurableApplicationContext
ApplicationContext
有一个子接口 ConfigurableApplicationContext
,从类名就可以看出,它提供了对 ApplicationContext
进行配置的能力,浏览其内部方法可知,提供了诸如设置父容器、设置 Environment
等能力。
AbstractApplicationContext
ApplicationContext
有一个非常重要的抽象实现 AbstractApplicationContext
,其他具体实现都会继承这个抽象实现,在其内部通过委派的方式实现了一些接口的能力,除此之外还有一个与 Spring Bean 的生命周期息息相关的方法:refresh()
。
3. Bean 的生命周期
自定义一个 SpringBoot 的主启动类:
1 2 3 4 5 6 7 8
| @SpringBootApplication public class A03Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A03Application.class, args); context.close(); } }
|
在启动类所在路径下再定义一个类,使其能够被自动装配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Slf4j @Component public class LifeCycleBean {
public LifeCycleBean() { log.info("构造"); }
@Autowired public void autowire(@Value("${JAVA_HOME}") String home) { log.info("依赖注入: {}", home); }
@PostConstruct public void init() { log.info("初始化"); }
@PreDestroy public void destroy() { log.info("销毁"); } }
|
运行主启动类,查看控制台的日志信息(只列举主要信息):
indi.mofan.bean.a03.LifeCycleBean : 构造
indi.mofan.bean.a03.LifeCycleBean : 依赖注入: D:\environment\JDK1.8
indi.mofan.bean.a03.LifeCycleBean : 初始化
indi.mofan.bean.a03.LifeCycleBean : 销毁
除此之外,Spring 还提供了一些对 Bean 生命周期的各个阶段进行拓展的 BeanPostProcessor
,比如 InstantiationAwareBeanPostProcessor
和 DestructionAwareBeanPostProcessor
。
实现这两个接口,并使用 @Component
注解标记实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| @Slf4j @Component public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {
@Override public void postProcessBeforeDestruction(Object o, String beanName) throws BeansException { if ("lifeCycleBean".equals(beanName)) { log.info("<<<<<<<<<< 销毁执行之前,如 @PreDestroy"); } }
@Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { if ("lifeCycleBean".equals(beanName)) { log.info("<<<<<<<<<< 实例化之前执行,这里返回的对象会替换掉原本的 bean"); } return null; }
@Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { if ("lifeCycleBean".equals(beanName)) { log.info("<<<<<<<<<< 实例化之后执行,如果返回 false 会跳过依赖注入节点"); } return true; }
@Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException { if ("lifeCycleBean".equals(beanName)) { log.info("<<<<<<<<<< 依赖注入阶段执行,如 @Autowired、@Value、@Resource"); } return pvs; }
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if ("lifeCycleBean".equals(beanName)) { log.info("<<<<<<<<<< 初始化执行之前,这里返回的对象会替换掉原本的 bean,如 @PostConstruct、@ConfigurationProperties"); } return bean; }
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if ("lifeCycleBean".equals(beanName)) { log.info("<<<<<<<<<< 初始化之后执行,这里返回的对象会替换掉原本的 bean,如代理增强"); } return bean; } }
|
再运行主启动类,查看控制台的日志信息(只列举主要信息):
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 实例化之前执行,这里返回的对象会替换掉原本的 bean
indi.mofan.bean.a03.LifeCycleBean : 构造
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 实例化之后执行,如果返回 false 会跳过依赖注入节点
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 依赖注入阶段执行,如 @Autowired、@Value、@Resource
indi.mofan.bean.a03.LifeCycleBean : 依赖注入: D:\environment\JDK1.8
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 初始化执行之前,这里返回的对象会替换掉原本的 bean,如 @PostConstruct、@ConfigurationProperties
indi.mofan.bean.a03.LifeCycleBean : 初始化
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 初始化之后执行,这里返回的对象会替换掉原本的 bean,如代理增强
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 销毁执行之前,如 @PreDestroy
indi.mofan.bean.a03.LifeCycleBean : 销毁
为什么实现了 BeanPostProcessor
接口后就能够在 Bean 生命周期的各个阶段进行拓展呢?
这使用了模板方法设计模式。
现有如下代码,模拟 BeanFactory
构造 Bean:
1 2 3 4 5 6 7 8 9
| static class MyBeanFactory { public Object getBean() { Object bean = new Object(); System.out.println("构造 " + bean); System.out.println("依赖注入 " + bean); System.out.println("初始化 " + bean); return bean; } }
|
假设现在需要在依赖注入之后,初始化之前进行其他的操作,那首先能想到的就是在这个位置直接书写相关操作的代码,但这会使代码更加臃肿、增加耦合性,显然不是一种好方式。
可以定义一个接口:
1 2 3
| interface BeanPostProcessor { void inject(Object bean); }
|
然后对 MyBeanFactory
进行修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| static class MyBeanFactory { public Object getBean() { Object bean = new Object(); System.out.println("构造 " + bean); System.out.println("依赖注入 " + bean); for (BeanPostProcessor processor : processors) { processor.inject(bean); } System.out.println("初始化 " + bean); return bean; }
private List<BeanPostProcessor> processors = new ArrayList<>();
public void addProcessor(BeanPostProcessor processor) { processors.add(processor); } }
|
之后如果需要拓展,调用 MyBeanFactory
实例的 addProcessor()
方法添加拓展逻辑即可:
1 2 3 4 5 6
| public static void main(String[] args) { MyBeanFactory beanFactory = new MyBeanFactory(); beanFactory.addProcessor(bean -> System.out.println("解析 @Autowired")); beanFactory.addProcessor(bean -> System.out.println("解析 @Resource")); beanFactory.getBean(); }
|
构造 java.lang.Object@49097b5d
依赖注入 java.lang.Object@49097b5d
解析 @Autowired
解析 @Resource
初始化 java.lang.Object@49097b5d
Bean 生命周期图:
graph LR
创建 --> 依赖注入
依赖注入 --> 初始化
初始化 --> 可用
可用 --> 销毁
4. Bean 后置处理器
4.1 常见的 Bean 后置处理器
现有如下三个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
@Slf4j @ToString public class Bean1 { private Bean2 bean2;
@Autowired public void setBean2(Bean2 bean2) { log.info("@Autowired 生效: {}", bean2); this.bean2 = bean2; }
private Bean3 bean3;
@Resource public void setBean3(Bean3 bean3) { log.info("@Resource 生效: {}", bean3); this.bean3 = bean3; }
private String home;
@Autowired public void setHome(@Value("${JAVA_HOME}") String home) { log.info("@Value 生效: {}", home); this.home = home; }
@PostConstruct public void init() { log.info("@PostConstruct 生效"); }
@PreDestroy public void destroy() { log.info("@PreDestroy 生效"); } }
|
1 2 3 4 5
| public class Bean2 { }
public class Bean3 { }
|
Bean2
和 Bean3
很简单,而在 Bean1
中使用了多个注解以实现 Bean 注入和值注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class A04Application { public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("bean1", Bean1.class); context.registerBean("bean2", Bean2.class); context.registerBean("bean3", Bean3.class);
context.refresh();
context.close(); } }
|
运行上述方法后,控制台中只打印了与 Spring 相关的日志信息,也就是说 Bean1
中使用的注解并没有生效。
向 GenericApplicationContext
添加一些与 Bean 后置处理器相关的 Bean,使得 Bean1
中使用的注解能够生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static void main(String[] args) {
context.registerBean("bean3", Bean3.class);
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
context.refresh();
}
|
indi.mofan.bean.a04.Bean1 - @Autowired 生效: indi.mofan.bean.a04.Bean2@29b5cd00
indi.mofan.bean.a04.Bean1 - @Value 生效: D:\environment\JDK1.8
@Autowired
和 @Value
注解成功生效,但 @Resource
、@PostConstruct
和 @PreDestroy
依旧没有生效,因此还需要添加解析它们的 Bean 后置处理器。
1 2 3 4 5 6 7 8
| public static void main(String[] args) {
context.registerBean(CommonAnnotationBeanPostProcessor.class);
}
|
indi.mofan.bean.a04.Bean1 - @Resource 生效: indi.mofan.bean.a04.Bean3@12cdcf4
indi.mofan.bean.a04.Bean1 - @Autowired 生效: indi.mofan.bean.a04.Bean2@6121c9d6
indi.mofan.bean.a04.Bean1 - @Value 生效: D:\environment\JDK1.8
indi.mofan.bean.a04.Bean1 - @PostConstruct 生效
INFO indi.mofan.bean.a04.Bean1 - @PreDestroy 生效
解析 @ConfigurationProperties
使用 @ConfigurationProperties
可以指定配置信息的前缀,使得配置信息的读取更加简单。比如:
1 2 3 4 5 6 7 8
| @Getter @Setter @ToString @ConfigurationProperties(prefix = "java") public class Bean4 { private String home; private String version; }
|
上述代码用于获取环境变量中 java.home
和 java.version
的信息。
对先前的 main()
方法进行补充:
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) { context.registerBean("bean4", Bean4.class);
System.out.println(context.getBean(Bean4.class));
}
|
Bean4(home=null, version=null)
Bean4
成功添加到容器中,但值注入失败了,显然也是因为缺少解析 @ConfigurationProperties
注解的后置处理器。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) { context.registerBean("bean4", Bean4.class);
ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());
System.out.println(context.getBean(Bean4.class));
}
|
Bean4(home=D:\environment\JDK1.8\jre, version=1.8.0_251)
4.2 AutowiredAnnotationBeanPostProcessor
通过前文可知 AutowiredAnnotationBeanPostProcessor
用于解析 @Autowired
和 @Value
注解,那它究竟是怎么工作的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerSingleton("bean2", new Bean2()); beanFactory.registerSingleton("bean3", new Bean3()); beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
AutowiredAnnotationBeanPostProcessor postProcessor = new AutowiredAnnotationBeanPostProcessor(); postProcessor.setBeanFactory(beanFactory);
Bean1 bean1 = new Bean1(); System.out.println(bean1); postProcessor.postProcessProperties(null, bean1, "bean1"); System.out.println(bean1); }
|
Bean1(bean2=null, bean3=null, home=null)
21:31:27.409 [main] INFO indi.mofan.bean.a04.Bean1 - @Value 生效: ${JAVA_HOME}
21:31:27.419 [main] INFO indi.mofan.bean.a04.Bean1 - @Autowired 生效: indi.mofan.bean.a04.Bean2@5bcab519
Bean1(bean2=indi.mofan.bean.a04.Bean2@5bcab519, bean3=null, home=${JAVA_HOME})
在未调用 AutowiredAnnotationBeanPostProcessor#postProcessProperties()
方法时,Bean1
中的 bean2
、bean3
和 home
都没有注入成功,而在调用之后,成功注入了 bean2
和 home
,但 home
的值似乎有点奇怪,并没有打印出前文中相同的值,可能是因为没有成功解析 #{}
?
至于 bean3
为什么没注入成功,是因为 bean3
的注入是利用 @Resource
,而不是 @Autowired
。如果对 Bean1
进行修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Bean1 { @Autowired private Bean3 bean3;
@Resource public void setBean3(Bean3 bean3) { log.info("@Resource 生效: {}", bean3); this.bean3 = bean3; } }
|
再次运行有:
Bean1(bean2=null, bean3=null, home=null)
21:36:36.402 [main] INFO indi.mofan.bean.a04.Bean1 - @Value 生效: ${JAVA_HOME}
21:36:36.406 [main] INFO indi.mofan.bean.a04.Bean1 - @Autowired 生效: indi.mofan.bean.a04.Bean2@490ab905
Bean1(bean2=indi.mofan.bean.a04.Bean2@490ab905, bean3=indi.mofan.bean.a04.Bean3@56ac3a89, home=${JAVA_HOME})
成功注入了 bean3
。如果想要成功注入 home
,则需要在 BeanFactory
中添加 #{}
的解析器:
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) {
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);
postProcessor.postProcessProperties(null, bean1, "bean1"); System.out.println(bean1); }
|
Bean1(bean2=null, bean3=null, home=null)
indi.mofan.bean.a04.Bean1 - @Value 生效: D:\environment\JDK1.8
indi.mofan.bean.a04.Bean1 - @Autowired 生效: indi.mofan.bean.a04.Bean2@4fe3c938
Bean1(bean2=indi.mofan.bean.a04.Bean2@4fe3c938, bean3=indi.mofan.bean.a04.Bean3@5383967b, home=D:\environment\JDK1.8)
AutowiredAnnotationBeanPostProcessor#postProcessProperties()
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try { metadata.inject(bean, beanName, pvs); } catch (BeanCreationException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex); } return pvs; }
|
其中的 findAutowiringMetadata()
用于查找指定的 bean
对象中哪些地方使用了 @Autowired
、@Value
等与注入相关的注解,并将这些信息封装在 InjectionMetadata
对象中,之后调用其 inject()
方法利用反射完成注入。
findAutowiringMetadata()
方法是一个私有方法,尝试利用反射进行调用并进行断点查看 InjectionMetadata
对象中的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @SneakyThrows public static void main(String[] args) { AutowiredAnnotationBeanPostProcessor postProcessor = new AutowiredAnnotationBeanPostProcessor(); postProcessor.setBeanFactory(beanFactory);
Bean1 bean1 = new Bean1();
Method method = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class); method.setAccessible(true); InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null); System.out.println(metadata); }
|
InjectionMetadata
中有一个名为 injectedElements
的集合类型成员变量,根据上图所示,injectedElements
存储了被相关注解标记的成员变量、方法的信息,因为 Bean1
中的 bean3
成员变量、setBean2()
和 setHome()
方法恰好被 @Autowired
注解标记。
然后按照源码一样,调用 InjectionMetadata#inject()
方法进行依赖注入:
1 2 3 4 5 6 7 8 9 10 11
| @SneakyThrows public static void main(String[] args) {
InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null);
metadata.inject(bean1, "bean1", null); System.out.println(bean1); }
|
indi.mofan.bean.a04.Bean1 - @Value 生效: D:\environment\JDK1.8
indi.mofan.bean.a04.Bean1 - @Autowired 生效: indi.mofan.bean.a04.Bean2@5383967b
Bean1(bean2=indi.mofan.bean.a04.Bean2@5383967b, bean3=indi.mofan.bean.a04.Bean3@2ac273d3, home=D:\environment\JDK1.8)
调用 inject()
方法后会利用反射进行依赖注入,但在反射之前,肯定得先拿到被注入的对象或值,那这些对象或值是怎么取到的呢?
可以通过以下代码概括:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @SneakyThrows public static void main(String[] args) {
Field bean3 = Bean1.class.getDeclaredField("bean3"); DependencyDescriptor dd1 = new DependencyDescriptor(bean3, false); Object o1 = beanFactory.doResolveDependency(dd1, null, null, null); System.out.println(o1);
Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class); DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), false); Object o2 = beanFactory.doResolveDependency(dd2, null, null, null); System.out.println(o2);
Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class); DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true); Object o3 = beanFactory.doResolveDependency(dd3, null, null, null); System.out.println(o3); }
|
indi.mofan.bean.a04.Bean3@2ac273d3
indi.mofan.bean.a04.Bean2@192b07fd
D:\environment\JDK1.8
5. BeanFactory 后置处理器
5.1 常见的 BeanFactory 后置处理器
先引入要用到的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.0</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.15</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
|
需要用到的类信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package indi.mofan.bean.a05;
@Configuration @ComponentScan("indi.mofan.bean.a05.component") public class Config {
@Bean public Bean1 bean1() { return new Bean1(); }
@Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; }
@Bean(initMethod = "init") public DruidDataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/advanced_spring"); dataSource.setName("root"); dataSource.setPassword("123456"); return dataSource; } }
|
1 2 3 4 5 6 7 8
| package indi.mofan.bean.a05;
@Slf4j public class Bean1 { public Bean1() { System.out.println("我被 Spring 管理啦"); } }
|
1 2 3 4 5 6 7 8 9
| package indi.mofan.bean.a05.component;
@Slf4j @Component public class Bean2 { public Bean2() { log.info("我被 Spring 管理啦"); } }
|
继续使用 GenericApplicationContext
作为容器,向容器中注册 config
:
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class);
context.refresh();
for (String name : context.getBeanDefinitionNames()) { System.out.println(name); }
context.close(); }
|
config
并没有打印出除 config
以外的 Bean 信息,也就是说 Config
类中的 @ComponentScan
和 @Bean
注解都没有生效。
根据经验,显然是因为缺少某个后置处理器。
1 2 3 4 5 6 7 8
| public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); context.registerBean(ConfigurationClassPostProcessor.class); }
|
indi.mofan.bean.a05.component.Bean2 - 我被 Spring 管理啦
indi.mofan.bean.a05.Bean1 - 我被 Spring 管理啦
com.alibaba.druid.pool.DruidDataSource - {dataSource-1,root} inited
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
bean2
bean1
sqlSessionFactoryBean
dataSource
在使用 MyBatis 时,经常会使用到 @Mapper
注解,而这个注解的解析也需要使用到特定的 BeanFactory 后置处理器。
以下两个接口被 @Mapper
注解标记:
1 2 3 4 5 6 7 8 9
| package indi.mofan.bean.a05.mapper;
@Mapper public interface Mapper1 { }
@Mapper public interface Mapper2 { }
|
然后添加解析 @Mapper
注解的后置处理器:
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); context.registerBean(ConfigurationClassPostProcessor.class); context.registerBean(MapperScannerConfigurer.class, i -> i.getPropertyValues().add("basePackage", "indi.mofan.bean.a05.mapper")); }
|
其中的 basePackage
是 MapperScannerConfigurer
中的一个成员变量,表示需要扫描的包路径,设置的值恰好是被 @Mapper
注解标记的接口所在的包路径。
控制台打印的信息中增加了:
mapper1
mapper2
除此之外,还有一些常用的后置处理器并没有在上述信息中体现。
5.2 模拟实现
移除向容器中添加的 ConfigurationClassPostProcessor
和 MapperScannerConfigurer
两个后置处理器,自行编码模拟它们功能的实现。
组件扫描之 @ComponentScan
在 Bean2
所在包路径下再增加两个类,用于后续测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package indi.mofan.bean.a05.component;
@Slf4j @Controller public class Bean3 { public Bean3() { log.info("我被 Spring 管理啦"); } }
@Slf4j public class Bean4 { public Bean4() { log.info("我被 Spring 管理啦"); } }
|
编写 ComponentScanPostProcessor
用于实现 @ComponentScan
注解的解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override @SneakyThrows public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class); if (componentScan != null) { for (String packageName : componentScan.basePackages()) { System.out.println(packageName); String path = "classpath*:" + packageName.replace(".", "/") + "/**/**.class"; Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path); CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); for (Resource resource : resources) { MetadataReader reader = factory.getMetadataReader(resource); AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
if (annotationMetadata.hasAnnotation(Component.class.getName()) || annotationMetadata.hasMetaAnnotation(Component.class.getName())) { AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(reader.getClassMetadata().getClassName()) .getBeanDefinition(); String name = generator.generateBeanName(beanDefinition, registry); registry.registerBeanDefinition(name, beanDefinition); } } } } }
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
} }
|
然后再测试:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); context.registerBean(ComponentScanPostProcessor.class);
context.refresh();
for (String name : context.getBeanDefinitionNames()) { System.out.println(name); }
context.close(); }
|
indi.mofan.bean.a05.component
indi.mofan.bean.a05.component.Bean2 : 我被 Spring 管理啦
indi.mofan.bean.a05.component.Bean3 : 我被 Spring 管理啦
config
indi.mofan.bean.a05.ComponentScanPostProcessor
bean2
bean3
没使用 ConfigurationClassPostProcessor
也实现了 @ComponentScan
注解的解析!
@Bean 的解析
Config
类中再增加一个方法作为干扰项:
1 2 3 4 5 6 7 8 9 10
| @Configuration @ComponentScan("indi.mofan.bean.a05.component") public class Config {
public Bean2 bean2() { return new Bean2(); } }
|
与解析 @ComponentScan
一样,自行编写一个 BeanFactoryPostProcessor
的实现类用于解析 @Bean
注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public class AtBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override @SneakyThrows public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); MetadataReader reader = factory.getMetadataReader(new ClassPathResource("indi/mofan/bean/a05/Config.class")); Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName()); for (MethodMetadata method : methods) { System.out.println(method); String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
String methodName = method.getMethodName(); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition() .setFactoryMethodOnBean(methodName, "config") .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); if (StringUtils.hasLength(initMethod)) { builder.setInitMethodName(initMethod); } AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); registry.registerBeanDefinition(methodName, beanDefinition); } }
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
} }
|
在构造 BeanDefinition
时调用了 setAutowireMode()
方法设置注入模式,这是因为在 Config
类中有一特殊的被 @Bean
标记的方法:
1 2 3 4 5 6
| @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; }
|
接收一个 DataSource
类型的参数,需要将容器中这个类型的 Bean 进行注入,设置的 AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR
注入模式则能完成这个功能。
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean(AtBeanPostProcessor.class);
context.refresh();
for (String name : context.getBeanDefinitionNames()) { System.out.println(name); }
context.close(); }
|
indi.mofan.bean.a05.Config.bean1()
indi.mofan.bean.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
indi.mofan.bean.a05.Config.dataSource()
indi.mofan.bean.a05.Bean1 : 我被 Spring 管理啦
com.alibaba.druid.pool.DruidDataSource : {dataSource-1,root} inited
config
indi.mofan.bean.a05.AtBeanPostProcessor
bean1
sqlSessionFactoryBean
dataSource
@Mapper 的解析
@Mapper
注解是在接口上使用的,但根据前文内容可知,@Mapper
被解析后在 Spring 容器中也存在与被标记的接口相关的 Bean。
难道 Spring 能管理接口?
那肯定是不行的,Spring 只能管理对象这是毋庸置疑的。那这些接口是怎么变成对象被 Spring 管理的呢?
这依赖于 MapperFactoryBean
将接口转换为对象。
在 Config
添加注册 Mapper1
和 Mapper2
的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Bean public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper1> factoryBean = new MapperFactoryBean<>(Mapper1.class); factoryBean.setSqlSessionFactory(sqlSessionFactory); return factoryBean; }
@Bean public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper2> factoryBean = new MapperFactoryBean<>(Mapper2.class); factoryBean.setSqlSessionFactory(sqlSessionFactory); return factoryBean; }
|
再运行 main()
方法可以看到容器中存在名为 mapper1
和 mapper2
的 Bean。
这种方式虽然可以完成 Mapper 接口的注册,但每次只能单个注册,不能批量注册。
移除 Config
类中的 mapper1()
和 mapper2()
方法,自行编写 BeanDefinitionRegistryPostProcessor
接口的实现类完成 @Mapper
注解的解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override @SneakyThrows public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources("classpath:indi/mofan/bean/a05/mapper/**/*.class"); AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); for (Resource resource : resources) { MetadataReader reader = factory.getMetadataReader(resource); ClassMetadata classMetadata = reader.getClassMetadata(); if (classMetadata.isInterface()) { AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class) .addConstructorArgValue(classMetadata.getClassName()) .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE) .getBeanDefinition(); AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()) .getBeanDefinition(); String name = generator.generateBeanName(bd, registry); registry.registerBeanDefinition(name, beanDefinition); } } }
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
} }
|
再测试下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @SneakyThrows public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class);
context.registerBean(AtBeanPostProcessor.class);
context.registerBean(MapperPostProcessor.class);
context.refresh();
for (String name : context.getBeanDefinitionNames()) { System.out.println(name); }
context.close(); }
|
indi.mofan.bean.a05.Config.bean1()
indi.mofan.bean.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
indi.mofan.bean.a05.Config.dataSource()
indi.mofan.bean.a05.Bean1 : 我被 Spring 管理啦
com.alibaba.druid.pool.DruidDataSource : {dataSource-1,root} inited
config
indi.mofan.bean.a05.AtBeanPostProcessor
indi.mofan.bean.a05.MapperPostProcessor
bean1
sqlSessionFactoryBean
dataSource
mapper1
mapper2
容器中存在 mapper1
和 mapper2
两个 Bean。
5.3 【补充】注册创建完成的 Bean
如果要将 Bean 添加到 Spring 容器中,需要先根据配置文件或注解信息为每一个 Bean 生成一个 BeanDefinition
,然后将这些 BeanDefinition
添加到 BeanDefinitionRegistry
中,当创建 Bean 对象时,直接从 BeanDefinitionRegistry
中获取 BeanDefinition
来生成 Bean。
如果生成的 Bean 是单例的,Spring 会将它们保存到 SingletonBeanRegistry
中,后续需要时从这里面寻找,避免重复创建。
那么向 Spring 容器中添加单例 Bean 时,可以跳过注册 BeanDefinition
,直接向 SingletonBeanRegistry
中添加创建完成的 Bean。既然添加的是创建完成的 Bean,所以 这个 Bean 不会经过 Spring 的生命周期。
SingletonBeanRegistry
是一个接口,它有一个子接口名为 ConfigurableListableBeanFactory
,而这恰好是 BeanFactoryPostProcessor
接口中抽象方法的参数:
1 2 3 4
| @FunctionalInterface public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; }
|
尝试使用 BeanFactoryPostProcessor
注册创建完成的 Bean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Slf4j public class TestBeanFactoryPostProcessor { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.registerBean("bean2", Bean2.class); context.registerBean(MyBeanFactoryPostProcessor.class); context.refresh();
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println); System.out.println(">>>>>>>>>>>>>>>>>>"); System.out.println(context.getBean(Bean1.class)); }
static class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { Bean1 bean1 = new Bean1(); bean1.setName("mofan"); beanFactory.registerSingleton("bean1", bean1); } }
@Getter @ToString static class Bean1 { @Setter private String name; private Bean2 bean2;
@Autowired private void setBean2(Bean2 bean2) { log.debug("依赖注入 bean2"); this.bean2 = bean2; }
@PostConstruct public void init() { log.debug("初始化..."); } }
static class Bean2 { } }
|
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean2
testBeanFactoryPostProcessor.MyBeanFactoryPostProcessor
>>>>>>>>>>>>>>>>>>
TestBeanFactoryPostProcessor.Bean1(name=mofan, bean2=null)
BeanDefinition
的名称数组中不包含 bean1
,也没有输出任何与经过 Spring 生命周期相关的日志信息,容器中 bean1
里注入的 bean2
也是 null
。这表明通过这种方式注册的 Bean 不会注册 BeanDefinition
,也不会经过 Spring 生命周期。
6. Aware 接口
6.1 Aware 接口
Aware 接口用于注入一些与容器相关的信息,比如:
- BeanNameAware 注入 Bean 的名字
- BeanFactoryAware 注入 BeanFactory 容器
- ApplicationContextAware 注入 ApplicationContext 容器
- EmbeddedValueResolverAware 解析
${}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Slf4j public class MyBean implements BeanNameAware, ApplicationContextAware { @Override public void setBeanName(String name) { log.info("当前 Bean: " + this + "名字叫: " + name); }
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { log.info("当前 Bean: " + this + "容器是: " + applicationContext); } }
|
1 2 3 4 5 6 7
| public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("myBean", MyBean.class);
context.refresh(); context.close(); }
|
当前 Bean: indi.mofan.bean.a06.MyBean@16f7c8c1名字叫: myBean
当前 Bean: indi.mofan.bean.a06.MyBean@16f7c8c1容器是: org.springframework.context.support.GenericApplicationContext@2669b199
6.2 InitializingBean
1 2 3 4 5 6 7 8 9
| @Slf4j public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {
@Override public void afterPropertiesSet() throws Exception { log.info("当前 Bean: " + this + " 初始化"); } }
|
再次运行 main()
方法有:
当前 Bean: indi.mofan.bean.a06.MyBean@16f7c8c1名字叫: myBean
当前 Bean: indi.mofan.bean.a06.MyBean@16f7c8c1容器是: org.springframework.context.support.GenericApplicationContext@2669b199
当前 Bean: indi.mofan.bean.a06.MyBean@df27fae 初始化
当同时实现 Aware
接口和 InitializingBean
接口时,会先执行 Aware
接口。
BeanFactoryAware
、ApplicationContextAware
和 EmbeddedValueResolverAware
三个接口的功能可以使用 @Autowired
注解实现,InitializingBean
接口的功能也可以使用 @PostConstruct
注解实现,为什么还要使用接口呢?
@Autowired
和 @PostConstruct
注解的解析需要使用 Bean 后置处理器,属于拓展功能,而这些接口属于内置功能,不加任何拓展 Spring 就能识别。在某些情况下,拓展功能会失效,而内容功能不会失效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Slf4j public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {
@Autowired public void setApplicationContextWithAutowired(ApplicationContext applicationContext) { log.info("当前 Bean: " + this + " 使用 @Autowired 注解,容器是: " + applicationContext); }
@PostConstruct public void init() { log.info("当前 Bean: " + this + " 使用 @PostConstruct 注解初始化"); } }
|
再运行 main()
方法会发现使用的注解没有被成功解析,原因很简单,GenericApplicationContext
是一个干净的容器,其内部没有用于解析这些注解的后置处理器。如果想要这些注解生效,则需要像前文一样添加必要的后置处理器:
1 2
| context.registerBean(AutowiredAnnotationBeanPostProcessor.class); context.registerBean(CommonAnnotationBeanPostProcessor.class);
|
6.3 失效的 @Autowired 注解
在某些情况下,尽管容器中存在必要的后置处理器,但 @Autowired
和 @PostConstruct
注解也会失效。
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Slf4j @Configuration public class MyConfig1 { @Autowired public void setApplicationContext(ApplicationContext applicationContext) { log.info("注入 ApplicationContext"); }
@PostConstruct public void init() { log.info("初始化"); } }
|
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("myConfig1", MyConfig1.class); context.registerBean(AutowiredAnnotationBeanPostProcessor.class); context.registerBean(CommonAnnotationBeanPostProcessor.class); context.registerBean(ConfigurationClassPostProcessor.class);
context.refresh(); context.close(); }
|
indi.mofan.bean.a06.MyConfig1 : 注入 ApplicationContext
indi.mofan.bean.a06.MyConfig1 : 初始化
@Autowired
和 @PostConstruct
注解成功被解析。
如果再对 Config1
进行一点小小的修改呢?
1 2 3 4 5 6 7 8 9 10
| @Slf4j @Configuration public class MyConfig1 {
@Bean public BeanFactoryPostProcessor processor1() { return processor -> log.info("执行 processor1"); } }
|
在 Config1
中添加了一个被 @Bean
注解标记的 processor1()
方法,用于向容器中添加 BeanFactoryPostProcessor
。
如果再运行 main()
方法:
indi.mofan.bean.a06.MyConfig1 : 执行 processor1
processor1()
方法成功生效,但 @Autowired
和 @PostConstruct
注解的解析失败了。
对于 context.refresh();
方法来说,它主要按照以下顺序干了三件事:
- 执行 BeanFactory 后置处理器;
- 添加 Bean 后置处理器;
- 创建和初始化单例对象。
比如当 Java 配置类不包括 BeanFactoryPostProcessor
时:
sequenceDiagram
participant ac as ApplicationContext
participant bfpp as BeanFactoryPostProcessor
participant bpp as BeanPostProcessor
participant config as Java配置类
ac ->> bfpp : 1. 执行 BeanFactoryPostProcessor
ac ->> bpp : 2. 注册 BeanPostProcessor
ac ->> +config : 3. 创建和初始化
bpp ->> config : 3.1 依赖注入扩展(如 @Value 和 @Autowired)
bpp ->> config : 3.2 初始化扩展(如 @PostConstruct)
ac ->> config : 3.3 执行 Aware 及 InitializingBean
config -->> -ac : 3.4 创建成功
BeanFactoryPostProcessor
会在 Java 配置类初始化之前执行。
当 Java 配置类中定义了BeanFactoryPostProcessor
时,如果要创建配置类中的 BeanFactoryPostProcessor
就必须 提前 创建和初始化 Java 配置类。
在创建和初始化 Java 配置类时,由于 BeanPostProcessor
还未准备好,无法解析配置类中的 @Autowired
等注解,导致 @Autowired
等注解失效:
sequenceDiagram
participant ac as ApplicationContext
participant bfpp as BeanFactoryPostProcessor
participant bpp as BeanPostProcessor
participant config as Java配置类
ac ->> +config : 3. 创建和初始化
ac ->> config : 3.1 执行 Aware 及 InitializingBean
config -->> -ac : 3.2 创建成功
ac ->> bfpp : 1. 执行 BeanFactoryPostProcessor
ac ->> bpp : 2. 注册 BeanPostProcessor
要解决这个问题也很简单,使用相关接口的功能实现注入和初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Slf4j @Configuration public class MyConfig2 implements InitializingBean, ApplicationContextAware { @Override public void afterPropertiesSet() throws Exception { log.info("初始化"); }
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { log.info("注入 ApplicationContext"); }
@Bean public BeanFactoryPostProcessor processor2() { return processor -> log.info("执行 processor2"); } }
|
修改下 main()
方法:
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("myConfig2", MyConfig2.class); context.registerBean(AutowiredAnnotationBeanPostProcessor.class); context.registerBean(CommonAnnotationBeanPostProcessor.class);
context.registerBean(ConfigurationClassPostProcessor.class);
context.refresh(); context.close(); }
|
indi.mofan.bean.a06.MyConfig2 : 注入 ApplicationContext
indi.mofan.bean.a06.MyConfig2 : 初始化
indi.mofan.bean.a06.MyConfig2 : 执行 processor2
总结
Aware
接口提供了一种 内置 的注入手段,可以注入 BeanFactory、ApplicationContext;
InitializingBean
接口提供了一种 内置 的初始化手段;
- 内置的注入和初始化不受拓展功能的影响,总会被执行,因此 Spring 框架内部的类总是使用这些接口。
7. 初始化与销毁
初始化和销毁 Bean 的实现有三种:
- 依赖于后置处理器提供的拓展功能
- 相关接口的功能
- 使用
@Bean
注解中的属性进行指定
当同时存在以上三种方式时,它们的执行顺序也将按照上述顺序进行执行。
包含三种初始化方式的 Bean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Slf4j public class Bean1 implements InitializingBean { @PostConstruct public void init() { log.info("初始化1"); }
@Override public void afterPropertiesSet() throws Exception { log.info("初始化2"); }
public void init3() { log.info("初始化3"); } }
|
包含三种销毁方式的 Bean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Slf4j public class Bean2 implements DisposableBean { @PreDestroy public void destroy1() { log.info("销毁1"); }
@Override public void destroy() throws Exception { log.info("销毁2"); }
public void destroy3() { log.info("销毁3"); } }
|
测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @SpringBootApplication public class A07Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A07Application.class, args); context.close(); }
@Bean(initMethod = "init3") public Bean1 bean1() { return new Bean1(); }
@Bean(destroyMethod = "destroy3") public Bean2 bean2() { return new Bean2(); } }
|
indi.mofan.bean.a07.Bean1 : 初始化1
indi.mofan.bean.a07.Bean1 : 初始化2
indi.mofan.bean.a07.Bean1 : 初始化3
indi.mofan.bean.a07.Bean2 : 销毁1
indi.mofan.bean.a07.Bean2 : 销毁2
indi.mofan.bean.a07.Bean2 : 销毁3
8. Scope
8.1 Scope 的类型与销毁
Scope 用于指定 Bean 的作用范围,有如下五个取值:
singleton
:单例(默认值)。容器启动时创建(未设置延迟),容器关闭时销毁
prototype
:多例。每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory#destroyBean()
进行销毁
request
:作用于 Web 应用的请求范围。每次请求用到此 Bean 时创建,请求结束时销毁
session
:作用于 Web 应用的会话范围。每个会话用到此 Bean 时创建,会话结束时销毁
application
:作用于 Web 应用的 ServletContext
。Web 容器用到此 Bean 时创建,容器关闭时销毁
前两个取值不再赘述,重点看下后三个取值。
1 2 3 4 5 6 7 8 9
| @Slf4j @Component @Scope(WebApplicationContext.SCOPE_REQUEST) public class BeanForRequest { @PreDestroy public void destroy() { log.info("destroy"); } }
|
1 2 3 4 5 6 7 8 9
| @Slf4j @Component @Scope(WebApplicationContext.SCOPE_SESSION) public class BeanForSession { @PreDestroy public void destroy() { log.info("destroy"); } }
|
1 2 3 4 5 6 7 8 9
| @Slf4j @Component @Scope(WebApplicationContext.SCOPE_APPLICATION) public class BeanForApplication { @PreDestroy public void destroy() { log.info("destroy"); } }
|
编写一个 Controller 进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @RestController public class MyController { @Lazy @Autowired private BeanForRequest beanForRequest;
@Lazy @Autowired private BeanForSession beanForSession;
@Lazy @Autowired private BeanForApplication beanForApplication;
@GetMapping(value = "/test", produces = "text/html") public String test(HttpServletRequest request, HttpSession session) { session.setMaxInactiveInterval(10); return "<ul>" + "<li>request scope: " + beanForRequest + "</li>" + "<li>session scope: " + beanForSession + "</li>" + "<li>application scope: " + beanForApplication + "</li>" + "</ul>"; } }
|
主启动类:
1 2 3 4 5 6 7
| @SpringBootApplication public class A08Application {
public static void main(String[] args) { SpringApplication.run(A08Application.class, args); } }
|
如果使用的 JDK 版本大于 8,需要要启动参数中添加如下信息避免报错:
1
| --add-opens java.base/java.lang=ALL-UNNAMED
|
但更建议在 pom.xml
中添加以下配置,一劳永逸:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <argLine> --add-opens java.base/java.lang=ALL-UNNAMED </argLine> </configuration> </plugin> </plugins> </build>
|
运行主启动类,在浏览器中访问 http://localhost:8080/test
,页面上显示:
request scope: indi.mofan.bean.a08.BeanForRequest@34d37122
session scope: indi.mofan.bean.a08.BeanForSession@75ee7b19
application scope: indi.mofan.bean.a08.BeanForApplication@68b50897
刷新页面,页面上的信息变化为:
request scope: indi.mofan.bean.a08.BeanForRequest@2db4ac39
session scope: indi.mofan.bean.a08.BeanForSession@75ee7b19
application scope: indi.mofan.bean.a08.BeanForApplication@68b50897
可以看到 request scope
发生了变化,session scope
和 application scope
没有变化。
这是因为刷新页面后就产生了一个新的请求,而 request
的作用范围只在一个请求内,因此每一个新请求就对应一个新的对象。
那要怎么改变 session scope
呢?
换一个浏览器访问 http://localhost:8080/test
,两个浏览器中的会话肯定不是同一个,此时 session scope
应该会发生变化:
request scope: indi.mofan.bean.a08.BeanForRequest@2286f290
session scope: indi.mofan.bean.a08.BeanForSession@4f025f73
application scope: indi.mofan.bean.a08.BeanForApplication@68b50897
application
的作用范围是 ServletContext
,要想 application scope
发生变化可以重启程序。
销毁
当刷新页面后,除了 request scope
的值发生变化外,在 IDEA 的控制台能看到以下信息:
indi.mofan.bean.a08.BeanForRequest : destroy
这表示 request
作用范围的 Bean 进行了销毁,执行了销毁方法。
如果想看到 session
作用范围的 Bean 执行销毁方法,可以等 session 过期时在控制台上看到对应的信息。默认情况下,session 的过期时间是 30 分钟,为了更好地测试,可以在配置文件中添加:
1 2
| server.servlet.session.timeout=10s
|
这个配置是全局的,如果只想针对某个请求进行配置,则可以:
1 2 3 4 5 6 7
| @GetMapping(value = "/test", produces = "text/html") public String test(HttpServletRequest request, HttpSession session) { session.setMaxInactiveInterval(10); }
|
设置 session 过期时间为 10 秒后,并不表示不进行任何操作 10 秒后就能在控制台上看到执行销毁方法的信息,经过测试,大概会等 1 分钟,静静等待 1 分钟左右,控制台上显示:
indi.mofan.bean.a08.BeanForSession : destroy
很遗憾没有办法看到 application
作用范围的 Bean 执行销毁方法,因为 Spring 似乎并没有对 application
作用范围的 Bean 进行正确的销毁处理,因此在 Servlet 容器销毁时看不到 application
作用范围的 Bean 执行销毁方法。
8.2 Scope 失效分析
现有两个类:
1 2 3 4
| @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class F1 { }
|
1 2 3 4 5 6
| @Getter @Component public class E { @Autowired private F1 f1; }
|
之后进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Slf4j @ComponentScan("indi.mofan.bean.a09") public class A09Application { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);
E e = context.getBean(E.class); log.info("{}", e.getF1()); log.info("{}", e.getF1()); log.info("{}", e.getF1());
context.close(); } }
|
现在问题来了:F1
被 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
标记,之后向 e
中注入了 f1
,那么 log.info("{}", e.getF1());
打印出的 f1
应该都不是同一个对象吗?
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F1@5fdcaa40
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F1@5fdcaa40
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F1@5fdcaa40
获取到的 f1
居然都是同一个,也就是说向单例对象中注入多例对象失败了。
对于单例对象来说,依赖注入仅发生了一次,后续不会再注入其他的 f1
,因此 e
始终使用的是第一次注入的 f1
:
graph LR
e1(e 创建)
e2(e set 注入 f1)
f1(f1 创建)
e1-->f1-->e2
为了解决这个问题,可以使用 @Lazy
生成代理对象,虽然代理对象依旧是同一个,但每次使用代理对象中的方法时,会由代理对象创建新的目标对象:
graph LR
e1(e 创建)
e2(e set 注入 f1 代理)
f1(f1 创建)
f2(f1 创建)
f3(f1 创建)
e1-->e2
e2--使用f方法-->f1
e2--使用f方法-->f2
e2--使用f方法-->f3
解决方式一
1 2 3 4 5 6 7
| @Getter @Component public class E { @Lazy @Autowired private F1 f1; }
|
再修改下 main()
方法,打印下 f1
的 Class 信息,查看是否是代理对象:
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);
E e = context.getBean(E.class); log.info("{}", e.getF1().getClass()); log.info("{}", e.getF1()); log.info("{}", e.getF1()); log.info("{}", e.getF1());
context.close(); }
|
indi.mofan.bean.a09.A09Application : class indi.mofan.bean.a09.F1$$EnhancerBySpringCGLIB$$ea96cbb5
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F1@37271612
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F1@4c309d4d
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F1@37883b97
使用 @Lazy
注解后,注入的是代理对象,每次获取到的 f1
不再是同一个。
解决方式二
除了使用 @Lazy
注解外,可以使用 @Scope
注解的 proxyMode
属性指定代理模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Component @Scope( value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS ) public class F2 { }
@Getter @Component public class E {
@Autowired private F2 f2; }
|
之后再测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Slf4j @ComponentScan("indi.mofan.bean.a09") public class A09Application { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);
E e = context.getBean(E.class);
log.info("{}", e.getF2().getClass()); log.info("{}", e.getF2()); log.info("{}", e.getF2()); log.info("{}", e.getF2());
context.close(); } }
|
indi.mofan.bean.a09.A09Application : class indi.mofan.bean.a09.F2$$EnhancerBySpringCGLIB$$f28665e2
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F2@2525ff7e
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F2@524d6d96
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F2@152aa092
解决方式三
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class F3 { }
@Component public class E {
@Autowired private ObjectFactory<F3> f3;
public F3 getF3() { return f3.getObject(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Slf4j @ComponentScan("indi.mofan.bean.a09") public class A09Application { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);
E e = context.getBean(E.class);
log.info("{}", e.getF3()); log.info("{}", e.getF3());
context.close(); } }
|
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F3@76f2bbc1
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F3@306cf3ea
解决方式四
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class F4 { }
@Component public class E {
@Autowired private ApplicationContext applicationContext;
public F4 getF4() { return applicationContext.getBean(F4.class); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Slf4j @ComponentScan("indi.mofan.bean.a09") public class A09Application { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);
E e = context.getBean(E.class);
log.info("{}", e.getF4()); log.info("{}", e.getF4());
context.close(); } }
|
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F4@2beee7ff
indi.mofan.bean.a09.A09Application : indi.mofan.bean.a09.F4@5136d012
如果对性能要求较高,则推荐使用后两种方式,前两种使用代理会有一定的性能损耗;如果不在乎那点性能损耗,则可以使用第一种方式,这种方式最简单。
四种解决方式虽然不同,但在理念上殊途同归,都是推迟了其他 Scope Bean 的获取,或者说按需加载。