一、概述
Java程序员们对Spring Boot这个框架再熟悉不过了,它最大的特点就是自动配置、内嵌容器、可插拔式插件等等……这些特点使得Spring Boot能够做到快速开发,大大提高开发者的效率,今天这篇我来分享一下Spring Boot的启动流程。
二、Spring Boot核心注解
上篇我们自定义了一个spring-boot-starter,用大白话说这就是一个“自定义的可插拔式spring boot插件”,那么spring boot项目启动时是如何自动加载这些“可插拔式插件”的呢?接下来我们就先简单描述一下它的启动流程。
首先Spring Boot项目的启动类上都有一个@SpringBootApplication注解,该注解是一个组合注解,如下图所示
@Target、@Retention、@Documented这三个是java中的元注解,不知道的朋友可以自行查阅资料。
@Inherited的作用是传递继承关系,比如子类要继承父类的注解,那么父类上就需要标记该注解。
@SpringBootConfiguration,该注解里面有一个@Configuration注解,@Configuration是spring提供的注解,表示该类是可以被扫描的配置类。
@EnableAutoConfiguration该注解同样也是一个组合注解,且该注解就是Spring Boot的核心注解,如下图所示
该注解下有两个比较重要的注解@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class),其中@AutoConfigurationPackage注解下面有一行@Import(AutoConfigurationPackages.Registrar.class)注解,如下图所示
该注解通过@Import导入了一个AutoConfigurationPackages.Registrar.class类,该类的作用是扫描主程序类所在包及其子类包下的所有组件,所以spring boot项目的主启动类的位置要在所有子类的根目录位置。
@Import(AutoConfigurationImportSelector.class)注解导入了一个AutoConfigurationImportSelector.class类,该类的作用主要用来自动装配“可插拔式插件”的
@ComponentScan()注解的作用是定义组件扫描的范围。
以上Spring Boot的几个核心注解先简单介绍到这里。
三、Spring Boot启动流程
接下来我将通过debug模式来探究下spring boot项目的启动流程。用来debug的项目我们选用上篇中的gzh-springboot项目(版本2.2.1.RELEASE)。
如图所示我们先将代码定位到项目的主启动类,将断点打在第15行
断点依次经过一下代码
// 先进入run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
// 再j进入run的重载方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
// 实例化SpringApplication对象
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
// 实例化SpringApplication对象
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 判断当前webApplicationType是何类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 初始化META-INF/spring.factries文件下的可用ApplicationContextInitializer类
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置META-INF/spring.factries文件下的可用监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 设置main主启动类
this.mainApplicationClass = deduceMainApplicationClass();
}
以上代码是初始化SpringApplication对象的主要流程,在该过程中,扫描到的ApplicationContextInitializer类将存放在initializers集合里面,扫描到的ApplicationListener类将存放在listeners集合里面,如下图所示
完成初始化之后,开始执行run方法,该方法代码如下
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 第一步:获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 第二步:根据SpringApplicationRunListeners以及参数来准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 第三步:准备Banner打印器,就是项目启动时console上面显示的艺术字体
Banner printedBanner = printBanner(environment);
// 第四步:创建spring容器
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 第五步:spring容器前置处理
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 第六步:刷新容器
refreshContext(context);
// 第七步:spring后置处理
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
以上代码和spring的主要步骤很类似,我们这里就不过多赘述,直接将代码定位到前面分析的AutoConfigurationImportSelector.class将断点打在如下图所示的地方
代码执行到396行,如下所示
((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
我们这边主要关注getAutoConfigurationEntry和getAutoConfigurationMetadata方法,先看getAutoConfigurationMetadata如下
// 进入getAutoConfigurationMetadata方法
private AutoConfigurationMetadata getAutoConfigurationMetadata() {
if (this.autoConfigurationMetadata == null) {
// 调用loadMetadata方法
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
}
return this.autoConfigurationMetadata;
}
程序进入loadMetadata方法,如下图所示
从该图的信息中,我们可以看出程序加载了META-INF/spring-autoconfigure-metadata.properties文件,该文件内容如下
从debug中获得的信息来看,该文件的内容被加载到properties里面了。
看完getAutoConfigurationMetadata方法,我们再看getAutoConfigurationEntry方法,如下
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 第一步:获取合适的AnnotationAttributes对象
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 第二步:让springFactoryLoader加载一些组件的名字
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
// 删除一些不需要的组件名称
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
我们主要看第一步和第二步。
第一步debug进入代码如图所示
我们看到程序找的是org.springframework.boot.autoconfigure.EnableAutoConfiguration类,该类在我们上篇自定义spring-boot-starter中配置文件有定义,忘记的小伙伴可以去翻阅一下。
第二步关键代码如下
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 通过SpringFactoriesLoader加载一些组件名
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// 调用loadSPringFactories方法,如下图
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
我们可以看到classLoader.getResources(FACTORIES_RESOURCE_LOCATION),加载的是META-INF/spring.factories文件,我们自定义“可插拔式插件”的时候,就是定义了这个文件。
看完AutoConfigurationImportSelector.class相关的代码,我们再看下AutoConfigurationPackages.Registrar.class类,如下图所示,debug进入以下代码
new PackageImport(metadata).getPackageName()运行的结果是主启动类所在的包名,获取到主启动类所在包之后,程序就会扫描该包下的所有组件,将其放进IOC容器里面,具体详情这里就不再赘述……
以上分析了spring boot自动装配相关的源码,有时间的朋友可以深度再探究其他部分的源码。
四、小结
通过以上分析,我们大致总结一下spring boot自动装配的原理:当spring boot项目启动时会通过SpringFactoriesLoader.loadFactoryNames加载META-INF/spring.factories配置文件并读取里面的内容,然后自动配置的时候获取org.springframework.boot.autoconfigure.EnableAutoConfiguration相关的类,将该类自动装配到spring boot项目里面,通过这样的方式实现自定义sprin-boot-starter的可插拔功能。
另外spring boot会扫描启动类所在包下的所有组件,需要注意的是,spring boot项目的主启动类要放在最外层,否则会导致有些组件无法被注入到容器里面。
以上就是今天的分享了……
感兴趣的读者朋友可以 关注本公众号,和我们一起学习探究。
本人因所学有限,如有错误之处,望请各位指正!