一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情。
前言
所有的框架的目标必定包含让开发者更专注于业务,Spring Boot也一样。最耳熟能详的就是“约定大于配置”,通过“约定”让开发人员开箱即用,能够很快的进行体验,只需要很少的“需求”配置。
那么Spring Boot中有哪些约定呢?
- Maven的目录结构。默认resouces文件,存放资源配置文件。src-main-resources,src-main-java。默认的编译器生成的类都在target文件夹下面。
- Spring boot默认的配置文件必须是application.命名的yml文件或者properties文件,且唯一。
- application.yml中默认属性。如数据库连接信息必须是以spring:datasource:为前缀;多环境配置。该属性可以根据运行环境自动读取不同的配置文件;端口号、请求路径等。
下面我们主要开了解下Spring Boot的启动流程和自动装配。笔者的版本是
启动流程
从main方法中开始
package com.study.spring;
import lombok.SneakyThrows;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = {"com.study.spring.dao"})
public class Application {
@SneakyThrows
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
进入SpringApplication.run。
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。我们分这两部分进行阅读。
创建SpringApplication
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 没有指定就用默认的
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 这里primarySources为com.study.spring.Application
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 这里为SERVLET(基于servlet的web程序,需要启动内嵌的servlet web容器,比如tomact)。
// 其他知道还有NONE(什么都没有,正常流程,不额外的启动web容器,比如Tomact)、REACTIVE(基于reactive的web程序,需要启动内嵌reactive web容器)。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
// 设置ApplicationContextInitializer,用户IOC容器初始化之后初始化一些组件,后面会单独介绍。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 监听器ApplicationListener,用于围绕IOC容器初始化前中后去执行相关程序。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 决定主启动类,这里为com.study.spring.Application
this.mainApplicationClass = deduceMainApplicationClass();
}
大致如下图:
ApplicationContextInitializer
这个类的主要目的就是在ConfigurableApplicationContext类型(或者子类型)的ApplicationContext进行refresh之前。用来对应用上下文进行初始化的。
主要来源为各个依赖包的META-INF/spring.factories的org.springframework.context.ApplicationContextInitializer=
,使用的是SPI机制(通过配置寻找具体的实现,接口编程+策略模式+配置文件)。
具体在run方法中使用到了,在refresh之前用来初始化context,使用的是责任链模式。
可以看到这里在context中增加了BeanFactoryPostProcessor。
ApplicationListener
ApplicationListener是Spring提供的接口,作用是在Web服务器启动时去加载某些程序。由应用程序事件侦听器实现的接口。观察者设计模式基于标准java.util.EventListener。主要来源为各个依赖包的META-INF/spring.factories,还可以直接实现ApplicationListener。
上图就是自己实现的,不过是在refresh里面刷新完后执行的。这里是同步执行的,所以笔者使用了线程池进行了异步,不影响启动。
run方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
//获取运行过程监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//启动运行过程监听器,执行starting
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//构建环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
//这个就是springboot启动的时候打印的字符串,比如“永无bug”
Banner printedBanner = printBanner(environment);
//创建容器
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//IOC容器的前置处理器,这里会执行在喉间SpringApplication中设置的ApplicationContextInitializer
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新容器
refreshContext(context);
//IOC容器的后处理器
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//发出结束执行事件
listeners.started(context);
//执行Runners
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
大致执行入下图:
前面创建SpringApplication时设置的ApplicationListener会根据SpringApplicationRunListeners执行不同的方法触发不同的事件,比如listeners.starting(bootstrapContext,this.mainApplicationClass);
会触发类型为org.springframework.boot.context.event.ApplicationStartingEvent
事件的监听器进行执行。
最后可以看到使用了ApplicationEventMulticaster执行所有该事件类型的listener。同时可以看到这里使用了线程池异步执行。
自动装配
获取所有EnableAutoConfiguration
首先自动转配的实现来自于META-INF/spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration。在创建SpringApplication是设置ApplicationContextInitializer的时候是第一次loader spring.factories之后会cache住。
注册EnableAutoConfiguration
通过@EnableAutoConfiguration
的@Import(AutoConfigurationImportSelector.class)
找到AutoConfigurationImportSelector,在AutoConfigurationImportSelector#getCandidateConfigurations中打个断点,就可以看到了:
这里的ConfiguractionClassPostProcessor是一个BeanFactoryPostProcessor用来对所有自动装配的Configuration的bean定义进行注册,方便后面进行自动装配。
总结
在创建SpringApplication时对ApplicationContextInitializer进行设置是扫描了所有META-INF/spring.factories进行cache住了org.springframework.boot.autoconfigure.EnableAutoConfiguration,后面通过reflush中的invokeBeanFactoryPostProcessors(beanFactory)调用ConfiguractionClassPostProcessor进而调用AutoConfiguractionImportSelector拿到所有的autoconfiguration注册了bean定义进而实现自动装配。