SpringBoot启动配置原理
前言
上一篇:SpringBoot数据访问
下一篇:
一、配置原理
1.1 启动流程剖析
- 首先在springboot项目的
run()
上打个断点,跟着run方法一步步执行。
@SpringBootApplication
public class CodeApplication {
public static void main(String[] args) {
SpringApplication.run(CodeApplication.class, args);
}
}
- 在第一步它首先对参数进行了封装:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
- 然后创建了
SpringApplication
对象:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
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;然后保存起来
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListener
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//从多个配置类中找到有main方法的主配置类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
- 创建的目的就是为了将配置的所有初始化器以及listener拿到并保存起来.
- 创建完成之后执行run方法:
public ConfigurableApplicationContext run(String... args) {
//初始化操作
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
//获取SpringApplicationRunListeners;从类路径下META-INF/spring.factories
SpringApplicationRunListeners listeners = this.getRunListeners(args);
//回调所有的获取SpringApplicationRunListener.starting()方法
listeners.starting();
Collection exceptionReporters;
try {
//封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//准备环境-----01
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
//在控制台打印banner图标
Banner printedBanner = this.printBanner(environment);
//创建ApplicationContext;决定创建web的ioc还是普通的ioc
context = this.createApplicationContext();
//从类路径下META‐INF/spring.factories获取SpringBootExceptionReporter,原理同2中获取ApplicationContextInitializer和ApplicationListener
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
//准备上下文环境;将environment保存到ioc中 ----02
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat)
//具体见https://blog.csdn.net/qq_33034955/article/details/105787420的嵌入式servlet修改原理
//扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
this.refreshContext(context);
//空方法
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
//调用所有SpringApplicationRunListener的started方法
listeners.started(context);
//从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调-----03
//ApplicationRunner先回调,CommandLineRunner再回调
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
//调用所有SpringApplicationRunListener的running方法
listeners.running(context);
//整个SpringBoot应用启动完成以后返回启动的ioc容器;
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
-
上述子方法:
- 准备环境—01
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { //获取环境参数 ConfigurableEnvironment environment = this.getOrCreateEnvironment(); //配置环境参数 this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs()); //执行 ConfigurationPropertySources.attach((Environment)environment); 创建环境完成后回调environmentPrepared();表示环境准备完成 listeners.environmentPrepared((ConfigurableEnvironment)environment); //绑定环境参数 this.bindToSpringApplication((ConfigurableEnvironment)environment); if (!this.isCustomEnvironment) { environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass()); } ConfigurationPropertySources.attach((Environment)environment); return (ConfigurableEnvironment)environment; }
- 准备上下文环境----02
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //将创建好的环境放到ioc容器中 context.setEnvironment(environment); //注册 this.postProcessApplicationContext(context); //获取所有的初始化器然后执行它的initializer() this.applyInitializers(context); //回调所有的SpringApplicationRunListener的contextPrepared(); listeners.contextPrepared(context); if (this.logStartupInfo) { this.logStartupInfo(context.getParent() == null); this.logStartupProfileInfo(context); } //注册bean 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()); } Set<Object> sources = this.getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); this.load(context, sources.toArray(new Object[0])); //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded(); listeners.contextLoaded(context); }
- 从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调—03
private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); Iterator var4 = (new LinkedHashSet(runners)).iterator(); while(var4.hasNext()) { Object runner = var4.next(); if (runner instanceof ApplicationRunner) { this.callRunner((ApplicationRunner)runner, args); } if (runner instanceof CommandLineRunner) { this.callRunner((CommandLineRunner)runner, args); } } }
-
至此应用启动完成
1.2 几个重要的事件回调机制
-
以下机制的回调调用均在上述代码中注释标注
-
配置在META-INF/spring.factories
ApplicationContextInitializer
SpringApplicationRunListener
-
放在ioc容器中
ApplicationRunner
CommandLineRunner
1.3 自定义测试
- 这几个监听器均为接口,只需要创建各自的实习类,并配置一下即可。
- ApplicationContextInitializer:
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext con) {
System.out.println("ApplicationContextInitializer....initialize...");
}
}
- SpringApplicationRunListener
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
public MySpringApplicationRunListener(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...");
}
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...running...");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("SpringApplicationRunListener...failed...");
}
- ApplicationRunner
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner...run...");
}
}
- CommandLineRunner
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner...run...");
}
}
- 配置文件:注意一定要是放在META-INF下的spring.properties文件
org.springframework.context.ApplicationContextInitializer=\
com.atguigu.code.listener.MyApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=\
com.atguigu.code.listener.MySpringApplicationRunListener
- 运行结果测试
二、自定义starter
2.1 自定义准备
-
模式:
- 启动器只用来做依赖导入
- 专门来写一个自动配置模块;
- 启动器依赖自动配置模块,项目中引入相应的starter就会引入启动器的所有传递依赖
-
启动器
- 启动器模块是一个空 JAR 文件,仅提供辅助性依赖管理,这些依赖可能用于自动 装配或者其他类库
-
命名规范:
-
官方命名
spring-boot-starter-模块名
eg:
spring-boot-starter-web
、spring-boot-starter-jdbc
、spring-boot-starter-thymeleaf
-
自定义命名
模块名-spring-boot-starter
eg:
mybatis-spring-boot-start
-
-
如何编写自动配置:
- 自动配置:
@Configuration //指定这个类是一个配置类 @ConditionalOnXXX //在指定条件成立的情况下自动配置类生效 @AutoConfigureAfter //指定自动配置类的顺序 @Bean //给容器中添加组件 @ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置 @EnableConfigurationProperties //让xxxProperties生效加入到容器中 public class XxxxAutoConfiguration {
- 自动配置类要能加载,将需要启动就加载的自动配置类,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
2.2 自定义案例
-
自定义案例可以分为如下三部分:
- 创建自动配置项目
- 创建starter中间项目
- 创建测试项目
-
创建自动配置项目:
- 自动配置类:
package com.atguigu.starterspringbootautoconfigure.autoconfig; 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(StarterProperties.class) //让配置类生效,(注入到容器中) public class StarterAutoConfiguration { private final StarterProperties starterProperties; public StarterAutoConfiguration(StarterProperties starterProperties) { this.starterProperties = starterProperties; } @Bean public HelloService helloService() { return new HelloService(); } public class HelloService { public String sayHello(String name) { return starterProperties.getPrefix() + name + starterProperties.getSuffix(); } } }
- 自动配置类:
package com.atguigu.starterspringbootautoconfigure.autoconfig; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "atguigu.hello") public class StarterProperties { 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; } }
- 约束:
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.atguigu</groupId> <artifactId>starter-spring-boot-autoconfigure</artifactId> <version>0.0.1-SNAPSHOT</version> <name>starter-spring-boot-autoconfigure</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--引入spring‐boot‐starter;所有starter的基本配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--可以生成配置类提示文件--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies> </project>
- 配置文件:一定要放在resources文件夹下创建META-INF/spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.atguigu.starterspringbootautoconfigure.autoconfig.StarterAutoConfiguration
-
创建starter中间项目
- 依赖:
<?xml version="1.0" encoding="UTF-8"?> <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>com.atguigu</groupId> <artifactId>starter-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> <!--启动器--> <dependencies> <!--引入自动配置模块--> <dependency> <groupId>com.atguigu</groupId> <artifactId>starter-spring-boot-autoconfigure</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> </project>
-
创建测试项目
- 依赖:
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.atguigu</groupId> <artifactId>starter-test</artifactId> <version>0.0.1-SNAPSHOT</version> <name>starter-test</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.atguigu</groupId> <artifactId>starter-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- controller:
package com.atguigu.startertest.controller; import com.atguigu.starterspringbootautoconfigure.autoconfig.StarterAutoConfiguration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @Autowired private StarterAutoConfiguration.HelloService helloService; @RequestMapping("/hello") public String sayHello() { String hello = helloService.sayHello("xiaoming"); return hello; } }
- 配置文件:
atguigu.hello.prefix=hello atguigu.hello.suffix=,world
-
测试结果: