SpringBoot 启动配置原理

启动配置原理

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

点进启动类的run方法可以看到,在源码中先创建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);
}

创建SpringApplication对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//保存主配置类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判断当前是否一个web应用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//从类路径下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//从类路径下找到META‐INF/spring.factories配置的所有ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//从多个配置类中找到有main方法的主配置类
this.mainApplicationClass = deduceMainApplicationClass();
}

starter-1

starter-2

运行run方法

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
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//获取SpringApplicationRunListeners;从类路径下META‐INF/spring.factories
SpringApplicationRunListeners listeners = getRunListeners(args);
//回调所有的SpringApplicationRunListener.starting()方法
listeners.starting();
try {
//封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//准备环境
//创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//加载忽略Bean的配置信息
configureIgnoreBeanInfo(environment);
//输出图标
Banner printedBanner = printBanner(environment);
//创建IOC容器 ApplicationContext;决定创建web的ioc还是普通的ioc
context = createApplicationContext();
//出异常时作报告用
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备上下文环境;将environment保存到ioc中;
//applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
//回调所有的SpringApplicationRunListener的contextPrepared();
//prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);
//扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//所有的SpringApplicationRunListener回调started方法
listeners.started(context);
//从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
//ApplicationRunner先回调,CommandLineRunner再回调
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
//当IOC容器、environment初始化完成,run方法即将结束时回调listeners的running方法
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
//整个SpringBoot应用启动完成以后返回启动的ioc容器;
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
26
27
28
29
30
31
32
33
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//准备上下文环境;将environment保存到ioc中;
context.setEnvironment(environment);
postProcessApplicationContext(context);
//applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
applyInitializers(context);
//回调所有的SpringApplicationRunListener的contextPrepared();
listeners.contextPrepared(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 DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
//prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
listeners.contextLoaded(context);
}
1
2
3
4
5
6
7
8
9
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
//回调之前保存的所有的ApplicationContextInitializer的initialize方法
initializer.initialize(context);
}
}
1
2
3
4
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] {SpringApplication.class, String[].class};
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

配置在META-INF/spring.factoriesApplicationContextInitializerSpringApplicationRunListener

只需要放在ioc容器中 :ApplicationRunnerCommandLineRunner

starter-9

事件监听机制

ApplicationContextInitializer

1
2
3
4
5
6
public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer...initialize..."+applicationContext);
}
}

SpringApplicationRunListener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {

//必须有的构造器
public HelloSpringApplicationRunListener(SpringApplication application, String[] args){

}

@Override
public void starting() {
System.out.println("SpringApplicationRunListener...starting...");
}

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
Object o = environment.getSystemProperties().get("os.name");
System.out.println("SpringApplicationRunListener...environmentPrepared.."+o);
}

@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...contextPrepared...");
}

@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...contextLoaded...");
}

@Override
public void started(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...started...");
}
}

配置(META-INF/spring.factories\表示换行,,表示分隔

1
2
3
org.springframework.context.ApplicationContextInitializer=\ com.springboot.listener.HelloApplicationContextInitializer

org.springframework.boot.SpringApplicationRunListener=\ com.springboot.listener.HelloSpringApplicationRunListener

ApplicationRunner

1
2
3
4
5
6
7
@Component
public class HelloApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner...run....");
}
}

CommandLineRunner

1
2
3
4
5
6
7
@Component
public class HelloCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner...run..."+ Arrays.asList(args));
}
}
1
2
3
4
5
6
7
8
9
输出顺序:
SpringApplicationRunListener...starting...
SpringApplicationRunListener...environmentPrepared...
ApplicationContextInitializer...initialize...
SpringApplicationRunListener...contextPrepared...
SpringApplicationRunListener...contextLoaded...
ApplicationRunner...run....
CommandLineRunner...run...
SpringApplicationRunListener...started...

自定义starter

SpringBoot starter机制

SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。

starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。SpringBoot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。

SpringBoot提供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。

为什么要自定义starter

在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,麻烦至极。

如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

spring-boot-starter中集成了spring全家桶的许多依赖支持,如下:

starter-3

命名规范

启动器(starter),启动器模块是一个空 JAR 文件,仅提供辅助性依赖管理,这些依赖可能用于自动装配或者其他类库。

一个完整的Spring Boot Starter可能包含以下组件:

  • autoconfigure模块:包含自动配置的代码
  • starter模块:提供对autoconfigure模块的依赖,以及一些其它的依赖

starter-4

如果你不需要区分这两个概念的话,也可以将自动配置代码模块与依赖管理模块合并成一个模块。

官方命名空间:

– 前缀:“spring-boot-starter-

– 模式:spring-boot-starter-模块名

– 举例:spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc

推荐自定义命名空间:

– 后缀:“-spring-boot-starter

– 模式:模块-spring-boot-starter

– 举例:mybatis-spring-boot-starter

如何编写

1
2
3
4
5
6
7
@Configuration  //指定这个类是一个配置类
@ConditionalOnXXX //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter //指定自动配置类的顺序
@Bean //给容器中添加组件

@ConfigurationPropertie //结合相关xxxProperties类来绑定相关的配置
@EnableConfigurationProperties //让xxxProperties生效加入到容器中
1
2
3
4
5
6

自动配置类要能加载
将需要启动就加载的自动配置类,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

新建项目

新建一个工程名midkuro-spring-boot-startermaven空项目,只做pom.xml的依赖引入。

starter-5

新建一个工程名midkuro-spring-boot-starter-autoconfigurerSpring-boot项目

starter-6

并修改其midkuro-spring-boot-starter-autoconfigurer项目的pom.xml文件,引入spring-boot-starter依赖

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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>cn.midkuro.com</groupId>
<artifactId>midkuro-spring-boot-starter-autoconfigurer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>

<properties>
<maven-jar-plugin.version>2.6</maven-jar-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>

</project>

然后修改midkuro-spring-boot-starterpom.xml,引入相关依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>cn.midkuro.com</groupId>
<artifactId>midkuro-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>midkuro-spring-boot-starter</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>cn.midkuro.com</groupId>
<artifactId>midkuro-spring-boot-starter-autoconfigurer</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

</dependencies>
</project>

定义配置类

midkuro-spring-boot-starter-autoconfigurer工程中增加配置类

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
package cn.midkuro.com.autoconfigurer;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "mtex.hello")
public class HelloProperties {
private String prefix;
private String suffix;

public String getPrefix() {
return prefix;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}

public String getSuffix() {
return suffix;
}

public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package cn.midkuro.com.autoconfigurer;

public class HelloService {

private HelloProperties properties;

public HelloProperties getProperties() {
return properties;
}

public void setProperties(HelloProperties properties) {
this.properties = properties;
}

public String sayHello(String name) {
return properties.getPrefix() + "-" + name + "-" + properties.getSuffix();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package cn.midkuro.com.autoconfigurer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnWebApplication //只有在web环境中生效
@EnableConfigurationProperties(HelloProperties.class)

public class HelloServiceAutoConfiguration {

@Autowired
private HelloProperties properties;

@Bean
public HelloService helloService() {
HelloService helloService = new HelloService();
helloService.setProperties(properties);
return helloService;
}
}

starter-7

配置spring.factories

想要xxxAutoConfiguration配置默认生效,需要在META-INF/spring.factories增加启动类相关配置

1
2
3
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.midkuro.com.autoconfigurer.HelloServiceAutoConfiguration

\斜杠符号表示换行,多个类以,逗号分开,具体可以参考spring-boot-starter的配置文件:

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
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
...

测试

新建工程名midkuro-spring-boot-starter-testspring-boot项目,引入打包好的自定义starter依赖:

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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>cn.mastercom</groupId>
<artifactId>mtex-spring-boot-starter-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<maven-jar-plugin.version>2.6</maven-jar-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>


<dependencies>

<dependency>
<groupId>cn.midkuro.com</groupId>
<artifactId>midkuro-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

</project>

新建测试Controller

1
2
3
4
5
6
7
8
9
10
@RestController
public class HelloController {
@Autowired
private HelloService helloService;

@GetMapping("/sayHello")
public void sayHello(String name) {
helloService.sayHello(name);
}
}

添加配置到application.properties中:

1
2
mtex.hello.prefix=hello
mtex.hello.suffix=world

运行启动类SpringBootStarterTestApplication

1
2
3
4
5
6
7
8
9
10
11
package cn.midkuro.com;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootStarterTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootStarterTestApplication.class, args);
}
}

starter-8

最后更新: 2020年12月30日 09:22

原始链接: https://midkuro.gitee.io/2020/06/03/springboot-starter/

× 请我吃糖~
打赏二维码