封面画师: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的类图

ConfigurableApplicationContext 接口继承了 ApplicationContext 接口,而 ApplicationContext 接口又间接地继承了 BeanFactory 接口,除此之外还继承了其他很多接口,相当于对 BeanFactory 进行了拓展。

1.1 什么是 BeanFactory

  • 它是 ApplicationContext 的父接口
  • 它才是 Spring 的核心容器,主要的 ApplicationContext 实现 组合 了它的功能,也就是说,BeanFactoryApplicationContext 中的一个成员变量。

常用的 context.getBean("xxx") 方法,其实是调用了 BeanFactorygetBean() 方法。

1.2 BeanFactory 能做什么?

进入 BeanFactory 接口,在 IDEA 中使用快捷键 Ctrl + F12 查看这个接口中所有的方法定义:

BeanFactory中的方法

通过这些方法定义可知,BeanFactory 表面上只有 getBean() 方法,但实际上 Spring 中的控制反转、基本的依赖注入、乃至 Bean 的生命周期中的各个功能都是由它的实现类提供。

查看 DefaultListableBeanFactory 类的类图:

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 类的类图:

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 外,其余三个文件内容如下:

1
thanks=Thank you
1
thanks=谢谢
1
thanks=謝謝

测试 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);
// --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:

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");
// 设置国际化资源文件的 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 的使用

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);
// --snip--

Resource[] resources = context.getResources("classpath:application.properties");
Assert.isTrue(resources.length > 0, "加载类路径下的 application.properties 文件失败");

// 使用 classpath* 可以加载 jar 里类路径下的 resource
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);
// --snip--

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
#{"author.name":"mofan"}
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);
// --snip--

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();
// 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 工厂中应该还存在 bean1bean2,那为什么现在没有呢?

很明显是现在的 BeanFactory 缺少了解析 @Configuration@Bean 两个注解的能力。

1
2
3
4
5
6
7
public static void main(String[] args) {
// --snip--

// 给 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 工厂中依旧没有 bean1bean2 呢?

现在仅仅是将处理器添加到了 Bean 工厂,还没有使用处理器。

使用处理器很简单,先获取到处理器,然后再使用即可。像 internalConfigurationAnnotationProcessor 这样的 Bean,都有一个共同的类型,名为 BeanFactoryPostProcessor,因此可以:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
// --snip--

// 使用后置处理器
// 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

依赖注入

bean1bean2 已经被添加到 Bean 工厂中,尝试获取 bean1 中的 bean2,查看 bean2 是否成功注入到 bean1 中:

1
2
3
4
public static void main(String[] args) {
// --snip--
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
}
null

bean2 没有成功被注入到 bean1 中。

在先前添加到 BeanFactory 中的后置处理器里,有名为 internalAutowiredAnnotationProcessorinternalCommonAnnotationProcessor 的两个后置处理器。前者用于解析 @Autowired 注解,后者用于解析 @Resource 注解,它们都有一个共同的类型 BeanPostProcessor,因此可以:

1
2
3
4
5
6
7
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());
}
----------------------------------------
[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

建立 BeanPostProcessorBeanFactory 的关系后,bean2 被成功注入到 bean1 中了。

除此之外还可以发现:当需要使用 Bean 时,Bean 才会被创建,即按需加载。那有没有什么办法预先就初始化好单例对象呢?

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
// --snip--

// 预先初始化单例对象(完成依赖注入和初始化流程)
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 {
// --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 工厂中添加了 bean3bean4,并且计划在 bean1 中注入 Inter 类型的 Bean。

现在 Bean 工厂中 Inter 类型的 Bean 有两个,分别是 bean3bean4,那么会注入哪一个呢?

如果只使用 @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) {
// --snip--
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) {
// --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

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
// --snip--
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());
}
}

// --snip--
}

设置的 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);
// --snip--
}

值越小,优先级越大,就排在更前面,因此当设置了 AnnotationAwareOrderComparator 比较器后,CommonAnnotationBeanPostProcessor 排在更前面,@Resource 就先生效。

2.2 ApplicationContext 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author mofan
* @date 2022/12/23 23:32
*/
@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() {
// 使用相对路径时,以模块为起点(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
indi.mofan.bean.a02.A02Application$Bean1@2db7a79b

从 XML 文件中读取 Bean 的定义

ClassPathXmlApplicationContextFileSystemXmlApplicationContext 都依赖于从 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 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 配置类来创建。首先定义配置类:

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() {
// 提供内嵌的 Web 容器
return new TomcatServletWebServerFactory();
}

@Bean
public DispatcherServlet dispatcherServlet() {
// 添加前控制器
return new DispatcherServlet();
}

@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
// 将 DispatcherServlet 注册到 Tomcat 服务器
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}

// 如果 bean 以 '/' 开头,将 '/' 后的 bean 的名称作为访问路径
@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 字样:

AnnotationConfigServletWebServerApplicationContext的使用

2.3 【补充】BeanFactory 接口体系

BeanFactory 其实就是 Spring IoC 容器,它本身是一个接口,提供了一系列获取 Bean 的方式。

基于它也有众多子接口:

  • ListableBeanFactory:提供获取 Bean 集合的能力,比如一个接口可能有多个实现,通过该接口下的方法就能获取某种类型的所有 Bean;
  • HierarchicalBeanFactoryHierarchical 意为“层次化”,通常表示一种具有层级结构的概念或组织方式,这种层次化结构可以通过父子关系来表示对象之间的关联,比如树、图、文件系统、组织架构等。根据该接口下的方法可知,能够获取到父容器,说明 BeanFactory 有父子容器概念;
  • AutowireCapableBeanFactory:提供了创建 Bean、自动装配 Bean、属性填充、Bean 初始化、依赖注入等能力,比如 @Autowired 注解的底层实现就依赖于该接口的 resolveDependency() 方法;
  • ConfigurableBeanFactory:该接口并未直接继承至 BeanFactory,而是继承了 HierarchicalBeanFactoryConfigurable 意为“可配置的”,就是说该接口用于对 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

AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner 中都有一个 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) {
// 既实现了 BeanFactory,又实现了 BeanDefinitionRegistry
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// ClassPathBeanDefinitionScanner 的一种替代,编程式显式注册 bean
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 {
// --snip--
}

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);
}

// --snip--

}

实现获取资源的方式并不是由实现类自身完成,而是交给其内部的一个成员变量完成,这样的方式就是委派(这和对象适配器模式很相似)。

在日常编码遇到这样的实现逻辑时,类名可以以 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);
// 调用 close 方法,显示生命周期的销毁阶段
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,比如 InstantiationAwareBeanPostProcessorDestructionAwareBeanPostProcessor

实现这两个接口,并使用 @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 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
/**
* @author mofan
* @date 2022/12/25 22:55
*/
@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 {
}

Bean2Bean3 很简单,而在 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 是一个干净的容器
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 中使用的注解能够生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
// --snip--

context.registerBean("bean3", Bean3.class);

// 解析值注入内容
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
// @Autowired @Value
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);

context.refresh();

// --snip--
}
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) {
// --snip--

// @Resource @PostConstruct @PreDestroy
context.registerBean(CommonAnnotationBeanPostProcessor.class);

// --snip--
}
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.homejava.version 的信息。

对先前的 main() 方法进行补充:

1
2
3
4
5
6
7
8
9
10
11
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 注解的后置处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
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:\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();
// 注册成品 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)
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 中的 bean2bean3home 都没有注入成功,而在调用之后,成功注入了 bean2home,但 home 的值似乎有点奇怪,并没有打印出前文中相同的值,可能是因为没有成功解析 #{}

至于 bean3 为什么没注入成功,是因为 bean3 的注入是利用 @Resource,而不是 @Autowired。如果对 Bean1 进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
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) {
// --snip--

// ${} 的解析器
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

// --snip--

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) {
// --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对象中包含的信息

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) {
// --snip--

// 获取 Bean1 上加了 @Value、@Autowired 注解的成员变量、方法参数信息
InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null);

// 调用 InjectionMetadata 来进行依赖注入,注入时按类型查找值
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) {
// --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);
}
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);
// @ComponentScan @Bean @Import @ImportResource
context.registerBean(ConfigurationClassPostProcessor.class);

// --snip--
}
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);
// @ComponentScan @Bean @Import @ImportResource
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(MapperScannerConfigurer.class,
i -> i.getPropertyValues().add("basePackage", "indi.mofan.bean.a05.mapper"));

// --snip--
}

其中的 basePackageMapperScannerConfigurer 中的一个成员变量,表示需要扫描的包路径,设置的值恰好是被 @Mapper 注解标记的接口所在的包路径。

控制台打印的信息中增加了:

mapper1
mapper2

除此之外,还有一些常用的后置处理器并没有在上述信息中体现。

5.2 模拟实现

移除向容器中添加的 ConfigurationClassPostProcessorMapperScannerConfigurer 两个后置处理器,自行编码模拟它们功能的实现。

组件扫描之 @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
/**
* @author mofan
* @date 2023/1/7 22:13
*/
public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {

/**
* 调用 context.refresh() 方法时回调
*/
@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);
// indi.mofan.bean.a05.component -> classpath*:indi/mofan/bean/a05/component/**/**.class
String path = "classpath*:" + packageName.replace(".", "/") + "/**/**.class";
// Resource[] resources = context.getResources(path);
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();
// System.out.println("类名: " + reader.getClassMetadata().getClassName());
// System.out.println("是否加了 @Component: " + annotationMetadata.hasAnnotation(Component.class.getName()));
// System.out.println("是否加了 @Component 派生: " + annotationMetadata.hasMetaAnnotation(Component.class.getName()));
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();
}

// --snip--
}

与解析 @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
/**
* @author mofan
* @date 2023/1/7 22:55
*/
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 添加注册 Mapper1Mapper2 的方法:

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() 方法可以看到容器中存在名为 mapper1mapper2 的 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
/**
* @author mofan
* @date 2023/1/7 23:45
*/
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);
/*
* AtBeanPostProcessor 的注册不能少,因为需要容器中存在 SqlSessionFactoryBean
* 而 SqlSessionFactoryBean 是在配置类中利用 @Bean 进行注册的
*/
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

容器中存在 mapper1mapper2 两个 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
/**
* @author mofan
* @date 2023/1/8 16:12
*/
@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 {
// --snip--

@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 {
// --snip--

@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 {
// --snip--

@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(); 方法来说,它主要按照以下顺序干了三件事:

  1. 执行 BeanFactory 后置处理器;
  2. 添加 Bean 后置处理器;
  3. 创建和初始化单例对象。

比如当 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

总结

  1. Aware 接口提供了一种 内置 的注入手段,可以注入 BeanFactory、ApplicationContext;
  2. InitializingBean 接口提供了一种 内置 的初始化手段;
  3. 内置的注入和初始化不受拓展功能的影响,总会被执行,因此 Spring 框架内部的类总是使用这些接口。

7. 初始化与销毁

初始化和销毁 Bean 的实现有三种:

  1. 依赖于后置处理器提供的拓展功能
  2. 相关接口的功能
  3. 使用 @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 过期时间为 10 秒
session.setMaxInactiveInterval(10);
// ServletContext sc = request.getServletContext();
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 scopeapplication 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
# 修改 session 过期时间为 10s
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 过期时间为 10 秒
session.setMaxInactiveInterval(10);

// --snip--
}

设置 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 的获取,或者说按需加载。