# 容器与 bean
# 一。容器接口
以 SpringBoot 的启动类为例:
@Slf4j | |
@SpringBootApplication | |
public class A01Application { | |
public static void main(String[] args) { | |
SpringApplication.run(A01Application.class, args); | |
} | |
} |
其中的 run() 方法是有返回值的:
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args); |
在 IDEA 中使用快捷键 Ctrl + Alt + U 查看 ConfigurableApplicationContext 类的类图:
ConfigurableApplicationContext 接口继承了 ApplicationContext 接口,而 ApplicationContext 接口又间接地继承了 BeanFactory 接口,除此之外还继承了其他很多接口,相当于对 BeanFactory 进行了拓展。
# 1. 什么是 BeanFactory
- 它是
ApplicationContext的父接口 - 它才是 Spring 的核心容器,主要的
ApplicationContext实现 组合 了它的功能,也就是说,BeanFactory是ApplicationContext中的一个成员变量。
常用的 context.getBean("xxx") 方法,其实是调用了 BeanFactory 的 getBean() 方法。
# 2.BeanFactory 能做什么?
进入 BeanFactory 接口,在 IDEA 中使用快捷键 Ctrl + F12 查看这个接口中所有的方法定义:
通过这些方法定义可知, BeanFactory 表面上只有 getBean() 方法,但实际上 Spring 中的控制反转、基本的依赖注入、乃至 Bean 的生命周期中的各个功能都是由它的实现类提供。
查看 DefaultListableBeanFactory 类的类图:

DefaultListableBeanFactory 实现了 BeanFactory 接口,它能管理 Spring 中所有的 Bean,当然也包含 Spring 容器中的那些单例对象。
DefaultListableBeanFactory 还继承了 DefaultSingletonBeanRegistry 类,这个类就是用来管理 Spring 容器中的单例对象。
在 IDEA 提供的类图中选中 DefaultSingletonBeanRegistry ,然后按下 F4 进入这个类。它有一个 Map 类型的成员变量 singletonObjects :
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); |
Map 的 key 就是 Bean 的名字,而 value 是对应的 Bean,即单例对象。
现有如下两个 Bean:
@Component | |
public class Component1 { | |
} | |
@Component | |
public class Component2 { | |
} |
查看 singletonObjects 中是否存在这两个 Bean 的信息:
@Slf4j | |
@SpringBootApplication | |
public class A01Application { | |
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
# 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 外,其余三个文件内容如下:
thanks=Thank you |
thanks=谢谢 |
thanks=謝謝 |
测试 MessageSource 接口中 getMessage() 方法的使用:
public static void main(String[] args) { | |
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args); | |
// --snip-- | |
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:
public static void main(String[] args) { | |
GenericApplicationContext context = new GenericApplicationContext(); | |
context.registerBean("messageSource", MessageSource.class, () -> { | |
ResourceBundleMessageSource ms = new ResourceBundleMessageSource(); | |
// 设置编码格式 | |
ms.setDefaultEncoding("utf-8"); | |
// 设置国际化资源文件的 basename | |
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的使用
public static void main(String[] args) { | |
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args); | |
// --snip-- | |
Resource[] resources = context.getResources("classpath:application.properties"); | |
assert resources.length > 0; | |
// 使用 classpath* 可以加载 jar 里类路径下的 resource | |
resources = context.getResources("classpath*:META-INF/spring.factories"); | |
assert resources.length > 0; | |
context.close(); | |
} |
EnvironmentCapable的使用
public static void main(String[] args) { | |
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args); | |
// --snip-- | |
System.out.println(context.getEnvironment().getProperty("java_home")); | |
System.out.println(context.getEnvironment().getProperty("server.port")); | |
context.close(); | |
} |
java_home 是从环境变量中读取, properties.name 则是从 application.yml 配置文件中读取。
#{"author.name":"mofan"} | |
properties: | |
name: "mofan" | |
age: 20 | |
person: | |
gender: "man" |
运行 main() 方法后,控制台打印出:
D:\java
8080
ApplicationEventPublisher的使用
定义事件类 UserRegisteredEvent :
public class UserRegisteredEvent extends ApplicationEvent { | |
public UserRegisteredEvent(Object source) { | |
super(source); | |
} | |
} |
将 Component1 作为发送事件的 Bean:
@Component | |
public class Component1 { | |
private static final Logger log = LoggerFactory.getLogger(Component1.class); | |
@Autowired | |
private ApplicationEventPublisher context; | |
public void register() { | |
System.out.println("用户注册"); | |
log.debug("用户注册"); | |
context.publishEvent(new UserRegisteredEvent(this)); | |
} | |
} |
将 Component2 作为事件监听器:
@Component | |
public class Component2 { | |
private static final Logger log = LoggerFactory.getLogger(Component2.class); | |
@EventListener | |
public void aaa(UserRegisteredEvent event) { | |
System.out.println("{"+event+"}"); | |
log.debug("{}", event); | |
System.out.println("发送短信"); | |
log.debug("发送短信"); | |
} | |
} |
在 main() 方法中使用 Component1 发送事件:
public static void main(String[] args) { | |
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args); | |
context.getBean(Component1.class).register(); | |
context.close(); | |
} |
运行 main() 方法后,控制台打印出:
用户注册
[DEBUG] 21:46:36.119 [main] com.kangkang.a01.Component1 - 用户注册
{com.kangkang.a01.UserRegisteredEvent[source=com.kangkang.a01.Component1@20440c6c]}
[DEBUG] 21:46:36.120 [main] com.kangkang.a01.Component2 - com.kangkang.a01.UserRegisteredEvent[source=com.kangkang.a01.Component1@20440c6c]
发送短信
[DEBUG] 21:46:36.121 [main] com.kangkang.a01.Component2 - 发送短信
# 二、容器实现
# 1.BeanFactory 的实现
现有如下类,尝试将 Config 添加到 Bean 工厂中:
@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 注册到工厂即可。
public static void main(String[] args) { | |
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); | |
//bean 的定义(class,scope,初始化,销毁) | |
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class) | |
.setScope("singleton") | |
.getBeanDefinition(); | |
beanFactory.registerBeanDefinition("config", beanDefinition); | |
// 只有 config | |
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println); | |
} |
config
现在 Bean 工厂中 有且仅有一个 名为 config 的 Bean。
解析配置类
根据对 @Configuration 和 @Bean 两个注解的认识可知,Bean 工厂中应该还存在 bean1 和 bean2 ,那为什么现在没有呢?
很明显是现在的 BeanFactory 缺少了解析 @Configuration 和 @Bean 两个注解的能力。
public static void main(String[] args) { | |
// 给 BeanFactory 添加一些常用的后置处理器 | |
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 ,因此可以:
public static void main(String[] args) { | |
// 使用后置处理器 | |
// BeanFactoryPostProcessor 补充了一些 Bean 的定义 | |
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 中:
public static void main(String[] args) { | |
// --snip-- | |
System.out.println(beanFactory.getBean(Bean1.class).getBean2()); | |
} |
null
bean2 没有成功被注入到 bean1 中。
在先前添加到 BeanFactory 中的后置处理器里,有名为 internalAutowiredAnnotationProcessor 和 internalCommonAnnotationProcessor 的两个后置处理器。前者用于解析 @Autowired 注解,后者用于解析 @Resource 注解,它们都有一个共同的类型 BeanPostProcessor ,因此可以:
public static void main(String[] args) { | |
// --snip-- | |
System.out.println("---------------------------------------------"); | |
// Bean 后置处理器,对 Bean 的生命周期的各个阶段提供拓展,例如 @AutoWired @Resource... | |
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor); | |
System.out.println(beanFactory.getBean(Bean1.class).getBean2()); | |
} |
补充定义后,输出bean1中的bean2-->
[DEBUG] 15:08:32.146 [main] c.k.a02.TestBeanFactory$Bean1 - 构造 Bean1()
Logger[com.kangkang.a02.TestBeanFactory$Bean1]
[DEBUG] 15:08:32.161 [main] c.k.a02.TestBeanFactory$Bean2 - 构造 Bean2()
com.kangkang.a02.TestBeanFactory$Bean2@352c1b98
建立 BeanPostProcessor 和 BeanFactory 的关系后, bean2 被成功注入到 bean1 中了。
除此之外还可以发现:当需要使用 Bean 时,Bean 才会被创建,即按需加载。那有没有什么办法预先就初始化好单例对象呢?
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()); | |
} |
补充定义后,输出bean1中的bean2-->
[DEBUG] 15:08:32.146 [main] c.k.a02.TestBeanFactory$Bean1 - 构造 Bean1()
Logger[com.kangkang.a02.TestBeanFactory$Bean1]
[DEBUG] 15:08:32.161 [main] c.k.a02.TestBeanFactory$Bean2 - 构造 Bean2()
com.kangkang.a02.TestBeanFactory$Bean2@352c1b98
目前可以知道, BeanFactory 不会 :
- 主动调用
BeanFactory后置处理器; - 主动添加
Bean后置处理器; - 主动初始化单例对象;
- 解析
${}和#{}
后置处理器的排序
在最初给出的类信息中进行补充:
@Configuration | |
static class Config { | |
// --snip-- | |
@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 { | |
// --snip-- | |
@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") 呢?
public static void main(String[] args) { | |
System.out.println(beanFactory.getBean(Bean1.class).getInter()); | |
} |
com.kangkang.a02.TestBeanFactory$Bean3@21129f1f
根据打印的结果可知, @Autowired 先生效了,这是因为 internalAutowiredAnnotationProcessor 排在 internalCommonAnnotationProcessor 之前。可以查看它们的先后关系:
public static void main(String[] args) { | |
// --snip-- | |
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 :
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
com.kangkang.a02.TestBeanFactory$Bean3@21129f1f
改变 BeanPostProcessor 的先后顺序后, @Resource(name = "bean4") 生效了,成功注入了 bean4 。
为什么使用 beanFactory.getDependencyComparator() 后就改变了 BeanPostProcessor 的先后顺序呢?
在调用的 AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory); 方法源码中有:
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 是:
private int order = Ordered.LOWEST_PRECEDENCE - 2; |
CommonAnnotationBeanPostProcessor 设置的 order 是:
public CommonAnnotationBeanPostProcessor() { | |
setOrder(Ordered.LOWEST_PRECEDENCE - 3); | |
} |
值越小,优先级越大,就排在更前面,因此当设置了 AnnotationAwareOrderComparator 比较器后, CommonAnnotationBeanPostProcessor 排在更前面, @Resource 就先生效。
# 2.ApplicationContext 的实现
@Slf4j | |
public class A02Application { | |
static class Bean1 { | |
} | |
static class Bean2 { | |
private Bean1 bean1; | |
public void setBean1(Bean1 bean1) { | |
this.bean1 = bean1; | |
} | |
public Bean1 getBean1() { | |
return bean1; | |
} | |
} | |
} |
ClassPathXmlApplicationContext
较为经典的容器,基于 classpath 下的 xml 格式的配置文件来创建。
<?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"> | |
<!-- 控制反转,让 bean1 被 Spring 容器管理 --> | |
<bean id="bean1" class="com.kangkang.a02.TestApplicationContext.Bean1"/> | |
<!-- 控制反转,让 bean2 被 Spring 容器管理 --> | |
<bean id="bean2" class="com.kangkang.a02.TestApplicationContext.Bean2"> | |
<!-- 依赖注入,建立与 bean1 的依赖关系 --> | |
<property name="bean1" ref="bean1"/> | |
</bean></beans> |
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
com.kangkang.a02.TestApplicationContext$Bean1@127a7a2e
FileSystemXmlApplicationContext
与 ClassPathXmlApplicationContext 相比, FileSystemXmlApplicationContext 是基于磁盘路径下 xml 格式的配置文件来创建。
private static void testFileSystemXmlApplicationContext() { | |
// 使用相对路径时,以模块为起点(IDEA 中需要设置 Working directory),也支持绝对路径 | |
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
com.kangkang.a02.TestApplicationContext$Bean1@5495333e
从 XML 文件中读取 Bean 的定义
ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 都依赖于从 XML 文件中读取 Bean 的信息,而这都利用了 XmlBeanDefinitionReader 进行读取。
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 ClassPathResource("b01.xml")); | |
reader.loadBeanDefinitions(new FileSystemResource("bean\\src\\main\\resources\\b01.xml")); | |
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println); | |
} |
读取之前...
读取之后...
bean1
bean2
AnnotationConfigApplicationContext
基于 Java 配置类来创建。首先定义配置类:
@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; | |
} | |
} |
// ⬇️较为经典的容器,基于 java 配置类来创建 | |
private static void testAnnotationConfigApplicationContext() { | |
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); | |
for (String beanDefinitionName : context.getBeanDefinitionNames()) { | |
System.out.println(beanDefinitionName); | |
} | |
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
testApplicationContext.Config
bean1
bean2
com.kangkang.a02.TestApplicationContext$Bean1@3899782c
与前面两种基于 XML 创建 ApplicationContext 的方式相比,使用 AnnotationConfigApplicationContext 后,使得容器中多了一些后置处理器相关的 Bean。
如果要在先前的两种方式中也添加上这些 Bean,可以在 XML 进行配置:
<?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
@Configuration | |
static class WebConfig { | |
@Bean | |
public ServletWebServerFactory servletWebServerFactory() { | |
return new TomcatServletWebServerFactory(); | |
} | |
@Bean | |
public DispatcherServlet dispatcherServlet() { | |
return new DispatcherServlet(); | |
} | |
@Bean | |
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) { | |
return new DispatcherServletRegistrationBean(dispatcherServlet, "/"); | |
} | |
@Bean("/hello") | |
public Controller controller() { | |
return (request, response) -> { | |
response.getWriter().print("hello"); | |
return null; }; | |
} | |
} |
// ⬇️较为经典的容器,基于 java 配置类来创建,用于 web 环境 | |
private static void testAnnotationConfigServletWebServerApplicationContext() { | |
AnnotationConfigServletWebServerApplicationContext context = | |
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); | |
for (String beanDefinitionName : context.getBeanDefinitionNames()) { | |
System.out.println(beanDefinitionName); | |
} | |
} |
运行代码,在浏览器中访问 http://localhost:8080/hello 路径则会显示出 hello 字样:

# 三、Bean 的生命周期
自定义一个 SpringBoot 的主启动类:
@SpringBootApplication | |
public class A03 { | |
public static void main(String[] args) { | |
ConfigurableApplicationContext context = SpringApplication.run(A03.class,args); | |
context.close(); | |
} | |
} |
在启动类所在路径下再定义一个类,使其能够被自动装配:
@Component | |
public class LifeCycleBean { | |
private static final Logger log = LoggerFactory.getLogger(LifeCycleBean.class); | |
public LifeCycleBean() { | |
log.info("构造"); | |
System.out.println(log); | |
} | |
@Autowired | |
public void autowire(@Value("${JAVA_HOME}") String home) { | |
log.debug("依赖注入:{}", home); | |
} | |
@PostConstruct | |
public void init() { | |
log.debug("初始化"); | |
} | |
@PreDestroy | |
public void destroy() { | |
log.debug("销毁"); | |
} | |
} |
运行主启动类,查看控制台的日志信息(只列举主要信息):
[INFO ] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean - 构造
[DEBUG] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean - 依赖注入:D:\java
[DEBUG] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean - 初始化
[DEBUG] 15:31:41.679 [main] com.kangkang.a03.LifeCycleBean - 销毁
除此之外,Spring 还提供了一些对 Bean 生命周期的各个阶段进行拓展的 BeanPostProcessor ,比如 InstantiationAwareBeanPostProcessor 和 DestructionAwareBeanPostProcessor 。
实现这两个接口,并使用 @Component 注解标记实现类:
@Component | |
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor { | |
private static final Logger log = LoggerFactory.getLogger(MyBeanPostProcessor.class); | |
@Override | |
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { | |
if (beanName.equals("lifeCycleBean")) | |
log.debug("<<<<<< 销毁之前执行, 如 @PreDestroy"); | |
} | |
@Override | |
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { | |
if (beanName.equals("lifeCycleBean")) | |
log.debug("<<<<<< 实例化之前执行, 这里返回的对象会替换掉原本的 bean"); | |
return null; } | |
@Override | |
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { | |
if (beanName.equals("lifeCycleBean")) | |
log.debug("<<<<<< 实例化之后执行, 这里如果返回 false 会跳过依赖注入阶段"); | |
return true; } | |
@Override | |
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException { | |
if (beanName.equals("lifeCycleBean")) | |
log.debug("<<<<<< 依赖注入阶段执行, 如 @Autowired、@Value、@Resource"); | |
return pvs;// 先不理会 | |
} | |
@Override | |
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | |
if (beanName.equals("lifeCycleBean")) | |
log.debug("<<<<<< 初始化之前执行, 这里返回的对象会替换掉原本的 bean, 如 @PostConstruct、@ConfigurationProperties"); | |
return bean; | |
} | |
@Override | |
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { | |
if (beanName.equals("lifeCycleBean")) | |
log.debug("<<<<<< 初始化之后执行, 这里返回的对象会替换掉原本的 bean, 如代理增强"); | |
return bean; | |
} | |
} |
再运行主启动类,查看控制台的日志信息(只列举主要信息):
[DEBUG] 15:31:41.140 [main] c.kangkang.a03.MyBeanPostProcessor - <<<<<< 实例化之前执行, 这里返回的对象会替换掉原本的 bean
[INFO ] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean - 构造
Logger[com.kangkang.a03.LifeCycleBean]
[DEBUG] 15:31:41.140 [main] c.kangkang.a03.MyBeanPostProcessor - <<<<<< 实例化之后执行, 这里如果返回 false 会跳过依赖注入阶段
[DEBUG] 15:31:41.140 [main] c.kangkang.a03.MyBeanPostProcessor - <<<<<< 依赖注入阶段执行, 如 @Autowired、@Value、@Resource
[DEBUG] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean - 依赖注入:D:\java
[DEBUG] 15:31:41.140 [main] c.kangkang.a03.MyBeanPostProcessor - <<<<<< 初始化之前执行, 这里返回的对象会替换掉原本的 bean, 如 @PostConstruct、@ConfigurationProperties
[DEBUG] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean - 初始化
[DEBUG] 15:31:41.140 [main] c.kangkang.a03.MyBeanPostProcessor - <<<<<< 初始化之后执行, 这里返回的对象会替换掉原本的 bean, 如代理增强
[INFO ] 15:31:41.262 [main] o.s.b.a.w.s.WelcomePageHandlerMapping - Adding welcome page: class path resource [static/index.html]
[INFO ] 15:31:41.457 [main] o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]
[INFO ] 15:31:41.462 [main] o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path ''
[INFO ] 15:31:41.479 [main] com.kangkang.a03.A03 - Started A03 in 1.299 seconds (JVM running for 2.028)
[DEBUG] 15:31:41.679 [main] c.kangkang.a03.MyBeanPostProcessor - <<<<<< 销毁之前执行, 如 @PreDestroy
[DEBUG] 15:31:41.679 [main] com.kangkang.a03.LifeCycleBean - 销毁
为什么实现了 BeanPostProcessor 接口后就能够在 Bean 生命周期的各个阶段进行拓展呢?
这使用了模板方法设计模式。
现有如下代码,模拟 BeanFactory 构造 Bean:
static class MyBeanFactory { | |
private final List<BeanPostProcessor> processors = new ArrayList<>(); | |
public Object getBean() { | |
Object bean = new Object(); | |
System.out.println("构造 " + bean); | |
System.out.println("依赖注入 " + bean); | |
System.out.println("初始化 " + bean); | |
return bean; | |
} | |
} |
假设现在需要在依赖注入之后,初始化之前进行其他的操作,那首先能想到的就是在这个位置直接书写相关操作的代码,但这会使代码更加臃肿、增加耦合性,显然不是一种好方式。
可以定义一个接口:
interface BeanPostProcessor { | |
void inject(Object bean); | |
} |
然后对 MyBeanFactory 进行修改:
static class MyBeanFactory { | |
private final List<BeanPostProcessor> processors = new ArrayList<>(); | |
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; | |
} | |
public void addBeanPostProcessor(BeanPostProcessor processor) { | |
processors.add(processor); | |
} | |
} |
之后如果需要拓展,调用 MyBeanFactory 实例的 addProcessor() 方法添加拓展逻辑即可:
public static void main(String[] args) { | |
MyBeanFactory beanFactory = new MyBeanFactory(); | |
beanFactory.addBeanPostProcessor(bean -> System.out.println("解析@Autowired")); | |
beanFactory.addBeanPostProcessor(bean -> System.out.println("解析@Resource")); | |
Object bean = beanFactory.getBean(); | |
} |
构造 java.lang.Object@7823a2f9
依赖注入 java.lang.Object@7823a2f9
解析@Autowired
解析@Resource
初始化 java.lang.Object@7823a2f9
Bean 生命周期图:
graph LR | |
创建 --> 依赖注入 | |
依赖注入 --> 初始化 | |
初始化 --> 可用 | |
可用 --> 销毁 |
# 四、Bean 后置处理器
# 1. 常见的 Bean 后置处理器
现有如下三个类:
public class Bean1 { | |
private static final Logger log = LoggerFactory.getLogger(Bean1.class); | |
private Bean2 bean2; | |
@Autowired | |
public void setBean2(Bean2 bean2) { | |
log.debug("@Autowired 生效: {}", bean2); | |
this.bean2 = bean2; | |
} | |
@Autowired | |
private Bean3 bean3; | |
@Resource | |
public void setBean3(Bean3 bean3) { | |
log.debug("@Resource 生效: {}", bean3); | |
this.bean3 = bean3; | |
} | |
private String home; | |
@Autowired | |
public void setHome(@Value("${JAVA_HOME}") String home) { | |
log.debug("@Value 生效: {}", home); | |
this.home = home; | |
} | |
@PostConstruct | |
public void init() { | |
log.debug("@PostConstruct 生效"); | |
} | |
@PreDestroy | |
public void destroy() { | |
log.debug("@PreDestroy 生效"); | |
} | |
@Override | |
public String toString() { | |
return "Bean1{" + | |
"bean2=" + bean2 + | |
", bean3=" + bean3 + | |
", home='" + home + '\'' + | |
'}'; | |
} | |
} |
public class Bean2 { | |
} | |
public class Bean3 { | |
} |
Bean2 和 Bean3 很简单,而在 Bean1 中使用了多个注解以实现 Bean 注入和值注入。
public class A04Application { | |
public static void main(String[] args) { | |
// GenericApplicationContext 是一个干净的容器 | |
GenericApplicationContext context = new GenericApplicationContext(); | |
// 用原始方式注册三个 bean | |
context.registerBean("bean1", Bean1.class); | |
context.registerBean("bean2", Bean2.class); | |
context.registerBean("bean3", Bean3.class); | |
// 初始化容器。执行 beanFactory 后置处理器,添加 bean 后置处理器,初始化所有单例 | |
context.refresh(); | |
// 销毁容器 | |
context.close(); | |
} | |
} |
运行上述方法后,控制台中只打印了与 Spring 相关的日志信息,也就是说 Bean1 中使用的注解并没有生效。
向 GenericApplicationContext 添加一些与 Bean 后置处理器相关的 Bean,使得 Bean1 中使用的注解能够生效。
public static void main(String[] args) { | |
context.registerBean("bean3", Bean3.class); | |
// 解析值注入内容 | |
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); | |
// @Autowired @Value | |
context.registerBean(AutowiredAnnotationBeanPostProcessor.class); | |
context.refresh(); | |
} |
[DEBUG] 15:39:55.390 [main] com.kangkang.a04.Bean1 - @Resource 生效: com.kangkang.a04.Bean3@78fa769e
[DEBUG] 15:39:55.413 [main] com.kangkang.a04.Bean1 - @Value 生效: D:\java
public static void main(String[] args) { | |
// @Resource @PostConstruct @PreDestroy | |
context.registerBean(CommonAnnotationBeanPostProcessor.class); | |
} |
[DEBUG] 15:39:55.390 [main] com.kangkang.a04.Bean1 - @Resource 生效: com.kangkang.a04.Bean3@78fa769e
[DEBUG] 15:39:55.413 [main] com.kangkang.a04.Bean1 - @Value 生效: D:\java
[DEBUG] 15:39:55.421 [main] com.kangkang.a04.Bean1 - @Autowired 生效: com.kangkang.a04.Bean2@3fce8fd9
[DEBUG] 15:39:55.422 [main] com.kangkang.a04.Bean1 - @PostConstruct 生效
Bean1{bean2=com.kangkang.a04.Bean2@3fce8fd9, bean3=com.kangkang.a04.Bean3@78fa769e, home='D:\java'}
[DEBUG] 15:39:55.424 [main] com.kangkang.a04.Bean1 - @PreDestroy 生效
解析 @ConfigurationProperties
使用 @ConfigurationProperties 可以指定配置信息的前缀,使得配置信息的读取更加简单。比如:
@ConfigurationProperties(prefix = "java") | |
public class Bean4 { | |
private String home; | |
private String version; | |
public String getHome() { | |
return home; | |
} | |
public void setHome(String home) { | |
this.home = home; | |
} | |
public String getVersion() { | |
return version; | |
} | |
public void setVersion(String version) { | |
this.version = version; | |
} | |
@Override | |
public String toString() { | |
return "Bean4{" + | |
"home='" + home + '\'' + | |
", version='" + version + '\'' + | |
'}'; | |
} | |
} |
上述代码用于获取环境变量中 java.home 和 java.version 的信息。
对先前的 main() 方法进行补充:
public static void main(String[] args) { | |
// --snip-- | |
context.registerBean("bean4", Bean4.class); | |
// --snip-- | |
System.out.println(context.getBean(Bean4.class)); | |
// --snip-- | |
} |
Bean4(home=null, version=null)
Bean4 成功添加到容器中,但值注入失败了,显然也是因为缺少解析 @ConfigurationProperties 注解的后置处理器。
public static void main(String[] args) { | |
// --snip-- | |
context.registerBean("bean4", Bean4.class); | |
// --snip-- | |
// @ConfigurationProperties | |
ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory()); | |
System.out.println(context.getBean(Bean4.class)); | |
// --snip-- | |
} |
Bean4{home='D:\Java', version='11.0.12'}
# 2.AutowiredAnnotationBeanPostProcessor
通过前文可知 AutowiredAnnotationBeanPostProcessor 用于解析 @Autowired 和 @Value 注解,那它究竟是怎么工作的呢?
public static void main(String[] args) { | |
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); | |
// 注册成品 Bean,不再进行 Bean 的创建、依赖注入、初始化等操作 | |
beanFactory.registerSingleton("bean2", new Bean2()); | |
beanFactory.registerSingleton("bean3", new Bean3()); | |
// @Value | |
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); | |
// 查看哪些属性、方法加了 @Autowired,这称之为 InjectionMetadata | |
AutowiredAnnotationBeanPostProcessor postProcessor = new AutowiredAnnotationBeanPostProcessor(); | |
postProcessor.setBeanFactory(beanFactory); | |
Bean1 bean1 = new Bean1(); | |
System.out.println(bean1); | |
// 执行依赖注入,@Autowired、@Value | |
postProcessor.postProcessProperties(null, bean1, "bean1"); | |
System.out.println(bean1); | |
} |
Bean1{bean2=null, bean3=null, home='null'}
[DEBUG] 15:45:26.473 [main] com.kangkang.a04.Bean1 - @Value 生效: D:\java
[DEBUG] 15:45:26.489 [main] com.kangkang.a04.Bean1 - @Autowired 生效: com.kangkang.a04.Bean2@1972e513
Bean1{bean2=com.kangkang.a04.Bean2@1972e513, bean3=null, home=${JAVA_HOME}}
在未调用 AutowiredAnnotationBeanPostProcessor#postProcessProperties() 方法时, Bean1 中的 bean2 、 bean3 和 home 都没有注入成功,而在调用之后,成功注入了 bean2 和 home ,但 home 的值似乎有点奇怪,并没有打印出前文中相同的值,可能是因为没有成功解析 #{} ?
至于 bean3 为什么没注入成功,是因为 bean3 的注入是利用 @Resource ,而不是 @Autowired 。如果对 Bean1 进行修改:
public class Bean1 { | |
// --snip-- | |
@Autowired | |
private Bean3 bean3; | |
@Resource | |
public void setBean3(Bean3 bean3) { | |
log.info("@Resource 生效: {}", bean3); | |
this.bean3 = bean3; | |
} | |
// --snip-- | |
} |
再次运行有:
Bean1{bean2=null, bean3=null, home='null'}
[DEBUG] 15:45:26.473 [main] com.kangkang.a04.Bean1 - @Value 生效: D:\java
[DEBUG] 15:45:26.489 [main] com.kangkang.a04.Bean1 - @Autowired 生效: com.kangkang.a04.Bean2@1972e513
Bean1{bean2=com.kangkang.a04.Bean2@1972e513, bean3=com.kangkang.a04.Bean3@7ae0a9ec, home=${JAVA_HOME}}
成功注入了 bean3 。如果想要成功注入 home ,则需要在 BeanFactory 中添加 #{} 的解析器:
public static void main(String[] args) { | |
// --snip-- | |
// ${} 的解析器 | |
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders); | |
// --snip-- | |
postProcessor.postProcessProperties(null, bean1, "bean1"); | |
System.out.println(bean1); | |
} |
Bean1{bean2=null, bean3=null, home='null'}
[DEBUG] 15:45:26.473 [main] com.kangkang.a04.Bean1 - @Value 生效: D:\java
[DEBUG] 15:45:26.489 [main] com.kangkang.a04.Bean1 - @Autowired 生效: com.kangkang.a04.Bean2@1972e513
Bean1{bean2=com.kangkang.a04.Bean2@1972e513, bean3=com.kangkang.a04.Bean3@7ae0a9ec, home='D:\java'}
AutowiredAnnotationBeanPostProcessor#postProcessProperties()
源码如下:
@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 对象中的信息:
public static void main(String[] args) { | |
// --snip-- | |
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); | |
// 获取 Bean1 上加了 @Value、@Autowired 注解的成员变量、方法参数信息 | |
InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null); | |
// 此处断点 | |
System.out.println(metadata); | |
} |

InjectionMetadata 中有一个名为 injectedElements 的集合类型成员变量,根据上图所示, injectedElements 存储了被相关注解标记的成员变量、方法的信息,因为 Bean1 中的 bean3 成员变量、 setBean2() 和 setHome() 方法恰好被 @Autowired 注解标记。
然后按照源码一样,调用 InjectionMetadata#inject() 方法进行依赖注入:
public static void main(String[] args) { | |
// --snip-- | |
// 获取 Bean1 上加了 @Value、@Autowired 注解的成员变量、方法参数信息 | |
InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null); | |
// 调用 InjectionMetadata 来进行依赖注入,注入时按类型查找值 | |
metadata.inject(bean1, "bean1", null); | |
System.out.println(bean1); | |
} |
[DEBUG] 13:45:33.838 [main] com.kangkang.a04.Bean1 - @Value 生效: D:\java
[DEBUG] 13:45:33.857 [main] com.kangkang.a04.Bean1 - @Autowired 生效: com.kangkang.a04.Bean2@1972e513
Bean1{bean2=com.kangkang.a04.Bean2@1972e513, bean3=com.kangkang.a04.Bean3@7ae0a9ec, home='D:\java'}
调用 inject() 方法后会利用反射进行依赖注入,但在反射之前,肯定得先拿到被注入的对象或值,那这些对象或值是怎么取到的呢?
可以通过以下代码概括:
public static void main(String[] args) { | |
// --snip-- | |
// 如何按类型查找值 | |
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); | |
// MethodParameter 构造方法的第二个参数表示需要解析的方法中参数的索引 | |
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); | |
} |
com.kangkang.a04.Bean3@7ae0a9ec
com.kangkang.a04.Bean2@1972e513
D:\java
# 五、BeanFactory 后置处理器
# 1. 常见的 BeanFactory 后置处理器
先引入要用到的依赖:
<dependency> | |
<groupId>org.mybatis.spring.boot</groupId> | |
<artifactId>mybatis-spring-boot-starter</artifactId> | |
<version>2.1.4</version> | |
</dependency> | |
<dependency> | |
<groupId>com.alibaba</groupId> | |
<artifactId>druid</artifactId> | |
<version>1.2.15</version> | |
</dependency> | |
<dependency> | |
<groupId>mysql</groupId> | |
<artifactId>mysql-connector-java</artifactId> | |
</dependency> |
需要用到的类信息:
@Configuration | |
@ComponentScan("com.kangkang.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.setDriverClassName("com.mysql.jdbc.Driver"); | |
dataSource.setUrl("jdbc:mysql://localhost:3306/spring_db"); | |
dataSource.setUsername("root"); | |
dataSource.setPassword("1234"); | |
return dataSource; | |
} | |
} |
public class Bean1 { | |
private static final Logger log = LoggerFactory.getLogger(Bean1.class); | |
public Bean1() { | |
log.debug("我被 Spring 管理啦"); | |
} | |
} |
@Component | |
public class Bean2 { | |
private static final Logger log = LoggerFactory.getLogger(Bean2.class); | |
public Bean2() { | |
log.debug("我被 Spring 管理啦"); | |
} | |
} |
继续使用 GenericApplicationContext 作为容器,向容器中注册 config :
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 注解都没有生效。
根据经验,显然是因为缺少某个后置处理器。
public static void main(String[] args) { | |
GenericApplicationContext context = new GenericApplicationContext(); | |
context.registerBean("config", Config.class); | |
// @ComponentScan @Bean @Import @ImportResource | |
context.registerBean(ConfigurationClassPostProcessor.class); | |
// --snip-- | |
} |
config
com.kangkang.a05.AtBeanPostProcessor
com.kangkang.a05.MapperPostProcessor
bean1
sqlSessionFactoryBean
dataSource
在使用 MyBatis 时,经常会使用到 @Mapper 注解,而这个注解的解析也需要使用到特定的 BeanFactory 后置处理器。
以下两个接口被 @Mapper 注解标记:
package com.kangkang.a05.mapper; | |
import org.apache.ibatis.annotations.Mapper; | |
@Mapper | |
public interface Mapper1 { | |
} | |
package com.kangkang.a05.mapper; | |
import org.apache.ibatis.annotations.Mapper; | |
@Mapper | |
public interface Mapper2 { | |
} |
然后添加解析 @Mapper 注解的后置处理器:
public static void main(String[] args) { | |
GenericApplicationContext context = new GenericApplicationContext(); | |
context.registerBean("config", Config.class); | |
// @ComponentScan @Bean @Import @ImportResource | |
context.registerBean(ConfigurationClassPostProcessor.class); | |
context.registerBean(MapperScannerConfigurer.class, | |
i -> i.getPropertyValues().add("basePackage", "indi.mofan.bean.a05.mapper")); | |
// --snip-- | |
} |
其中的 basePackage 是 MapperScannerConfigurer 中的一个成员变量,表示需要扫描的包路径,设置的值恰好是被 @Mapper 注解标记的接口所在的包路径。
控制台打印的信息中增加了:
mapper1
mapper2
除此之外,还有一些常用的后置处理器并没有在上述信息中体现。
# 2. 模拟实现
移除向容器中添加的 ConfigurationClassPostProcessor 和 MapperScannerConfigurer 两个后置处理器,自行编码模拟它们功能的实现。
组件扫描之 @ComponentScan
在 Bean2 所在包路径下再增加两个类,用于后续测试:
package com.kangkang.a05.component; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.stereotype.Controller; | |
@Controller | |
public class Bean3 { | |
private static final Logger log = LoggerFactory.getLogger(Bean3.class); | |
public Bean3() { | |
log.debug("我被 Spring 管理啦"); | |
} | |
} | |
package com.kangkang.a05.component; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
public class Bean4 { | |
private static final Logger log = LoggerFactory.getLogger(Bean4.class); | |
public Bean4() { | |
log.debug("我被 Spring 管理啦"); | |
} | |
} |
编写 ComponentScanPostProcessor 用于实现 @ComponentScan 注解的解析:
package com.kangkang.a05; | |
import org.springframework.beans.BeansException; | |
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | |
import org.springframework.beans.factory.support.AbstractBeanDefinition; | |
import org.springframework.beans.factory.support.BeanDefinitionBuilder; | |
import org.springframework.beans.factory.support.BeanDefinitionRegistry; | |
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; | |
import org.springframework.context.annotation.AnnotationBeanNameGenerator; | |
import org.springframework.context.annotation.ComponentScan; | |
import org.springframework.core.annotation.AnnotationUtils; | |
import org.springframework.core.io.Resource; | |
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; | |
import org.springframework.core.type.AnnotationMetadata; | |
import org.springframework.core.type.classreading.CachingMetadataReaderFactory; | |
import org.springframework.core.type.classreading.MetadataReader; | |
import org.springframework.stereotype.Component; | |
import java.io.IOException; | |
public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor { | |
@Override | |
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { | |
try { | |
ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class); | |
if (componentScan != null) { | |
for (String p : componentScan.basePackages()) { | |
System.out.println(p); | |
String path = "classpath*:" + p.replace(".", "/") + "/**/*.class"; | |
System.out.println(path); | |
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); | |
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path); | |
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); | |
for (Resource resource : resources) { | |
System.out.println(resource); | |
MetadataReader reader = factory.getMetadataReader(resource); | |
AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata(); | |
if (annotationMetadata.hasAnnotation(Component.class.getName()) || | |
annotationMetadata.hasMetaAnnotation(Component.class.getName())) { | |
AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(reader.getClassMetadata().getClassName()).getBeanDefinition(); | |
String name = generator.generateBeanName(bd, beanDefinitionRegistry); | |
beanDefinitionRegistry.registerBeanDefinition(name, bd); | |
} | |
} | |
} | |
} | |
}catch (IOException e){ | |
e.printStackTrace(); | |
} | |
} | |
@Override | |
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { | |
} | |
} |
然后再测试:
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(); | |
} |
[DEBUG] 14:02:56.305 [main] com.kangkang.a05.component.Bean2 - 我被 Spring 管理啦
[DEBUG] 14:02:56.305 [main] com.kangkang.a05.component.Bean3 - 我被 Spring 管理啦
config
componentScanPostProcessor
bean2
bean3
没使用 ConfigurationClassPostProcessor 也实现了 @ComponentScan 注解的解析!
@Bean 的解析
Config 类中再增加一个方法作为干扰项:
@Configuration | |
@ComponentScan("com.kangkang.a05.component") | |
public class Config { | |
public Bean2 bean2() { | |
return new Bean2(); | |
} | |
// --snip-- | |
} |
与解析 @ComponentScan 一样,自行编写一个 BeanFactoryPostProcessor 的实现类用于解析 @Bean 注解:
package com.kangkang.a05; | |
import org.springframework.beans.BeansException; | |
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | |
import org.springframework.beans.factory.support.AbstractBeanDefinition; | |
import org.springframework.beans.factory.support.BeanDefinitionBuilder; | |
import org.springframework.beans.factory.support.BeanDefinitionRegistry; | |
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.core.io.ClassPathResource; | |
import org.springframework.core.type.MethodMetadata; | |
import org.springframework.core.type.classreading.CachingMetadataReaderFactory; | |
import org.springframework.core.type.classreading.MetadataReader; | |
import java.io.IOException; | |
import java.util.Set; | |
public class AtBeanPostProcessor implements BeanDefinitionRegistryPostProcessor { | |
@Override | |
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { | |
try { | |
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); | |
MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/kangkang/a05/Config.class")); | |
Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName()); | |
for (MethodMetadata method : methods) { | |
System.out.println(method); | |
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); | |
String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString(); | |
builder.setFactoryMethodOnBean(method.getMethodName(), "config"); | |
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); | |
if (initMethod.length() > 0) { | |
builder.setInitMethodName(initMethod); | |
} | |
AbstractBeanDefinition bd = builder.getBeanDefinition(); | |
beanDefinitionRegistry.registerBeanDefinition(method.getMethodName(), bd); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
@Override | |
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { | |
} | |
} |
在构造 BeanDefinition 时调用了 setAutowireMode() 方法设置注入模式,这是因为在 Config 类中有一特殊的被 @Bean 标记的方法:
@Bean | |
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { | |
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); | |
sqlSessionFactoryBean.setDataSource(dataSource); | |
return sqlSessionFactoryBean; | |
} |
接收一个 DataSource 类型的参数,需要将容器中这个类型的 Bean 进行注入,设置的 AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR 注入模式则能完成这个功能。
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(); | |
} |
com.kangkang.a05.Config.bean1()
com.kangkang.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
com.kangkang.a05.Config.dataSource()
[DEBUG] 14:06:12.511 [main] com.kangkang.a05.Bean1 - 我被 Spring 管理啦
[INFO ] 14:06:12.555 [main] c.a.druid.pool.DruidDataSource - {dataSource-1} inited
config
com.kangkang.a05.AtBeanPostProcessor
bean1
sqlSessionFactoryBean
dataSource
@Mapper 的解析
@Mapper 注解是在接口上使用的,但根据前文内容可知, @Mapper 被解析后在 Spring 容器中也存在与被标记的接口相关的 Bean。
难道 Spring 能管理接口?
那肯定是不行的,Spring 只能管理对象这是毋庸置疑的。那这些接口是怎么变成对象被 Spring 管理的呢?
这依赖于 MapperFactoryBean 将接口转换为对象。
在 Config 添加注册 Mapper1 和 Mapper2 的方法:
@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 注解的解析:
package com.kangkang.a05; | |
import org.mybatis.spring.mapper.MapperFactoryBean; | |
import org.springframework.beans.BeansException; | |
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | |
import org.springframework.beans.factory.support.AbstractBeanDefinition; | |
import org.springframework.beans.factory.support.BeanDefinitionBuilder; | |
import org.springframework.beans.factory.support.BeanDefinitionRegistry; | |
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; | |
import org.springframework.context.annotation.AnnotationBeanNameGenerator; | |
import org.springframework.core.io.Resource; | |
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; | |
import org.springframework.core.type.ClassMetadata; | |
import org.springframework.core.type.classreading.CachingMetadataReaderFactory; | |
import org.springframework.core.type.classreading.MetadataReader; | |
import java.io.IOException; | |
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor { | |
@Override | |
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { | |
try { | |
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); | |
Resource[] resources = resolver.getResources("classpath:com/kangkang/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 bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class) | |
.addConstructorArgValue(classMetadata.getClassName()) | |
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE) | |
.getBeanDefinition(); | |
AbstractBeanDefinition bd1 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()) | |
.getBeanDefinition(); | |
String s = generator.generateBeanName(bd1, beanDefinitionRegistry); | |
beanDefinitionRegistry.registerBeanDefinition(s,bd); | |
} | |
} | |
}catch (IOException e){ | |
e.printStackTrace(); | |
} | |
} | |
@Override | |
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { | |
} | |
} |
再测试下:
public static void main(String[] args) { | |
GenericApplicationContext context = new GenericApplicationContext(); | |
context.registerBean("config", Config.class); | |
context.registerBean(AtBeanPostProcessor.class); | |
/* | |
* AtBeanPostProcessor 的注册不能少,因为需要容器中存在 SqlSessionFactoryBean | |
* 而 SqlSessionFactoryBean 是在配置类中利用 @Bean 进行注册的 | |
*/ | |
context.registerBean(MapperPostProcessor.class); | |
context.refresh(); | |
for (String name : context.getBeanDefinitionNames()) { | |
System.out.println(name); | |
} | |
context.close(); | |
} |
com.kangkang.a05.Config.bean1()
com.kangkang.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
com.kangkang.a05.Config.dataSource()
[DEBUG] 14:08:25.515 [main] com.kangkang.a05.Bean1 - 我被 Spring 管理啦
[INFO ] 14:08:25.574 [main] c.a.druid.pool.DruidDataSource - {dataSource-1} inited
config
com.kangkang.a05.AtBeanPostProcessor
com.kangkang.a05.MapperPostProcessor
bean1
sqlSessionFactoryBean
dataSource
mapper1
mapper2
容器中存在 mapper1 和 mapper2 两个 Bean。
# 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 接口中抽象方法的参数:
@FunctionalInterface | |
public interface BeanFactoryPostProcessor { | |
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; | |
} |
尝试使用 BeanFactoryPostProcessor 注册创建完成的 Bean:
@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 生命周期。
# 六、Aware 接口
# 1.Aware 接口
Aware 接口用于注入一些与容器相关的信息,比如:
- BeanNameAware 注入 Bean 的名字
- BeanFactoryAware 注入 BeanFactory 容器
- ApplicationContextAware 注入 ApplicationContext 容器
- EmbeddedValueResolverAware 解析
${}
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); | |
} | |
} |
public static void main(String[] args) { | |
GenericApplicationContext context = new GenericApplicationContext(); | |
context.registerBean("myBean", MyBean.class); | |
context.refresh(); | |
context.close(); | |
} |
当前bean com.kangkang.a06.MyBean@6f15d60e 名字叫:com.kangkang.a06.MyBean
当前bean com.kangkang.a06.MyBean@6f15d60e 容器是:org.springframework.beans.factory.support.DefaultListableBeanFactory@7486b455: defining beans [com.kangkang.a06.MyBean,org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor]; root of factory hierarchy
# 2.InitializingBean
@Slf4j | |
public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean { | |
// --snip-- | |
@Override | |
public void afterPropertiesSet() throws Exception { | |
log.info("当前 Bean: " + this + " 初始化"); | |
} | |
} |
再次运行 main() 方法有:
当前bean com.kangkang.a06.MyBean@6f15d60e 名字叫:com.kangkang.a06.MyBean
当前bean com.kangkang.a06.MyBean@6f15d60e 容器是:org.springframework.beans.factory.support.DefaultListableBeanFactory@7486b455: defining beans [com.kangkang.a06.MyBean,org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor]; root of factory hierarchy
当前bean com.kangkang.a06.MyBean@6f15d60e 初始化
当同时实现 Aware 接口和 InitializingBean 接口时,会先执行 Aware 接口。
BeanFactoryAware 、 ApplicationContextAware 和 EmbeddedValueResolverAware 三个接口的功能可以使用 @Autowired 注解实现, InitializingBean 接口的功能也可以使用 @PostConstruct 注解实现,为什么还要使用接口呢?
@Autowired 和 @PostConstruct 注解的解析需要使用 Bean 后置处理器,属于拓展功能,而这些接口属于内置功能,不加任何拓展 Spring 就能识别。在某些情况下,拓展功能会失效,而内容功能不会失效。
public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean { | |
// --snip-- | |
@Autowired | |
public void setApplicationContextWithAutowired(ApplicationContext applicationContext) { | |
log.info("当前 Bean: " + this + " 使用 @Autowired 注解,容器是: " + applicationContext); | |
} | |
@PostConstruct | |
public void init() { | |
log.info("当前 Bean: " + this + " 使用 @PostConstruct 注解初始化"); | |
} | |
} |
再运行 main() 方法会发现使用的注解没有被成功解析,原因很简单, GenericApplicationContext 是一个干净的容器,其内部没有用于解析这些注解的后置处理器。如果想要这些注解生效,则需要像前文一样添加必要的后置处理器:
context.registerBean(AutowiredAnnotationBeanPostProcessor.class); | |
context.registerBean(CommonAnnotationBeanPostProcessor.class); |
# 3. 失效的 @Autowired 注解
在某些情况下,尽管容器中存在必要的后置处理器,但 @Autowired 和 @PostConstruct 注解也会失效。
@Configuration | |
public class MyConfig1 { | |
@Autowired | |
public void setApplicationContext(ApplicationContext applicationContext) { | |
log.info("注入 ApplicationContext"); | |
} | |
@PostConstruct | |
public void init() { | |
log.info("初始化"); | |
} | |
} |
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(); | |
} |
com.kangkang.a06.MyConfig1 : 注入 ApplicationContext
com.kangkang.a06.MyConfig1 : 初始化
@Autowired 和 @PostConstruct 注解成功被解析。
如果再对 Config1 进行一点小小的修改呢?
@Configuration | |
public class MyConfig1 { | |
// --snip-- | |
@Bean | |
public BeanFactoryPostProcessor processor1() { | |
return processor -> log.info("执行 processor1"); | |
} | |
} |
在 Config1 中添加了一个被 @Bean 注解标记的 processor1() 方法,用于向容器中添加 BeanFactoryPostProcessor 。
如果再运行 main() 方法:
com.kangkang.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 |
要解决这个问题也很简单,使用相关接口的功能实现注入和初始化:
@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() 方法:
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(); | |
} |
com.kangkang.a06.MyConfig2 - 注入 ApplicationContext
com.kangkang.a06.MyConfig2 - 初始化
com.kangkang.a06.MyConfig2 - 执行 processor2
总结
Aware接口提供了一种 内置 的注入手段,可以注入 BeanFactory、ApplicationContext;InitializingBean接口提供了一种 内置 的初始化手段;- 内置的注入和初始化不受拓展功能的影响,总会被执行,因此 Spring 框架内部的类总是使用这些接口。
# 七、初始化与销毁
初始化和销毁 Bean 的实现有三种:
- 依赖于后置处理器提供的拓展功能
- 相关接口的功能
- 使用
@Bean注解中的属性进行指定
当同时存在以上三种方式时,它们的执行顺序也将按照上述顺序进行执行。
包含三种初始化方式的 Bean:
@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:
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"); | |
} | |
} |
测试一下:
@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(); | |
} | |
} |
com.kangkang.a07.Bean1 - 初始化 1
com.kangkang.a07.Bean1 - 初始化 2
com.kangkang.a07.Bean1 - 初始化 3
com.kangkang.a07.Bean2 - 销毁 1
com.kangkang.a07.Bean2 - 销毁 2
com.kangkang.a07.Bean2 - 销毁 3
# 八、Scope
# 1.Scope 的类型与销毁
Scope 用于指定 Bean 的作用范围,有如下五个取值:
singleton:单例(默认值)。容器启动时创建(未设置延迟),容器关闭时销毁prototype:多例。每次使用时创建,不会自动销毁,需要调用DefaultListableBeanFactory#destroyBean()进行销毁request:作用于 Web 应用的请求范围。每次请求用到此 Bean 时创建,请求结束时销毁session:作用于 Web 应用的会话范围。每个会话用到此 Bean 时创建,会话结束时销毁application:作用于 Web 应用的ServletContext。Web 容器用到此 Bean 时创建,容器关闭时销毁
前两个取值不再赘述,重点看下后三个取值。
@Scope("request") | |
@Component | |
public class BeanForRequest { | |
private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class); | |
@PreDestroy | |
public void destroy() { | |
log.debug("destroy"); | |
} | |
} |
@Scope("session") | |
@Component | |
public class BeanForSession { | |
private static final Logger log = LoggerFactory.getLogger(BeanForSession.class); | |
@PreDestroy | |
public void destroy() { | |
log.debug("destroy"); | |
} | |
} |
@Scope("application") | |
@Component | |
public class BeanForApplication { | |
private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class); | |
@PreDestroy | |
public void destroy() { | |
log.debug("destroy"); | |
} | |
} |
编写一个 Controller 进行测试:
@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) { | |
ServletContext sc = request.getServletContext(); | |
String sb = "<ul>" + | |
"<li>" + "request scope:" + beanForRequest + "</li>" + | |
"<li>" + "session scope:" + beanForSession + "</li>" + | |
"<li>" + "application scope:" + beanForApplication + "</li>" + | |
"</ul>"; | |
return sb; | |
} | |
} |
主启动类:
@SpringBootApplication | |
public class A08Application { | |
public static void main(String[] args) { | |
SpringApplication.run(A08Application.class, args); | |
} | |
} |
如果使用的 JDK 版本大于 8,需要要启动参数中添加如下信息避免报错:
--add-opens java.base/java.lang=ALL-UNNAMED |
运行主启动类,在浏览器中访问 http://localhost:8080/test ,页面上显示:
- request scope:com.kangkang.a08.BeanForRequest@39f0ecff
- session scope:com.kangkang.a08.BeanForSession@7b1d988f
- application scope:com.kangkang.a08.BeanForApplication@53ef544f
刷新页面,页面上的信息变化为:
- request scope:com.kangkang.a08.BeanForRequest@28ec257f
- session scope:com.kangkang.a08.BeanForSession@7b1d988f
- application scope:com.kangkang.a08.BeanForApplication@53ef544f
可以看到 request scope 发生了变化, session scope 和 application scope 没有变化。
这是因为刷新页面后就产生了一个新的请求,而 request 的作用范围只在一个请求内,因此每一个新请求就对应一个新的对象。
那要怎么改变 session scope 呢?
换一个浏览器访问 http://localhost:8080/test ,两个浏览器中的会话肯定不是同一个,此时 session scope 应该会发生变化:
- request scope:com.kangkang.a08.BeanForRequest@6622241e
- session scope:com.kangkang.a08.BeanForSession@271f2e25
- application scope:com.kangkang.a08.BeanForApplication@53ef544f
application 的作用范围是 ServletContext ,要想 application scope 发生变化可以重启程序。
销毁
当刷新页面后,除了 request scope 的值发生变化外,在 IDEA 的控制台能看到以下信息:
com.kangkang.a08.BeanForRequest - destroy
这表示 request 作用范围的 Bean 进行了销毁,执行了销毁方法。
如果想看到 session 作用范围的 Bean 执行销毁方法,可以等 session 过期时在控制台上看到对应的信息。默认情况下,session 的过期时间是 30 分钟,为了更好地测试,可以在配置文件中添加:
# 修改 session 过期时间为 10s | |
server.servlet.session.timeout=10s |
这个配置是全局的,如果只想针对某个请求进行配置,则可以:
@GetMapping(value = "/test", produces = "text/html") | |
public String test(HttpServletRequest request, HttpSession session) { | |
// 设置 session 过期时间为 10 秒 | |
session.setMaxInactiveInterval(10); | |
// --snip-- | |
} |
设置 session 过期时间为 10 秒后,并不表示不进行任何操作 10 秒后就能在控制台上看到执行销毁方法的信息,经过测试,大概会等 1 分钟,静静等待 1 分钟左右,控制台上显示:
com.kangkang.a08.BeanForSession : destroy
很遗憾没有办法看到 application 作用范围的 Bean 执行销毁方法,因为 Spring 似乎并没有对 application 作用范围的 Bean 进行正确的销毁处理,因此在 Servlet 容器销毁时看不到 application 作用范围的 Bean 执行销毁方法。
# 2.Scope 失效分析
现有两个类:
@Component | |
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) | |
public class F1 { | |
} |
@Getter | |
@Component | |
public class E { | |
@Autowired | |
private F1 f1; | |
} |
之后进行测试:
@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 应该都不是同一个对象吗?
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F1@715fa8c5
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F1@715fa8c5
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F1@715fa8c5
获取到的 f1 居然都是同一个,也就是说向单例对象中注入多例对象失败了。
对于单例对象来说,依赖注入仅发生了一次,后续不会再注入其他的 f1 ,因此 e 始终使用的是第一次注入的 f1 :
graph LR | |
e1(e 创建) | |
e2(e set 注入 f) | |
f1(f 创建) | |
e1-->f1-->e2 |
为了解决这个问题,可以使用 @Lazy 生成代理对象,虽然代理对象依旧是同一个,但每次使用代理对象中的方法时,会由代理对象创建新的目标对象:
graph LR | |
e1(e 创建) | |
e2(e set 注入 f代理) | |
f1(f 创建) | |
f2(f 创建) | |
f3(f 创建) | |
e1-->e2 | |
e2--使用f方法-->f1 | |
e2--使用f方法-->f2 | |
e2--使用f方法-->f3 |
解决方式一
@Getter | |
@Component | |
public class E { | |
@Lazy | |
@Autowired | |
private F1 f1; | |
} |
再修改下 main() 方法,打印下 f1 的 Class 信息,查看是否是代理对象:
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(); | |
} |
com.kangkang.a08.useless.A08_1 - class com.kangkang.a08.useless.F1$$EnhancerBySpringCGLIB$$48c18ed4
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F1@724bf25f
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F1@6f6cc7da
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F1@29f3c438
使用 @Lazy 注解后,注入的是代理对象,每次获取到的 f1 不再是同一个。
解决方式二
除了使用 @Lazy 注解外,可以使用 @Scope 注解的 proxyMode 属性指定代理模式:
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) | |
@Component | |
public class F2 { | |
} | |
@Getter | |
@Component | |
public class E { | |
@Autowired | |
private F2 f2; | |
} |
之后再测试:
@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(); | |
} | |
} |
com.kangkang.a08.useless.A08_1 - class com.kangkang.a08.useless.F2$$EnhancerBySpringCGLIB$$d60af49b
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F2@2c3158e0
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F2@412ebe64
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F2@6f731759
解决方式三
@Scope("prototype") | |
@Component | |
public class F3 { | |
} | |
@Component | |
public class E { | |
@Autowired | |
private ObjectFactory<F3> f3; | |
public F3 getF3() { | |
return f3.getObject(); | |
} | |
} |
@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(); | |
} | |
} |
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F3@7a83ccd2
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F3@599a9cb2
解决方式四
@Scope("prototype") | |
@Component | |
public class F4 { | |
} | |
@Component | |
public class E { | |
@Autowired | |
private ApplicationContext applicationContext; | |
public F4 getF4() { | |
return applicationContext.getBean(F4.class); | |
} | |
} |
@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(); | |
} | |
} |
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F4@3a1b36a1
com.kangkang.a08.useless.A08_1 - com.kangkang.a08.useless.F4@5e1a986c
如果对性能要求较高,则推荐使用后两种方式,前两种使用代理会有一定的性能损耗;如果不在乎那点性能损耗,则可以使用第一种方式,这种方式最简单。
四种解决方式虽然不同,但在理念上殊途同归,都是推迟了其他 Scope Bean 的获取,或者说按需加载。
