bilibili黑马四十九讲SpringBoot

37.Boot 骨架项目

使用 IDEA 创建 SpringBoot 项目时,会创建出 .mvn 目录、HELP.md、mvnw 和 mvnw.cmd 等不必要的文件。

Windows 下的Docker 这篇文章里介绍了如何在 windows 下安装 WSL。

进入 WSL,并输入

curl -G [https://start.spring.io/pom.xml](https://start.spring.io/pom.xml) -d dependencies=web,mysql,mybatis -o pom.xml

查看当前目录:pwd

手动指定路径并复制文件:

cp /home/daidawang/pom.xml /mnt/c/Users/zhangzhiwei/Documents/demo

进入指定路径: cd C:\Users\zhangzhiwei\Documents\demo

配置 idea 环境变量:

将 IDEA64 变量添加到 PATH

输入 idea .\pom.xml

idea 根据 pom 创建项目:

38.构建 spring war 项目

39.boot 启动流程

SpringBoot 的主启动类类似于:

1
2
3
4
5
6
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
}

其中 SpringApplication#run() 方法是核心方法:

1
2
3
4
5
6
7
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

最终使用 new 关键字构造了 SpringApplication 对象,然后调用了下面非静态的 run()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
//创建一个新的SpringApplication实例。
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

39.1 SpringApplication 构造分析

构造 SpringApplication 对象时做了如下几件事:

  1. 获取 BeanDefinition 对象。根据各种来源 添加 BeanDefinition(xml、配置类等)
  2. 推断应用类型。
  3. 添加 ApplicationContext 初始化器
  4. 添加事件监听器
  5. 主类推断 。得到当前方法运行的所在类

获取 BeanDefinition 源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
//@SpringBootApplication
public class A39_1 {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(A39_1.class);
application.setSources(Set.of("classpath:b01.xml"));

ConfigurableApplicationContext context = application.run(args);

for (String beanDefinitionName : context.getBeanDefinitionNames()) {
String resourceDescription = context.getBeanFactory().getBeanDefinition(beanDefinitionName).getResourceDescription();
System.out.println("名称: "+beanDefinitionName +" 资源来源: "+resourceDescription);
}

context.close();
}

static class Bean2{}

@Bean
public Bean2 bean2(){
return new Bean2();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
***************************
APPLICATION FAILED TO START
***************************

Description:

Web application could not be started as there was no org.springframework.boot.web.servlet.server.ServletWebServerFactory bean defined in the context.

Action:

Check your application's dependencies for a supported servlet web server.
Check the configured web application type.

这是因为添加了 spring-boot-starter-web 依赖,但 Spring 容器中并没有 SerletWebServerFactory 类型的 Bean。向容器添加:

1
2
3
4
5
6
@Bean
public TomcatServletWebServerFactory servletWebServerFactory(){
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
tomcatServletWebServerFactory.setPort(8081);
return tomcatServletWebServerFactory;
}
1
2
3
4
5
6
7
8
9
name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源: null
name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源: null
name: org.springframework.context.annotation.internalCommonAnnotationProcessor 来源: null
name: org.springframework.context.event.internalEventListenerProcessor 来源: null
name: org.springframework.context.event.internalEventListenerFactory 来源: null
name: a39_1 来源: null
name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源: null
name: bean2 来源: indi.mofan.a39.A39_1
name: servletWebServerFactory 来源: indi.mofan.a39.A39_1

来源为 null的 Bean 是 Spring 提供的 内置 Bean

使用 XML 配置文件添加 Bean,并利用 setSources() 方法设置创建 ApplicationContext 的其他来源:

1
2
3
4
5
public static void main(String[] args) {
SpringApplication spring = new SpringApplication(A39_1.class);
spring.setSources(Collections.singleton("classpath:b01.xml"));
// --snip--
}

创建 b01.xml

1
2
3
4
5
6
7
<?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="com.example.boot.A39_1"></bean>
</beans>

再次运行多出如下:

1
名称: bean1 资源来源: class path resource [b01.xml]

推断应用类型

应用类型的推断在构造方法中可以看到:

1
2
3
4
5
6
 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// --snip--
// 推断应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// --snip--
}

推断逻辑由 WebApplicationType 枚举中的 deduceFromClasspath() 方法完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static WebApplicationType deduceFromClasspath() {
// ClassUtils.isPresent() 判断类路径下是否存在某个类
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
// 响应式 Web 应用
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
// 非 Web 应用
return WebApplicationType.NONE;
}
}
// Web 应用
return WebApplicationType.SERVLET;
}

利用反射调用 方法

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

Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
deduceFromClasspath.setAccessible(true);
System.out.println("\t应用类型为: " + deduceFromClasspath.invoke(null));

// --snip--
}
1
应用类型为: SERVLET

添加 ApplicationContext 初始化器

调用 SpringApplication#run() 方法时会创建 ApplicationContext,最后调用 ApplicationContext#refresh() 方法完成初始化。

在创建与初始化完成之间的一些拓展功能就由 ApplicationContext 初始化器完成。

在 SpringApplication 的构造方法中,添加的初始化器信息从配置文件中读取:

1
2
3
4
5
6
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// --snip--
// 从配置文件中读取初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// --snip--
}

也可以调用 SpringApplication 对象的 addInitializers() 方法添加自定义初始化器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@SneakyThrows
public static void main(String[] args) {
// --snip--

spring.addInitializers(applicationContext -> {
if (applicationContext instanceof GenericApplicationContext) {
GenericApplicationContext context = (GenericApplicationContext) applicationContext;
context.registerBean("bean3", Bean3.class);
}
});

// 创建并初始化 Spring 容器
ConfigurableApplicationContext context = spring.run(args);
Arrays.stream(context.getBeanDefinitionNames()).forEach(i -> {
System.out.println("name: " + i +
" 来源: " + context.getBeanFactory().getBeanDefinition(i).getResourceDescription());
});
context.close();
}

static class Bean3 {
}
1
name: bean3 来源: null

添加事件监听器

与添加 ApplicationContext 初始化器一样,在 SpringApplication 的构造方法中,添加的事件监听器信息从配置文件中读取:

1
2
3
4
5
6
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// --snip--
// 从配置文件中读取事件监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// --snip--
}

可以调用 SpringApplication 对象的 addListeners() 方法添加自定义事件监听器:

1
2
3
4
5
6
7
8
@SneakyThrows
public static void main(String[] args) {
// --snip--
// 输出所有事件信息
spring.addListeners(event -> System.out.println("\t事件为: " + event));
// --snip--
context.close();
}
1
2
3
4
5
6
7
8
9
10
11
12
 事件类型为: class org.springframework.boot.context.event.ApplicationStartingEvent
事件类型为: class org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
事件类型为: class org.springframework.boot.context.event.ApplicationContextInitializedEvent
事件类型为: class org.springframework.boot.context.event.ApplicationPreparedEvent
事件类型为: class org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
事件类型为: class org.springframework.context.event.ContextRefreshedEvent
事件类型为: class org.springframework.boot.context.event.ApplicationStartedEvent
事件类型为: class org.springframework.boot.availability.AvailabilityChangeEvent
事件类型为: class org.springframework.boot.context.event.ApplicationReadyEvent
事件类型为: class org.springframework.boot.availability.AvailabilityChangeEvent
事件类型为: class org.springframework.boot.availability.AvailabilityChangeEvent
事件类型为: class org.springframework.context.event.ContextClosedEvent

主类推断

主类推断在构造方法中可以看到:

1
2
3
4
5
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// --snip--
// 主类推断
this.mainApplicationClass = deduceMainApplicationClass();
}

推断逻辑由 deduceMainApplicationClass() 方法完成,利用反射调用该方法:

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

Method deduceMainApplicationClass = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
deduceMainApplicationClass.setAccessible(true);
System.out.println("\t主类是: " + deduceMainApplicationClass.invoke(spring));

// --snip--
}
1
主类推断为: class com.example.boot.A39_1

39.2 SpringApplication run 分析

1
2
3
4
5
6
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
}
1
2
3
4
5
6
7
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

构造方法 new SpringApplication(primarySources)已经讲完,接下来看 run() 方法

第一步 得到 SpringApplicationRunListeners

事件发布器

在执行 run() 方法时,先获取到 SpringApplicationRunListeners,它是事件发布器组合,

作用是:在 Spring Boot 启动各个阶段发布事件。

SpringApplicationRunListeners 中使用 SpringApplicationRunListener 来描述单个事件发布器,

SpringApplicationRunListener 是一个接口,它有且仅有一个实现类 EventPublishingRunListener。

事件发布器都是在配置文件中读取,从 META-INFO/spring.factories 中读取,该文件中有这样一句:

1
2
3
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

从 META-INFO/spring.factories配置文件读取事件发布器信息,并发布各种事件

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
/**
* @version v1.0
* @ClassName: A39run方法
* @Description:
* @Author: 呆大王
* @Date: 2024/9/6
*/
public class A39 {
@SneakyThrows
public static void main(String[] args) {
SpringApplication app = new SpringApplication();
//添加事件监听器
app.addListeners(e-> System.out.println(e.getClass()));

//获取事件发布器SpringApplicationRunListener的实现类名
List<String> names = SpringFactoriesLoader.loadFactoryNames
(SpringApplicationRunListener.class, A39.class.getClassLoader());


for (String name : names) {
System.out.println(name);
Class<?> clazz = Class.forName(name);

Constructor<?> constructor = clazz.getConstructor(SpringApplication.class, String[].class);
SpringApplicationRunListener publisher = (SpringApplicationRunListener) constructor.newInstance(app, args);
//发布事件

//springboot启动
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
publisher.starting(bootstrapContext);
//环境信息准备完毕
publisher.environmentPrepared(bootstrapContext, new StandardEnvironment());
//在spring容器创建,并调用初始化器之后,发送下面事件
GenericApplicationContext context = new GenericApplicationContext();
publisher.contextPrepared(context);
//所有BeanDefinition加载完毕
publisher.contextLoaded(context);
//spring容器初始化完成(refresh方法调用完毕)
context.refresh();
publisher.started(context,null);
//spring启动完毕
publisher.ready(context,null);

//启动失败
publisher.failed(context, new RuntimeException("启动失败"));
}
}
}

在 SpringBoot 启动过程中,总共发布 7 种事件。

运行 main() 方法后,控制台打印出:

1
2
3
4
5
6
7
8
9
10
11
org.springframework.boot.context.event.EventPublishingRunListener
class org.springframework.boot.context.event.ApplicationStartingEvent
class org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
class org.springframework.boot.context.event.ApplicationContextInitializedEvent
class org.springframework.boot.context.event.ApplicationPreparedEvent
class org.springframework.context.event.ContextRefreshedEvent
class org.springframework.boot.context.event.ApplicationStartedEvent
class org.springframework.boot.availability.AvailabilityChangeEvent
class org.springframework.boot.context.event.ApplicationReadyEvent
class org.springframework.boot.availability.AvailabilityChangeEvent
class org.springframework.boot.context.event.ApplicationFailedEvent

但打印出的事件种类并不止 7 种,这是因为包含了其他事件发布器发布的事件,

EventPublishingRunListener 发布的事件的全限定类名包含 boot.context.event,

根据这个条件重新计算,恰好 7 个。

第八到十一步 完成 Spring 容器创建

  • 第八步:创建容器。在构造 SpringApplication 时推断出应用类型,使用应用类型直接创建
  • 第九步:准备容器。回调最终构造 SpringApplication时添加的初始化器
  • 第十步:加载 Bean 定义。从配置类、xml 配置文件读取 BeanDefintion,或者扫描某包路径下的 BeanDefintion 对象
  • 第十一步:调用 Application #refresh() 方法,完成 Spring 容器的创建。
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public class A39_3 {

@SneakyThrows
@SuppressWarnings("all")
public static void main(String[] args) {
SpringApplication application = new SpringApplication();
application.addInitializers(applicationContext -> System.out.println("执行初始化器增强..."));
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器");
GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器");
for (ApplicationContextInitializer initializer : application.getInitializers()) {
initializer.initialize(context);
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义");
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();

AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(context.getDefaultListableBeanFactory());
reader.register(Config.class);

XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource("b03.xml"));

ClassPathBeanDefinitionScanner classPathBeanDefinitionScanner = new ClassPathBeanDefinitionScanner(beanFactory);
classPathBeanDefinitionScanner.scan("com.example.boot.sub");


System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器");
context.refresh();
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("name: "+beanDefinitionName+" 来源: "+
beanFactory.getBeanDefinition(beanDefinitionName).getResourceDescription());
}

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner");

}

private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
GenericApplicationContext context = null;
switch (type) {
case REACTIVE:
context = new AnnotationConfigReactiveWebServerApplicationContext();
break;
case SERVLET:
context = new AnnotationConfigServletWebApplicationContext();
break;
case NONE:
context = new AnnotationConfigApplicationContext();
break;
}
return context;
}

static class Bean4 {

}

static class Bean5 {

}

static class Bean6 {

}

@Configuration
static class Config {
@Bean
Bean5 bean5() {
return new Bean5();
}

@Bean
public ServletWebServerFactory servletWebServerFactory() {
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory();
webServerFactory.setPort(8081);
return webServerFactory;
}
}
}

b03.xml:

1
2
3
4
5
6
<?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="bean4" class="com.example.boot.A39_3.Bean4"></bean>
</beans>

com.example.boot.sub 包下的 Bean:

1
2
3
@Component
public class Bean7 {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args
>>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器
>>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器
执行初始化器增强...
>>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义
>>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器
name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源: null
name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源: null
name: org.springframework.context.annotation.internalCommonAnnotationProcessor 来源: null
name: org.springframework.context.event.internalEventListenerProcessor 来源: null
name: org.springframework.context.event.internalEventListenerFactory 来源: null
name: a39_3.Config 来源: null
name: bean4 来源: class path resource [b03.xml]
name: bean7 来源: file [D:\workspace\advanced-spring\boot\target\classes\com\example\boot\sub\Bean7.class]
name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源: null
name: bean5 来源: com.example.boot.A39_3$Config
name: servletWebServerFactory 来源: com.example.boot.A39_3$Config
>>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner

第二步 封装启动 args

调用 DefaultApplicationArguments的构造方法,传入 args :

1
2
3
4
5
6
7
8
@SneakyThrows
@SuppressWarnings("all")
public static void main(String[] args) {
// --snip--
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);
// --snip--
}

第十二步 执行 Runner

在 SpringBoot 启动成功后,可以执行一些 Runner,进行预处理和测试

Runner 分为:CommandLineRunner , ApplicationRunner

1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface CommandLineRunner {
void run(String... args) throws Exception;
}

@FunctionalInterface
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}

它们都是 函数式接口,内部抽象方法长得也像,

  • CommandLineRunner 直接接受启动参数
  • ApplicationRunner 接收封装后的 ApplicationArguments,即** 第二步** 封装得对象

在配置类Config中添加这两种类型的 Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Bean
public ApplicationRunner applicationRunner() {
return new ApplicationRunner() {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner........"+Arrays.toString(args.getSourceArgs()));
System.out.println(args.getOptionNames());
System.out.println(args.getOptionValues(args.getOptionNames().iterator().next()));
System.out.println(args.getNonOptionArgs());
}
};
}

@Bean
public CommandLineRunner commandLineRunner() {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner........"+ Arrays.toString(args));
}
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@SneakyThrows
@SuppressWarnings("all")
public static void main(String[] args) {
// --snip--

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);

// --snip--

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner");
for (CommandLineRunner runner : context.getBeansOfType(CommandLineRunner.class).values()) {
runner.run(args);
}

for (ApplicationRunner runner : context.getBeansOfType(ApplicationRunner.class).values()) {
runner.run(arguments);
}
}

在运行 main 方法时,添加程序参数 --server.port=8080 debug:

1
2
3
4
5
6
>>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner
ApplicationRunner........[--server.port=8081, DEBUG]
[server.port]
[8081]
[DEBUG]
CommandLineRunner........[--server.port=8081, DEBUG]

第三步 准备 Environment 添加命令行参数

Environment环境对象,是对配置信息的抽象

配置信息来源可以是:系统环境变量、properties 配置文件、yaml 配置文件等.

SpringBoot 提供了名为 ApplicationEnvironment 的类表示环境对象

默认情况下,创建的 ApplictionEnvironment对象中配置信息的来源只有两个:

  • 系统属性
  • 系统变量
1
2
3
4
5
6
public class Step3 {
public static void main(String[] args) {
ApplicationEnvironment env = new ApplicationEnvironment();
env.getPropertySources().forEach(System.out::println);
}
}
1
2
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}

针对相同名称的配置信息,按照来源的先后顺序获取.

获取虚拟机参数

获取 JAVA_HOME 的配置信息:

1
2
3
4
public static void main(String[] args) {
ApplicationEnvironment env = new ApplicationEnvironment();
System.out.println(env.getProperty("JAVA_HOME"));
}
1
D:\software\work\jdk\jdk8\jdk

由于 PropertiesPropertySource 中并不存在名为 JAVA_HOME 的配置信息

所以从 SystemEnvironmentPropertySource 中获取 JAVA_HOME 的配置信息。

获取命令行参数

在 IDEA 的 Run/Debug Configurations 中的 VM options 添加 -DJAVA_HOME=abc,

使得 PropertiesPropertySource 中存在名为 JAVA_HOME 的配置信息:

1
abc

获取properties 配置文件信息

如果要从配置文件 application.properties 中读取配置信息,可以添加配置信息的来源。配置文件的优先级最低,添加来源时调用 addLast() 方法:

1
2
3
4
5
6
7
8
@SneakyThrows
public static void main(String[] args) {
ApplicationEnvironment env = new ApplicationEnvironment();
env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("application.properties")));
env.getPropertySources().forEach(System.out::println);

System.out.println(env.getProperty("author.name"));
}
1
author.name=daidawang
1
2
3
4
5
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
ResourcePropertySource {name='class path resource [application.properties]'}
abc
daidawang

获取系统环境变量

在 SpringBoot 中,这里 **只 **添加 SimpleCommandLinePropertSource

并且它的优先级最高,使用 addFirst() 方法添加:

1
2
3
4
5
6
7
8
9
@SneakyThrows
public static void main(String[] args) {
ApplicationEnvironment env = new ApplicationEnvironment();
env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("application.properties")));
env.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));
env.getPropertySources().forEach(System.out::println);

System.out.println(env.getProperty("author.name"));
}

添加程序参数 --author.name=呆大王

1
2
3
4
5
6
SimpleCommandLinePropertySource {name='commandLineArgs'}
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
ResourcePropertySource {name='class path resource [application.properties]'}
abc
呆大王

第四步 添加 ConfigurationPropertySources

创建 step4.properties 文件,内容如下:

1
2
3
user.first-name=dai
user.middle_name=da
user.lastName=wang

读取文件内容:

1
2
3
4
5
6
7
8
9
10
11
@SneakyThrows
public static void main(String[] args) {
ApplicationEnvironment env = new ApplicationEnvironment();
env.getPropertySources().addLast(
new ResourcePropertySource("step4", new ClassPathResource("step4.properties"))
);
env.getPropertySources().forEach(System.out::println);
System.out.println(env.getProperty("user.first-name"));
System.out.println(env.getProperty("user.middle-name"));
System.out.println(env.getProperty("user.last-name"));
}

step4.properties 文件中配置信息的 key 是 user.middle_name,但在读取时,使用的是 user.middle-name;还有 user.lastName 的 key,但读取时使用 user.last-name。能读取成功吗?

1
2
3
4
5
6
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
ResourcePropertySource {name='step4'}
George
null
null

显然是不行的,为了能读取成功,需要实现 松散绑定,添加 ConfigurationPropertySources:

1
2
3
4
5
6
@SneakyThrows
public static void main(String[] args) {
// --snip--
ConfigurationPropertySources.attach(env);
// --snip--
}
1
2
3
4
5
6
7
ConfigurationPropertySourcesPropertySource {name='configurationProperties'}
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
ResourcePropertySource {name='step4'}
dai
da
wang

第五步 使用EnvironmentPostProcessorApplicationListener 进行环境对象后置处理

在第三步中 只 添加 SimpleCommandLinePropertySource 读取系统属性和系统变量,

而 读取 properties、YAML 配置文件的源就是在第五步中添加的。

完成这样功能需要使用到 EnvironmentPostProcessor,其具体实现是 ConfigDataEnvironmentPostProcessor。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
SpringApplication app = new SpringApplication();
ApplicationEnvironment env = new ApplicationEnvironment();

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 增强前");
env.getPropertySources().forEach(System.out::println);
ConfigDataEnvironmentPostProcessor processor1 = new ConfigDataEnvironmentPostProcessor(
new DeferredLogs(), new DefaultBootstrapContext()
);
processor1.postProcessEnvironment(env, app);

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 增强后");
env.getPropertySources().forEach(System.out::println);
System.out.println(env.getProperty("author.name"));

RandomValuePropertySourceEnvironmentPostProcessor processor2 =
new RandomValuePropertySourceEnvironmentPostProcessor(new DeferredLog());
processor2.postProcessEnvironment(env, app);
}
1
2
3
4
5
6
7
8
>>>>>>>>>>>>>>>>>>>>>>>> 增强前
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
>>>>>>>>>>>>>>>>>>>>>>>> 增强后
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.properties]' via location 'optional:classpath:/''}
"daidawang"

EnvironmentPostProcessor 还有一个有趣的实现:RandomValuePropertySourceEnvironmentPostProcessor,该实现提供了随机值的生成。

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

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 再次增强后");
env.getPropertySources().forEach(System.out::println);
System.out.println(env.getProperty("random.string"));
System.out.println(env.getProperty("random.int"));
System.out.println(env.getProperty("random.uuid"));
}
1
2
3
4
5
6
7
8
>>>>>>>>>>>>>>>>>>>>>>>> 再次增强后
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.properties]' via location 'optional:classpath:/''}
5ef4038a709215938cbd3e1c031f66dd
1481116109
18548e0b-8bad-458b-b38e-bf793aa24ced

在 SpringBoot 中实现不是上述代码的方式添加后置处理器的,

同样会从 META-INF/spring.factories 配置文件读取并初始化处理器:

1
2
3
4
5
6
7
8
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor

SpringBoot 读取 META-INF/spring.factories 配置文件初始化环境后置处理器,再执行处理逻辑由

EnvironmentPostProcessorApplicationListener 完成.

它是事件监听器,同样在META-INF/spring.factories 配置文件中读取并初始化:

1
2
3
4
5
6
7
8
9
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener\

要想该监听器成功监听到事件,需要第五步发布一个事件,而事件的发布由第一步获取的事件发布器完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
SpringApplication app = new SpringApplication();
app.addListeners(new EnvironmentPostProcessorApplicationListener());
ApplicationEnvironment env = new ApplicationEnvironment();

List<String> names = SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, Step5.class.getClassLoader());
names.forEach(System.out::println);

EventPublishingRunListener publisher = new EventPublishingRunListener(app, args);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 增强前");
env.getPropertySources().forEach(System.out::println);
publisher.environmentPrepared(new DefaultBootstrapContext(), env);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 增强后");
env.getPropertySources().forEach(System.out::println);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor
>>>>>>>>>>>>>>>>>>>>>>>> 增强前
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
>>>>>>>>>>>>>>>>>>>>>>>> 增强后
PropertiesPropertySource {name='systemProperties'}
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.properties]' via location 'optional:classpath:/''}

配置文件 EnvironmentPostProcessor 实现有很多,但是上述打印的信息生效的不多,是否生效于项目的依赖配置无关.

第六步 绑定配置信息到 SpringApplication

@ConfigurationProperties 注解的原理

使用 @ConfigurationProperties 注解可以指定一个前缀,SpringBoot 将根据指定的前缀和属性名称在配置文件中寻找对应的信息并完成注入,其底层是利用 Binder 实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class A39ConfigurationProperties原理 {
public static void main(String[] args) throws IOException {
ApplicationEnvironment applicationEnvironment = new ApplicationEnvironment();
applicationEnvironment.getPropertySources()
.addLast(new ResourcePropertySource("step4",new ClassPathResource("step4.properties")));
User user = Binder.get(applicationEnvironment).bind("user", User.class).get();
System.out.println(user);

User user1 = new User();
Binder.get(applicationEnvironment).bind("user", Bindable.ofInstance(user1));
System.out.println(user1);
}

@Data
static class User{
private String lastName;
private String middleName;
private String firstName;
}
}

step4.properties :

1
2
3
user.first-name=dai
user.middle_name=da
user.lastName=wang
1
2
A39ConfigurationProperties原理.User(lastName=wang, middleName=da, firstName=dai)
A39ConfigurationProperties原理.User(lastName=wang, middleName=da, firstName=dai)

绑定配置信息到 SpringApplication

在第六步中,绑定 spring.main 前缀的配置信息到 SpringApplication 对象也是利用了 Binder。

假设 step6.properties 配置文件的信息如下:

1
2
spring.main.banner-mode=off
spring.main.lazy-initialization=true
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
/**
* @version v1.0
* @ClassName: A39run方法6步 绑定spring.main 前缀的配置信息到 SpringApplication 对象
* @Description: 读取配置文件并绑定到SpringApplication
* 通过@ConfigurationProperties 的原理Binder 将配置文件中的属性绑定到 SpringApplication
* @Author: 呆大王
* @Date: 2024/9/9
*/
public class A39run方法6步 {
public static void main(String[] args) throws IOException, IllegalAccessException, NoSuchFieldException {
ApplicationEnvironment applicationEnvironment = new ApplicationEnvironment();
applicationEnvironment.getPropertySources()
.addLast(new ResourcePropertySource("step6",new ClassPathResource("step6.properties")));

SpringApplication application = new SpringApplication();
Class<? extends SpringApplication> clazz = application.getClass();
Field bannerMode = clazz.getDeclaredField("bannerMode");
bannerMode.setAccessible(Boolean.TRUE);
Field lazyInitialization = clazz.getDeclaredField("lazyInitialization");
lazyInitialization.setAccessible(Boolean.TRUE);

//绑定前
System.out.println(bannerMode.get(application));
System.out.println(lazyInitialization.get(application));

// 绑定
Binder.get(applicationEnvironment).bind("spring.main", Bindable.ofInstance(application));

//绑定后
System.out.println(bannerMode.get(application));
System.out.println(lazyInitialization.get(application));
}
}
1
2
3
4
CONSOLE
false
OFF
true

第七步 打印 Banner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @version v1.0
* @ClassName: A39run方法7步
* @Description:
* @Author: 呆大王
* @Date: 2024/9/9
*/
public class A39run方法7步 {
public static void main(String[] args) {
ApplicationEnvironment applicationEnvironment = new ApplicationEnvironment();
SpringApplicationBannerPrinter springApplicationBannerPrinter = new SpringApplicationBannerPrinter(
new DefaultResourceLoader(),
new SpringBootBanner()
);

printer.print(env, A39run方法7步.class, System.out); }
}

除此之外还可以自定义文字和图片 Banner,文字 Banner 的文件类型需要是 txt,图片 Banner 的文件类型需要是 gif。

文字:

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

// 测试文字 banner
env.getPropertySources().addLast(new MapPropertySource(
"custom",
Collections.singletonMap("spring.banner.location", "banner1.txt")
));
printer.print(env, A39run方法7步.class, System.out);
}

文字 Banner 可以从 网站 上自定义.

获取 Spring 或 SpringBoot 的版本号可以使用:

1
2
System.out.println("SpringBoot: " + SpringBootVersion.getVersion());
System.out.println("Spring: " + SpringVersion.getVersion());

步骤总结

  1. 得到 SpringApplicationRunListeners 事件发布器
  • 发布 starting 事件 1️⃣
  1. 将 main 方法的参数 封装ApplicationArguments 对象(此对象将参数分为两类a选项参数 –开头.可添加到命令行 b非选项参数)
  2. 准备 environment 并添加选项参数(命令行参数)
  3. 松绑定 , ConfigurationPropertySources 处理
  • 发布 environmentPrepared 事件 ,表示environment已经准备 2️⃣
  1. 通过spring.factories 里找到事件监听器 EnvironmentPostProcessorApplicationListener , 调用environmentPrepared 方法增强environment,例如随机数的源和读取 application.properties 的源
  2. 绑定 spring.main 前缀的 key 到 SpringApplication 对象
  3. 打印 Banner
  4. 创建容器
  5. 准备容器,应用初始化器增强 ApplicationContext
  • 发布 contextPrepared 事件,表示容器创建好并调用初始化器 3️⃣
  1. 获取所有 BeanDefinition 源(xml、配置类、组件扫描) , 加载 BeanDefinition 到 ApplicationContext 容器
  • 发布 contextLoaded 事件4️⃣
  1. refresh 容器,调用 Bean 工厂后置处理器 、bean 后置处理器、初始化单例 bean
  • 发布 started 事件,表示 ApplicationContext 准备好了 5️⃣
  1. 调用实现 ApplicationRunner 或者 CommandLineRunner 的 bean
  • 发布 running 事件 6️⃣
  • 这其中有异常,发布 Application Failed 事件 7️⃣

步骤对应的代码:

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
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
//1.得到 SpringApplicationRunListeners 事件发布器
SpringApplicationRunListeners listeners = getRunListeners(args);
//发布 starting 事件 1️⃣
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//2.将 main 方法的参数 封装ApplicationArguments 对象(此对象将参数分为两类a选项参数 --开头.可添加到命令行 b非选项参数)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//进入prepareEnvironment方法
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
//7.打印 Banner
Banner printedBanner = printBanner(environment);
//8.创建容器
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//进入prepareContext
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//11.refresh 容器,调用 Bean 工厂后置处理器 、bean 后置处理器、初始化单例 bean
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
//发布 started 事件
listeners.started(context, timeTakenToStartup);
//12.调用实现 ApplicationRunner 或者 CommandLineRunner 的 bean
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
//发布 running 事件
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
//这其中有异常,发布 Application Failed 事件
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}

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
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
//3.准备 environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 并添加选项参数(命令行参数)
configureEnvironment(environment, applicationArguments.getSourceArgs());
//4.松绑定
ConfigurationPropertySources.attach(environment);
//发布 environmentPrepared 事件 ,表示environment已经准备
//5.通过spring.factories 里找到事件监听器 EnvironmentPostProcessorApplicationListener ,
//调用environmentPrepared 方法增强environment,例如随机数的源和读取 application.properties 的源
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
//6.绑定 spring.main 前缀的 key 到 SpringApplication 对象
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
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
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
//9.准备容器,应用初始化器增强 ApplicationContext
applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
context.addBeanFactoryPostProcessor(new SpringApplication.PropertySourceOrderingBeanFactoryPostProcessor(context));
// Load the sources
//10.获取所有 BeanDefinition 源(xml、配置类、组件扫描) , 加载 BeanDefinition 到 ApplicationContext 容器
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
//发布 contextLoaded 事件
listeners.contextLoaded(context);
}

40.Tomcat 内嵌容器

41.自动配置

41.1 自动配置原理

创建以下类:

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
/**
* 模拟第三方配置类
*/
static class AutoConfiguration1{
@Bean
public Bean1 bean1() {
return new Bean1();
}
}
@ToString
@NoArgsConstructor
@AllArgsConstructor
static class Bean1 {
private String name;
}

/**
* 模拟第三方配置类
*/
static class AutoConfiguration2 {
@Bean
public Bean2 bean2() {
return new Bean2();
}
}

static class Bean2 {
}

其中 AutoConfiguration1 和 AutoConfiguration2 用来模拟第三方配置类,注意它们并没有被 @Configuration 注解标记,因此在未进行其他操作时,不会被添加到 Spring 容器中。

然后编写自己的配置类,使用 @Import 注解将第三方配置类添加到 Spring 容器中:

1
2
3
4
5
6
@Configuration
//bean1和bean2可以通过@Import注解注入
@Import({AutoConfiguration1.class,AutoConfiguration2.class})
static class Config {

}
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config",Config.class);
context.registerBean(ConfigurationClassPostProcessor.class);

context.refresh();

for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
1
2
3
4
5
6
myAutoConfiguration
org.springframework.context.annotation.ConfigurationClassPostProcessor
org.springframework.boot.A41自动装配.A41$AutoConfiguration1
bean1
org.springframework.boot.A41自动装配.A41$AutoConfiguration2
bean2

@Improt 的 ImportSelector

如果有多个第三方配置类,难不成到一个个地导入?

点击进入 @Import

可以使用 ImportSelector,获取需要自动装配的 Bean 的全限定类名数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
//bean1和bean2可以通过@Import注解注入
//@Import({AutoConfiguration1.class,AutoConfiguration2.class})
@Import(MyImportSelector.class)
static class Config {

}

static class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//将类名写死在代码里
return new String[]{
AutoConfiguration1.class.getName(),AutoConfiguration2.class.getName()
};
}
}

如果 selectImports() 方法返回的全限定类名可以从文件中读取,就更方便了。

在当前项目的类路径下创建 META-INF/spring.factories 文件,约定一个 key,对应的 value 即为需要指定装配的 Bean:

1
2
3
4
5
# 内部类作为 key 时,最后以 $ 符号分割
# 每行的结尾用\表示和下一行 的内容 拼接为同一行
org.springframework.boot.A41自动装配.A41$MyImportSelector=\
org.springframework.boot.A41自动装配.A41.AutoConfiguration1,\
org.springframework.boot.A41自动装配.A41.AutoConfiguration2

修改 ImportSelector 的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
    static class MyImportSelector implements ImportSelector {

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//将类名写死在代码里
// return new String[]{AutoConfiguration1.class.getName(),AutoConfiguration2.class.getName()};
List<String> names = SpringFactoriesLoader.loadFactoryNames(
MyImportSelector.class, null
);
return names.toArray(new String[0]);
}
}

运行 main() 方法后,控制台打印出同样的结果。

SpringFactoriesLoader.loadFactoryNames() 不仅只扫描当前项目类型路径下

的 META-INF/spring.factories 文件,

而是会扫描包括 Jar 包里类路径下的 META-INF/spring.factories 文件。

针对 SpringBoot 来说,自动装配的 Bean 使用以下语句加载:

1
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, null);

SpringBoot2.7 及其以后版本的自动装配

在 SpringBoot2.7 及其以后版本,SpringBoot 不在读取 META-INF/spring.factories文件中的 key

为 org.springframework.boot.autoconfigure.EnableAutoConfiguration的 values 来实现自动装配.

为了更贴合 SPI 机制,SpringBoot 将读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的内容,

该文件每一行都表示需要自动装配的 Bean 的全限定类名,可以使用 #作为注释,其加载方式使用:

1
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader());

其中 AutoConfiguration是一个注解,全限定类名为 <font style="background-color:rgba(255, 255, 255, 0);">org.springframework.boot.autoconfigure.AutoConfiguration</font>

也就是说可以自定义注解,创建自定义 META-INF/spring/full-qualified-annotation-name.imports文件,

在文件里声明需要自动装配的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package dai.dawang.a41;
/**
* @version v1.0
* @ClassName: MyAutoConfiguration
* @Description:
* @Author: 呆大王
* @Date: 2024/9/13
*/

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutoConfiguration {
}
1
2
3
4
5
public class A41 {

static class Bean3 {
}
}

创建 META-INF/spring/indi.mofan.a41.MyAutoConfiguration.imports 文件:

1
dai.dawang.a41.A41$Bean3

修改 selectImports() 方法实现逻辑:

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
/**
* 本项目配置类
*/
@Configuration
//bean1和bean2可以通过两种方式注入
//方式一 @Import注解注入
//@Import({AutoConfiguration1.class,AutoConfiguration2.class})
//方式二 通过实现ImportSelector接口,MyImportSelector实现ImportSelector
@Import(MyImportSelector.class)
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1("本项目");
}
}

static class MyImportSelector implements ImportSelector {

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//将类名写死在代码里
// return new String[]{AutoConfiguration1.class.getName(),AutoConfiguration2.class.getName()};
List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, null).stream().forEach(System.out::println);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
//读取新自动装配文件
ImportCandidates load = ImportCandidates.load(MyAutoConfiguration.class, MyAutoConfiguration.class.getClassLoader());
//这里的namse是不可变的,
//SpringFactoriesLoader.loadFactoryNames源码里有 return Collections.unmodifiableMap(result);
//返回一个不可变的集合 ,所以需要转换为可变数组
names = new ArrayList<>(names);
load.forEach(names::add);
return names.toArray(new String[0]);
}
}

运行 main() 方法后,Spring 容器中的 Bean 多了 一个:

1
dai.dawang.a41.A41$Bean3

补充 @Import 的@Improt 的 ImportBeanDefinitionRegistrar

我们刚刚了解的 @Import 的 ImportSelector 属性和 实现类 <font style="background-color:rgba(255, 255, 255, 0);">DeferredImportSelector</font>

定义了冲突的 Bean

第三方装配了 Bean1:

1
2
3
4
5
6
7
8
9
/**
* 模拟第三方配置类
*/
static class AutoConfiguration1 {
@Bean
public Bean1 bean1() {
return new Bean1("第三方");
}
}

用户又自行定义了 Bean1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 本项目配置类
*/
@Configuration
//bean1和bean2可以通过两种方式注入
//方式一 @Import注解注入
//@Import({AutoConfiguration1.class,AutoConfiguration2.class})
//方式二 通过实现ImportSelector接口
@Import(MyImportSelector.class)
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1("本项目");
}
}

修改测试的 main() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
context.registerBean(ConfigurationClassPostProcessor.class);
//将默认 允许覆盖 设置为 不允许
// context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);


context.refresh();

//第三方 和 本地 配置同时注册bean1
System.out.println(context.getBean("bean1", Bean1.class));
//发现打印A41.Bean1(name=本项目)
//是因为:在加载myAutoConfiguration类时, @Import优先于 @Bean,所以先加载第三方才加载本地,后者覆盖前者,使得用户自定义的 Bean 生效
//在DefaultListableBeanFactory里 private boolean allowBeanDefinitionOverriding = true; 表示默认 允许覆盖

Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);

}
1
A41.Bean1(name=本项目)

用户自行定义的 Bean 生效了,这是因为:@Import 导入的 Bean 先于配置类中 @Bean 定义的 Bean 执行,后者覆盖前者,使得用户自定义的 Bean 生效。

但在 SpringBoot 中不是这样的,当后续添加的 Bean 想覆盖先前添加的 Bean,会出现错误。模拟 SpringBoot 的设置:

1
2
3
4
5
6
7
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
// 默认是 true,SpringBoot 修改为 false,使得无法进行覆盖
context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);

// --snip--
}
1
Exception in thread "main" org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'bean1' defined in dai.dawang.a41.A41$Config: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=config; factoryMethodName=bean1; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in dai.dawang.a41.A41$Config] for bean 'bean1' since there is already [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=dai.dawang.a41.A41$AutoConfiguration1; factoryMethodName=bean1; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [dai/dawang/a41/A41$AutoConfiguration1.class]] bound.

那这样是合理的吗?

显然不是。比如 SpringBoot 默认的数据连接池是 Hikari,如果用户想换成 Druid,岂不是做不到?

实际情况下是能做到的,这又是怎么做到的呢?

首先需要使用户的配置类中定义的 Bean 先于 @Import 导入的 Bean 添加到 Spring 容器中,

只需将选择器 MyImportSelector 实现的 ImportSelector 接口更换成其子接口 DeferredImportSelector 即可:

1
2
3
static class MyImportSelector implements DeferredImportSelector {
// --snip--
}
1
Exception in thread "main" org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'bean1' defined in class path resource [dai/dawang/a41/A41$AutoConfiguration1.class]: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=dai.dawang.a41.A41$AutoConfiguration1; factoryMethodName=bean1; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [dai/dawang/a41/A41$AutoConfiguration1.class]] for bean 'bean1' since there is already [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=config; factoryMethodName=bean1; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in dai.dawang.a41.A41$Config] bound.

尽管还是出现了异常,但异常信息中显示的是在配置类定义的 Bean 已存在,第三方装配的 Bean 无法再添加,这表明 Bean 的添加顺序修改成功。

最后在第三方定义的 Bean 上添加 @ConditionalOnMissingBean 注解,表示容器中存在同名的 Bean 时忽略该 Bean 的添加:

1
2
3
4
5
6
7
8
9
10
/**
* 模拟第三方配置类
*/
static class AutoConfiguration1 {
@Bean
@ConditionalOnMissingBean//仅当BeanFactory中没有满足指定要求的bean时才匹配的条件
public Bean1 bean1() {
return new Bean1("第三方");
}
}
1
A41.Bean1(name=本项目)

那么 <font style="background-color:rgba(255, 255, 255, 0);">ImportBeanDefinitionRegistrar</font>属性如何使用呢?

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
/**
* @version v1.0
* @ClassName: ImportBeanDefinitionRegistrar属性
* @Description: Import注解如何使用 ImportBeanDefinitionRegistrar属性
* @Author: 呆大王
* @Date: 2024/9/13
*/
public class ImportBeanDefinitionRegistrar属性 {

public static void main(String[] args) {
// AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
GenericApplicationContext context = new GenericApplicationContext();
// AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());

context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean("config", Config.class);
context.refresh();

Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
System.out.println(context.getBean(User.class));
}

@Configuration
@Import({MyImportBeanDefinitionRegistrar.class})
static class Config {

}

static class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 构建 BeanDefinition
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(User.class)
.addPropertyValue("name", "daidawang")
.addPropertyValue("age", 24)
.getBeanDefinition();
// 注册构建好的 BeanDefinition
registry.registerBeanDefinition("user", beanDefinition);
}
}

@Setter
@ToString
static class User {
private String name;
private int age;
}
}
1
2
3
4
org.springframework.context.annotation.ConfigurationClassPostProcessor
config
user
ImportBeanDefinitionRegistrar属性.User(name=daidawng, age=24)

41.2 Aop 自动配置

<font style="color:#DF2A3F;">AopAutoConfiguration</font>类完成 AOP 自动装配,

<font style="color:#DF2A3F;">AopAutoConfiguration</font>类通过<font style="color:#DF2A3F;">@Conditionalxxx</font>注解的判断后,

都会往 <font style="color:#DF2A3F;">BeanDefinition注册表: BeanDefinitionRegistry </font> 里添加 <font style="color:#DF2A3F;">AnnotationAwareAspectJAutoProxyCreator</font> 的 Bean 完成AOP 自动装配。

确保当前模块导入:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</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
28
29
30
31
32
33
34
35
/**
* @version v1.0
* @ClassName: AopAuto
* @Description: aop自动配置
* @Author: 呆大王
* @Date: 2024/9/14
*/
public class AopAuto {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
//在给定的注册表中注册所有相关的注释后处理器。
AnnotationConfigUtils.registerAnnotationConfigProcessors(
context.getDefaultListableBeanFactory()
);

context.registerBean(Config.class);
context.refresh();

Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);

}

@Configuration
@Import(MyImportSelector.class)
static class Config{}

static class MyImportSelector implements DeferredImportSelector {

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{AopAutoConfiguration.class.getName()};
}
}
}

1
2
3
4
5
6
7
8
9
10
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
dai.dawang.a41.aop自动配置.AopAuto$Config
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration$CglibAutoProxyConfiguration
org.springframework.aop.config.internalAutoProxyCreator
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

打印内容以 ai.dawang.a41.aop自动配置.AopAuto$Config

为分割线,上方是添加的一些后置处理器 ,下方是 AOP 自动装配添加的 Bean

点击进入 AopAutoConfiguration

类上添加注解 <font style="color:#DF2A3F;">@ConditionOnProperty</font>的配置信息: 如果文件中存在 前缀 为 <font style="color:#DF2A3F;">spring.aop</font> ,名称 为 <font style="color:#DF2A3F;">auto</font>的 key,且对应的 value 是 <font style="color:#DF2A3F;">true</font>时,配置类 <font style="color:#DF2A3F;">AopAutoConfiguration</font>生效, 属性 <font style="color:#DF2A3F;">matchIfMissing</font>表示:如果配置文件中未显式配置,该配置类依然生效。

不使用配置文件,使用 <font style="color:#DF2A3F;">StandardEnvironment</font>指定 <font style="color:#DF2A3F;">spring.aop.auto</font>的值为 <font style="color:#DF2A3F;">false</font>:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
//ApplicationEnvironment是StandardEnvironment的子类
StandardEnvironment environment = new StandardEnvironment();
//添加一个 简单命令行属性资源, 主要参数是main方法的参数args ,及选项参数需要以--开头
//AopAutoConfiguration类上注解@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
environment.getPropertySources().addLast(new SimpleCommandLinePropertySource("--spring.aop.auto=false"));
context.setEnvironment(environment);

//--snip--
}}
1
2
3
4
5
6
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
dai.dawang.a41.aop自动配置.AopAuto$Config

如果 spring.aop.auto 的值是 true,又会成功添加上 AOP 自动装配的 Bean。

再看 AopAutoConfiguration的内部类;

其内部存在两个类:AspectJAutoProxyingConfiguration 和 ClassProxyingConfiguration。

使用了 @ConditionalOnClass 注解判断 Advice.class 存在时,AspectJAutoProxyingConfiguration 生效;

使用 @ConditionalOnMissingClass 注解判断 org.aspectj.weaver.Advice 不存在时,ClassProxyingConfiguration 生效。

由于先前导入了 spring-boot-starter-aop 依赖,Advice.class 是存在的,AspectJAutoProxyingConfiguration 将生效。

AspectJAutoProxyingConfiguration 内部又有两个配置类:

这两个配置类通过使用 @ConditionalOnProperty 注解判断配置文件中是否存在 spring.aop.proxy-target-class 配置来让对应的配置类生效。

由于并未显式配置,因此 CglibAutoProxyConfiguration 将生效。

无论哪个配置类生效,它们都被 @EnableAspectJAutoProxy 标记,这个注解相当于是添加了些配置的 @Import 注解:

所以 @EnableAspectJAutoProxy 作用是向 Spring 容器中添加 AspectJAutoProxyRegistrar 类型的 Bean。

AspectJAutoProxyRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,可以使用编程的方式来注册一些 Bean:

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary() 方法是注册 Bean 的主要逻辑:

最终注册了 <font style="color:#DF2A3F;">AnnotationAwareAspectJAutoProxyCreator</font>。

使用打印的内容 <font style="color:#DF2A3F;">org.springframework.aop.config.internalAutoProxyCreator</font>作为名称,获取 <font style="color:#DF2A3F;">AnnotationAwareAspectJAutoProxyCreator</font> 类型的 Bean,并查看<font style="color:#DF2A3F;">proxyTargetClass</font>属性的值:

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
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
//ApplicationEnvironment是StandardEnvironment的子类
StandardEnvironment environment = new StandardEnvironment();
//添加一个 简单命令行属性资源, 主要参数是main方法的参数args ,及选项参数需要以--开头
//AopAutoConfiguration类上注解@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
environment.getPropertySources().addLast(new SimpleCommandLinePropertySource("--spring.aop.auto=true"));
context.setEnvironment(environment);

//在给定的注册表中注册所有相关的注释后处理器。
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());

context.registerBean(Config.class);
context.refresh();

Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>");

/**,
* @EnableAspectJAutoProxy(proxyTargetClass = true) 的作用是:
* 根据给定的@EnableAspectJ AutoProxy注释,在当前BeanDefinition注册表中适当注册AnnotationAwareAspectJAutoProxyCreator。
*
* 证明是:
* 点击进入AopAutoConfiguration,点击进入@EnableAspectJAutoProxy(proxyTargetClass = true)
* 点击进入@Import(AspectJAutoProxyRegistrar.class),
* 点击进入opConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
* 点击进入return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, (Object)null);
* 看到return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
* 注意参数AnnotationAwareAspectJAutoProxyCreator类
* AnnotationAwareAspectJAutoProxyCreator是BeanPostProcessor
*/
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(
"org.springframework.aop.config.internalAutoProxyCreator", AnnotationAwareAspectJAutoProxyCreator.class);
System.out.println(creator.isProxyTargetClass());
}
1
2
>>>>>>>>>>>>>>>>>>>>>>
true

还记得 <font style="color:#DF2A3F;">proxyTargetClass</font>属性是做什么的吗?

请参考 : 黑马四十九讲AOP 6.3 .spring 根据什么信息选择不同的代理? 章节

41.3 数据库相关的自动配置

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>

DataSource 自动配置

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
/**
* @version v1.0
* @ClassName: DataSourceAuto
* @Description: 数据库相关的自动配置
* @Author: 呆大王
* @Date: 2024/9/14
*/
public class DataSourceAuto {
@Configuration
@Import(MyImportSelector.class)
static class Config{

}

static class MyImportSelector implements ImportSelector {

public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
// 数据源自动配置类
DataSourceAutoConfiguration.class.getName(),
// mybatis自动配置类
MybatisAutoConfiguration.class.getName(),
// 事务自动配置类(事务回滚,提交)
DataSourceTransactionManagerAutoConfiguration.class.getName(),
// 事务自动配置类(事务切面,切点,事务控制)
TransactionAutoConfiguration.class.getName()
};
}
}
}
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
/**
* @version v1.0
* @ClassName: DataSourceAuto
* @Description: 数据库相关的自动配置
* @Author: 呆大王
* @Date: 2024/9/14
*/
public class DataSourceAuto {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.registerBean(Config.class);

StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addLast(
new SimpleCommandLinePropertySource(
"--spring.datasource.url=jdbc:mysql://localhost:3306/test",
"--spring.datasource.username=root",
"--spring.datasource.password=root"
)
);
context.setEnvironment(environment);

context.refresh();
HashMap<String, List<String>> map = new HashMap<>();
for (String name : context.getBeanDefinitionNames()) {
String resourceDescription = context.getBeanDefinition(name).getResourceDescription();
map.computeIfAbsent(resourceDescription, k -> new ArrayList<>()).add(name);
// System.out.println(name + " 来源: " + resourceDescription);
}

map.forEach((k, v) -> System.out.println("来源 "+k + "= " +"名称 :"+ v));
}

}
1
来源 class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]= 名称 :[jdbcConnectionDetailsHikariBeanPostProcessor, dataSource]

名叫 <font style="color:#DF2A3F;">dataSource</font>的 Bean 的来源是 <font style="color:#DF2A3F;">DataSourceConfiguration</font>,而不是 <font style="color:#DF2A3F;">DataSourceAutoConfiguration</font> 呢?

进入 DataSourceAutoConfiguration源码,

内部类 EmbeddedDatabaseConfiguration 和 PooledDataSourceConfiguration 都被 @Conditional注解标记,

当项目支持内嵌数据源时, EmbeddedDatabaseConfiguration 生效

当项目支持数据库连接池的数据源时,PooledDataSourceConfiguration生效

SpringBoot 默认数据库连接池是 Hikari,

所以PooledDataSourceConfiguration生效,

通过 @Import导入一系列 Bean 都是 DataSourceConfiguration的内部类,

所以 DataSource的 Bean 来源是 DataSourceConfiguration。

由于导入了 mybatis-spring-boot-starter,其内部依赖 mybatis-spring-boot-jdbc,而它又依赖了 HikariCP,因此最终数据库连接池 Hikari 生效:

在 <font style="color:#DF2A3F;">Hikari#dataSource()</font> 方法中,接受一个 <font style="color:#DF2A3F;">DataSourceProperties</font> 类型的参数,这要求 Spring 容器中存在 <font style="color:#DF2A3F;">DataSourceProperties</font> 类型的 Bean。

在最初的 <font style="color:#DF2A3F;">DataSourceAutoConfiguration</font> 自动配置类上有个 @<font style="color:#DF2A3F;">EnableConfigurationProperties</font> 注解,它将 <font style="color:#DF2A3F;">DataSourceProperties</font> 添加到容器中:

在 <font style="color:#DF2A3F;">DataSourceProperties</font> 中会绑定配置文件中以 <font style="color:#DF2A3F;">spring.datasource</font> 为前缀的配置:

获取 <font style="color:#DF2A3F;">DataSourceProperties</font> 类型的 Bean,并打印其 url、username 和 password:

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
    public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.registerBean(Config.class);

StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addLast(
new SimpleCommandLinePropertySource(
"--spring.datasource.url=jdbc:mysql://localhost:3306/test",
"--spring.datasource.username=root",
"--spring.datasource.password=root"
)
);
context.setEnvironment(environment);

context.refresh();
HashMap<String, List<String>> map = new HashMap<>();
for (String name : context.getBeanDefinitionNames()) {
String resourceDescription = context.getBeanDefinition(name).getResourceDescription();
map.computeIfAbsent(resourceDescription, k -> new ArrayList<>()).add(name);
// System.out.println(name + " 来源: " + resourceDescription);
}

//map.forEach((k, v) -> System.out.println("来源 "+k + "= " +"名称 :"+ v));

DataSourceProperties bean = context.getBean(DataSourceProperties.class);
System.out.println(bean.getUrl());
System.out.println(bean.getUsername());
System.out.println(bean.getPassword());
}
1
2
3
jdbc:mysql://localhost:3306/test
root
root

Mybatis 自动配置

进入 <font style="color:#DF2A3F;">MybatisAutoConfiguration</font>:

MybatisAutoConfiguration 的条件注解

<font style="color:#DF2A3F;">MybatisAutoConfiguration</font> 生效的条件有两个:

  • 类路径下有SqlSessionFactory, SqlSessionFactoryBean
  • Spring 容器中 有且仅有 一个 DataSource的 bean添加了 ]

同时添加了 MybatisProperties 类型的 Bean 到 Spring 容器中,并与配置文件中以 mybatis 为前缀的信息绑定。

<font style="color:#DF2A3F;">@AutoConfigureAfter</font> 注解指定了当前自动配置类在 DataSourceAutoConfiguration 和 MybatisLanguageDriverAutoConfiguration 两个自动配置类解析完成之后再解析。


接下来是 <font style="color:#DF2A3F;">sqlSessionFactory()</font>方法:

依赖 Spring 容器的 <font style="color:#DF2A3F;">DataSource</font>,当容器中不存在 SqlSessionFactory 时,将其添加到 Spring 容器中。


SqlSessionTemplate 的作用

然后是 <font style="color:#DF2A3F;">sqlSessionTemplate()</font> 方法,它与添加 SqlSessionFactory 到 Spring 容器的逻辑一样:

SqlSessionTemplate也是 SqlSession的实现,它提供与当前线程绑定的 SqlSession。

多个方法调用,如果它们来自同一个线程,那么获取的 SqlSession对象是同一个。

这也是 SqlSessionTemplate与 DefultSqlSession的区别。

在 Mybatis 中,MapperFactoryBean将接口转为对象,核心是 getObjext()方法,方法使用的是 SqlSessionTemplate


内部类MapperScannerRegistrarNotFoundConfiguration

@ConditionalOnMissingBean 判断 Spring 容器中缺失 MapperFactoryBean 和 MapperScannerConfigurer 时,该配置类生效。

生效时利用 @Import 导入 AutoConfiguredMapperScannerRegistrar:

AutoConfiguredMapperScannerRegistrar 实现了 <font style="color:#DF2A3F;">ImportBeanDefinitionRegistrar</font> 接口,允许通过编程的方式加 Bean 添加到 Spring 容器中(详细请 向上查找 : 41.1 补充 @Import 的@Improt 的 ImportBeanDefinitionRegistrar),而这里是去扫描 Mapper 接口,将其转换为对象添加到 Spring 容器中。

在 main 方法所在类的包路径下创建 mapper 包,并创建 2 个带@Mapper 一个不带共计三个类:

1
2
3
4
5
6
7
8
9
10
@Mapper
public interface Mapper1 {
}

@Mapper
public interface Mapper2 {
}

public interface Mapper3 {
}

运行 main() 方法,查看 Mapper1 和 Mapper2 是否被添加到 Spring 容器中。

结果是否定的。因为 没有设置要扫描的包路径 。

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

String packageName = DataSourceAuto.class.getPackageName();
System.out.println("当前包名: " + packageName);
AutoConfigurationPackages.register(context.getDefaultListableBeanFactory(),
packageName);

context.refresh();

// --snip--
}
1
2
3
当前包名: dai.dawang.a41.数据库相关的自动配置
来源 file [D:\workspace\advanced-spring\boot\target\classes\dai\dawang\a41\数据库相关的自动配置\mapper\Mapper1.class]= 名称 :[mapper1]
来源 file [D:\workspace\advanced-spring\boot\target\classes\dai\dawang\a41\数据库相关的自动配置\mapper\Mapper2.class]= 名称 :[mapper2]

@MapperScan 注解与 MybatisAutoConfiguration 在功能区别

  • <font style="color:#DF2A3F;">@MapeerScan</font> 可以指定扫描路径, 未指定时会把引导类范围内的所有接口当做 Mapper 接口;
  • <font style="color:#DF2A3F;">MybatisAutoConfiguration</font> 关注所有被 @Mapper 注解标记的类并忽略 未被标记的接口。

事务自动配置

事务自动配置与 DataSourceTransactionManagerAutoConfiguration、TransactionAutoConfiguration 有关。

DataSourceTransactionManagerAutoConfiguration 配置了 DataSourceTransactionManager 用来执行事务的提交、回滚操作。

TransactionAutoConfiguration 在功能上对标 @EnableTransactionManagement,包含以下三个 Bean:

  • BeanFactoryTransactionAttributeSourceAdvisor:事务切面类,包含通知和切点
  • TransactionInterceptor:事务通知类,由它在目标方法调用前后加入事务操作
  • AnnotationTransactionAttributeSource:解析 @Transactional 及事务属性,还包含了切点功能

如果自定义了 DataSourceTransactionManager 或是在引导类加了 @EnableTransactionManagement,则以自定义为准。

MVC 自动配置

MVC 自动配置用到 4 个类:

  • 配置内嵌 Tomcat 服务器工厂:ServletWebServerFactoryAutoConfiguration
  • 配置 DispatchSerlet:DispatcherServletAutoConfiguration
  • 配置 WebMVC 各种组件:WebMvcAutoConfiguration
  • 配置 MVC 的错误处理: ErrorMvcAutoConfiguration
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
public class MvcAuto {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
context.registerBean(Config.class);
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
String source = context.getBeanDefinition(name).getResourceDescription();
if (source != null) {
System.out.println(name + " 来源:" + source);
}
}
context.close();
}

@Configuration
@Import(MyImportSelector.class)
static class Config {

}

static class MyImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
// 配置内嵌 Tomcat 服务器工厂
ServletWebServerFactoryAutoConfiguration.class.getName(),
// 配置 DispatcherServlet
DispatcherServletAutoConfiguration.class.getName(),
// 配置 WebMVC 各种组件
WebMvcAutoConfiguration.class.getName(),
// 配置 MVC 的错误处理
ErrorMvcAutoConfiguration.class.getName()
};
}
}
}

tomcatServletWebServerFactory: 来源ServletWebServerFactoryConfiguration$EmbeddedTomcat


来源 class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.class]= 名称 :[servletWebServerFactoryCustomizer, tomcatServletWebServerFactoryCustomizer]

分别表示 web 的扩展 与 tomcat 的扩展


来源 class path resource [org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration$DispatcherServletConfiguration.class]= 名称 :[dispatcherServlet]


来源 null = 名称 :[org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration]

自定义自动配置类

在 SpringBoot 自动配置自定义组件分为两步:

  1. 在类路径下自定义 META-INF/spring.factories文件,以 org.springframework.boot.autoconfigure.EnableAutoConfiguration 为 key,设置需要自动装配的自定义组件的全限定类名为 value
  2. 编写配置类,添加 @EnableAutoConfiguration注解

SpringBoot2.7.0 及其以后版本

在类路径下自定义 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,文件 每一行 表示需要自动装配的类的全限定类名,所以不要随便换行

42.条件装配底层

42.1@Conditional

在 SpringBoot 的自动配置中,经常看到 <font style="color:#DF2A3F;">@Conditional</font>注解,该注解可以按条件加载配置类

<font style="color:#DF2A3F;">@Conditional</font> 注解是通过指定的 Class 实现 Condition接口 进行判断

现在判断类路径下是否存在 org.springframework.boot.autoconfigure.jdbc.DataSourceProperties 来加载不同的配置类, 当存在 DataSourceProperties 时加载 AutoConfiguration1,反之加载 AutoConfiguration2。

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package dai.dawang.a42条件装配底层;

import dai.dawang.a41.自动配置原理.A41;
import org.springframework.context.annotation.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;

import java.util.Arrays;

/**
* @version v1.0
* @ClassName: a42
* @Description: 条件装配
* @Author: 呆大王
* @Date: 2024/9/19
*/
public class a42 {

public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(Config.class);
/* context.registerBean(AutoConfiguration1.class);
context.registerBean(AutoConfiguration2.class);*/
context.refresh();

Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
}

@Configuration
// @Import({AutoConfiguration1.class, AutoConfiguration2.class})
@Import(MyImportSelector.class)
static class Config {

}
static class MyImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{AutoConfiguration1.class.getName(), AutoConfiguration2.class.getName()};
}
}



static class MyCondition1 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return ClassUtils.isPresent("org.springframework.boot.autoconfigure.jdbc.DataSourceProperties", null);
}
}

static class MyCondition2 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return !ClassUtils.isPresent("org.springframework.boot.autoconfigure.jdbc.DataSourceProperties", null);
}
}

@Configuration
@Conditional(MyCondition1.class)
static class AutoConfiguration1 {
@Bean
public Bean1 bean1() {
return new Bean1("bean1");
}
}

@Configuration
@Conditional(MyCondition2.class)
static class AutoConfiguration2 {
@Bean
public Bean2 bean2() {
return new Bean2("bean2");
}
}

static class Bean1 {
private String name;

public Bean1(String name) {
this.name = name;
}
}

static class Bean2 {
private String name;

public Bean2(String name) {
this.name = name;
}
}
}

1
2
3
4
org.springframework.context.annotation.ConfigurationClassPostProcessor
dai.dawang.a42条件装配底层.a42$Config
dai.dawang.a42条件装配底层.a42$AutoConfiguration1
bean1

项目导入了,所以MyCondition1 返回 ture,所以 AutoConfiguration1 的@Conditional 生效,所以导入 bean1

1
2
3
4
5
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>

42.2@ConditionalOnXxx

@ConditionalOnXxx 注解的使用,这种注解是将某个 <font style="color:#DF2A3F;">@Conditional</font> 的判断进行了封装,比如 ConditionalOnClass 就是用于判断某个 Class 是否存在。

因此针对上文中的代码可以做出修改:

自定义 @ConditionalOnClass注解,填入需要判断的全限定类名和判断条件

移除模拟的第三方配置上的 @Conditional注解,而是使用自定义的 @ConditionalOnClass

重写 Condition#matches() 方法

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* @version v1.0
* @ClassName: a42ConditionalOnXxx注解
* @Description: @ConditionalOnXxx注解的原理
* @Author: 呆大王
* @Date: 2024/9/19
*/
public class a42ConditionalOnXxx注解 {

public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(Config.class);
/* context.registerBean(AutoConfiguration1.class);
context.registerBean(AutoConfiguration2.class);*/
context.refresh();

Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
}

@Configuration
// @Import({AutoConfiguration1.class, AutoConfiguration2.class})
@Import(MyImportSelector.class)
static class Config {

}

static class MyImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{AutoConfiguration1.class.getName(), AutoConfiguration2.class.getName()};
}
}


static class ConditionalOnBean implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
Object o = annotationAttributes.get("classNames");
boolean present =Boolean.FALSE;
if (ObjectUtil.isNotEmpty(o)) {
List<String> collect = Arrays.stream((String[]) o).toList();
for (String name : collect) {
present = ClassUtils.isPresent(name, null);
}
}
boolean exits = ((boolean) annotationAttributes.get("exits"));
return exits ? present : !present;
}
}


//创建自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ConditionalOnBean.class)
static @interface ConditionalOnClass {
boolean exits() default true; //true表示存在, false表示不存在

String[] classNames() default {};
}


@Configuration
@ConditionalOnClass(classNames = "org.springframework.boot.autoconfigure.jdbc.DataSourceProperties", exits = true)
static class AutoConfiguration1 {
@Bean
public Bean1 bean1() {
return new Bean1("bean1");
}
}

@Configuration
@ConditionalOnClass(classNames = "org.springframework.boot.autoconfigure.jdbc.DataSourceProperties", exits = false)
static class AutoConfiguration2 {
@Bean
public Bean2 bean2() {
return new Bean2("bean2");
}
}

static class Bean1 {
private String name;

public Bean1(String name) {
this.name = name;
}
}

static class Bean2 {
private String name;

public Bean2(String name) {
this.name = name;
}
}

}

由于引入了 <font style="background-color:rgba(255, 255, 255, 0);">mybatis-spring-boot-starte</font> 所以打印 bean1:

1
2
3
4
org.springframework.context.annotation.ConfigurationClassPostProcessor
dai.dawang.a42条件装配底层.a42ConditionalOnXxx注解$Config
dai.dawang.a42条件装配底层.a42ConditionalOnXxx注解$AutoConfiguration1
bean1