springboot的启动流程源码分析
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | yangxiaohui227
来源 | urlify.cn/6jeaiu
测试项目,随便一个简单的springboot项目即可:
直接debug调试:
可见,分2步,第一步是创建SpringApplication对象,第二步是调用run方法:
1.SpringApplication对象的创建过程:
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) { //resourceLoader为null,因为我们没有传入,primarySources这里包含主启动类的ThymeleafApplication.class
this.resourceLoader = resourceLoader; //资源加载器,这里是null
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //将主启动类字节码存起来
this.webApplicationType = WebApplicationType.deduceFromClasspath(); //检测当前的项目web类型,后续会分析
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//这里涉及springboot的一个重要知识点,后续分析
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//这里涉及springboot的一个重要知识点,后续分析
this.mainApplicationClass = deduceMainApplicationClass();//这里检测main方法所在的类
}
通过SpringApplication的创建过程,我们分析下,它的主要几个方法:
this.webApplicationType = WebApplicationType.deduceFromClasspath();
因为我引入的是springboot-web相关依赖,所以,在本次测试项目中,webApplication的类型是AnnotationConfigServletWebServerApplicationContext
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))和setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)):
后续很多地方也会用到这个功能,在此解析下:
所以上面2个方法分别是从spring.factories文件中,找到key为org.springframework.context.ApplicationContextInitializer 的所有值,然后创建对应的实例,另一个ApplicationListener同理
接下来我们分析下:this.mainApplicationClass = deduceMainApplicationClass();
检测main方法所在的类,我们自己写的代码,自己肯定很容易知道是哪个类,但springboot框架不知道,那怎么检测呢,我们先看一个异常栈信息:
如上图所示,我们只要从一个异常的堆栈中就可以获取到main方法了,所以源码检测main方法也是一样的:
至此SpringApplication对象创建完毕,后续我们分析下它的run方法都做了些啥:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); //一个计时器,用于计算启动的时间
stopWatch.start();//计时开始
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();//异常报告器列表
configureHeadlessProperty();//这个是给System对象设置一个属性:java.awt.headless=true
SpringApplicationRunListeners listeners = getRunListeners(args); //从spring.factories文件中读取SpringApplicationRunListener的实现类,并创建对应的实例,这个类后续分析
listeners.starting();//调用监听器的starting方法
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //这里仅仅是对请求参数进行封装成一个对象
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);//创建并配置环境的一些属性,然后调用监听器的相应方法,后续会分析
configureIgnoreBeanInfo(environment);//往System对象中设置属性spring.beaninfo.ignore的值
Banner printedBanner = printBanner(environment);//打印启动的图标,后续分析
context = createApplicationContext();//在创建SpringApplication对象时,已经检测出当前环境是什么样的webAppliction的类型,这里就是创建该类型的实例,本次代码演示的是:AnnotationConfigServletWebServerApplicationContext
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);//从spring.factories文件中读取SpringBootExceptionReporter的实现类,并创建对应的实例
prepareContext(context, environment, listeners, applicationArguments, printedBanner);//为后续实例化所有的bean进行初始化工作,后续分析
refreshContext(context);//这里开始实例化所有的bean,也就是spring ioc的核心,调用父类的refresh()方法,同时对tomcat进行了实例化,这些源码我都有专门的博客分析过了,所以这里不再重复
afterRefresh(context, applicationArguments); //目前是空实现
stopWatch.stop(); //计时结束
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);//打印本次启动花了多长时间:Root WebApplicationContext: initialization completed in 4186 ms
}
listeners.started(context);//调用Listenners的stared方法
callRunners(context, applicationArguments);//调用ApplicationRunner和CommandLineRunner的方法
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);//调用监听器的running方法
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
通过上面的分析,整个启动流程在原来spring启动项目的基础上,在不同的阶段,加上了监听器的各个方法调用,现在我们来详细分析上面的一些方法:
SpringApplicationRunListeners 和 SpringApplicationRunListener 关系,很明显,前者包含了多个后者:
class SpringApplicationRunListeners {
private final Log log;
private final List listeners;
SpringApplicationRunListeners(Log log, Collection extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting(); //开始启动,此时环境和上下文都还没开始启动
}
}
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment); //环境已经准备好了,我们可以通过实现对应的listener接口来修改环境中变量
}
}
void contextPrepared(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextPrepared(context); //上下文已经准备好
}
}
void contextLoaded(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextLoaded(context);//上下文已经加载完毕
}
}
void started(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context); //上下文已经启动完毕
}
}
void running(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);//上下文启动完毕后,进入运行状态
}
}
void failed(ConfigurableApplicationContext context, Throwable exception) {
for (SpringApplicationRunListener listener : this.listeners) {
callFailedListener(listener, context, exception);
}
}
private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
Throwable exception) {
try {
listener.failed(context, exception);//启动失败做些啥
}
catch (Throwable ex) {
if (exception == null) {
ReflectionUtils.rethrowRuntimeException(ex);
}
if (this.log.isDebugEnabled()) {
this.log.error("Error handling failed", ex);
}
else {
String message = ex.getMessage();
message = (message != null) ? message : "no error message";
this.log.warn("Error handling failed (" + message + ")");
}
}
}
}
可见SpringApplicationRunListener 会在不同的启动周期调用不同的方法,不必非常刻意理解每个方法的具体含义,我们可以根据每个方法在源码中的调用位置,自己实现一个SpringApplicationRunListener做一些个性化的修改
接下来,我们分析下:ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);方法,准备环境对象
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();//获取一个环境对象,不存在就创建
configureEnvironment(environment, applicationArguments.getSourceArgs());//进行命令行参数配置,我们命令行参数没数据是一个空数组,所以不会做什么,这里主要的设置了ConversionService对象到enviroment中
ConfigurationPropertySources.attach(environment);//配置configurationProperties
listeners.environmentPrepared(environment); //监听器处理环境准备好事件
bindToSpringApplication(environment);//绑定environment到SpringApplication中
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
下面看看Banner printedBanner = printBanner(environment); 效果如下:我们也可以自定义
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) { //如果是设置了不打印banner,这里就不会打印
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader()); //资源加载器
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner); //创建banner打印器
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger); //打印到日志文件
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);//打印到控制台,这里我们以打印到控制台来根据
}
继续分析:bannerPrinter.print(environment, this.mainApplicationClass, System.out);
Banner print(Environment environment, Class> sourceClass, PrintStream out) {
Banner banner = getBanner(environment); //从环境中获取banner
banner.printBanner(environment, sourceClass, out); //打印banner 注意Banner是一个接口,这里会根据子类来调用不同的打印方法,如图片banner和文本banner是不一样的
return new PrintedBanner(banner, sourceClass);
}
现在重点在于如何获取banner:getBanner(environment);
private Banner getBanner(Environment environment) {
Banners banners = new Banners();
banners.addIfNotNull(getImageBanner(environment)); //获取图片banner
banners.addIfNotNull(getTextBanner(environment));//获取文本banner
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
return DEFAULT_BANNER; //都获取不到,就用默认的banner
}
//看看图片banner是如何获取的
接下来,我们看看文本banner的获取:
当加载不到默认的图片banner或者文本banner就会使用默认的banner,我们看看默认的banner是啥:
所以,如果我们要打印自定义的banner,只要在resources文件夹下加入banner.txt 或者banner.gif/banner.jpg/banner.png即可:
接下来,我们分析:prepareContext(context, environment, listeners, applicationArguments, printedBanner);准备上下文:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment); //给context设置环境对象,context在这里是AnnotationConfigServletWebServerApplicationContext
postProcessApplicationContext(context);//这里根据条件给context设置一些重要的对象
applyInitializers(context);//Spring.factories文件中配置的ApplicationContextInitializer实现类,对context进行一些初始化操作,我们想对context进行特定操作也可以通过这种方式
listeners.contextPrepared(context);//监听器打印上下文已经准备好事件
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);//打印日志,当前启动的是那个yml “No active profile set, falling back to default profiles: default”
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();//获取bean工厂
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);//注册bean
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner); //注册banner这个bean,这里比较有趣了,后续你可以通过beanFactory获得该bean,然后继续打印banner
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set
//之后分析下callRunners(context, applicationArguments);//调用ApplicationRunner和CommandLineRunner的方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List
最后讲讲springboot自动装配机制:前面 load(context, sources.toArray(new Object[0])); 我们分析过,它会加载主启动类,而主启动类有个注解@SpringBootApplication,该注解会被解析,所以我们分析下该注解:
这里有个重要的注解:
该注解向容器注入了一个bean,@import可以把其当作@componet注解,一样是往容器中注入某个bean,那么我们可以分析该bean:
AutoConfigurationImportSelector 实现了ImportSelector 接口,该接口有个方法返回需要注入spring容器的bean的全限定类名:
现在我们只要分析实现类的这个方法即可:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); //该方法是重点
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());//将获取到的配置类转成数组
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata); //获取@EnableAutoConfiguration 注解的属性exclude 和 excludeName
List configurations = getCandidateConfigurations(annotationMetadata, attributes);//获取配置类,这个是重点后续跟进
configurations = removeDuplicates(configurations);//去重,下面是做一些排除操作,因为@EnableAutoConfiguration 注解可以配置要排除哪些类
Set exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
如上图所示:这里的意思是加载spring.factories文件中key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有value值,注意的是这些value值并不要求是EnableAutoConfiguration的子类,他们可以没有任何继承关系
这些value值的bean都会被spring管理,这也就是各种框架整合springBoot的核心所在,因为你得项目如果想交给spring管理,你可以将自己的配置类配到spring.factories文件中,key为org.springframework.boot.autoconfigure.EnableAutoConfiguration 即可
感谢点赞支持下哈