SpringBoot启动原理解析
概述
前面已经介绍过Spring的IOC和AOP原理,那我们再来看我们平时很熟悉的SpringBoot的启动原理是怎么样的。
SpringBoot启动流程
spring boot 启动流程图片来自: https://www.processon.com/view/link/59812124e4b0de2518b32b6e
创建一个springboot 项目
这里就拿之前写过的springboot demo项目来为例,看一下其启动类DemoApplication
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
简单到一眼就可以让你发现它的关键信息:@SpringBootApplication、SpringApplication.run(DemoApplication.class, args) 在这两个地方。
SpringApplication.run(DemoApplication.class, args)
我们可以跟踪这个run方法进去看看里边的玄机,最终我们会看到一个核心的run方法:
public ConfigurableApplicationContext run(String... args) {
//应用启动计时器器StopWatch 对象
StopWatch stopWatch = new StopWatch();
//计时开始(后面有个对应的计时结束)
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//配置环境信息
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
// banner配置和打印
Banner printedBanner = this.printBanner(environment);
//注释1. 创建ApplicationContext上下文容器
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//注释2. 容器初始化(很重要的ApplicationContext#refresh就在这里!!!)
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
//应用启动计时结束, 到这应用启动就结束了
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
注释1:创建ApplicationContext上下文容器
注释2:容器初始化(很重要的ApplicationContext#refresh就在这里!!!)
对于容器的初始化,我们在Spring IOC系列文章中已经讲过了,对于beanFactory的创建,bean定义的注册,beanFactoryPostProcessor的调用,bean的创建和初始化,beanPostProcessor的调用等等,可以回顾一下《Spring源码阅读----Spring IoC之BeanFactory、ApplicationContext》,后面可以参考。
先到这里,我们先来解析注解,后面再回过来展开这里的细节。
@SpringBootApplication注解
@SpringBootApplication注解可以看到它是一个复合注解,前面四个就不解释了,另外包含了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解。
- @SpringBootConfiguration
点开发现它包含了@Configuration注解,@Configuration在Spring中已经很熟悉了是个配置类的注解。
之前我们在applicationContext.xml中写过配置信息:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--bean 定义 -->
<bean id="testAaService" class="com.zhlab.ssm.demo.web.service.TestAaService">
<property name="testBbService" ref="testBbService"></property>
</bean>
<bean id="testBbService" class="com.zhlab.ssm.demo.web.service.TestBbService">
<property name="testAaService" ref="testAaService"></property>
</bean>
</beans>
那我们在springboot里就可以写一个:
@Configuration
public class XxConfig {
//bean 定义
@Bean
public TestBbService testBbService(){
TestBbService bb = new TestBbService();
bb.setTestAaService(testAaService());
return bb;
}
@Bean
public TestAaService testAaService(){
return new TestAaService();
}
}
这两种效果是一样的前者为xml配置、后者为JavaConfig的配置形式,TestAaService、TestBbService 两个类在介绍Spring 循环依赖一文中有。
-
@ComponentScan
字面意思可以看出它是组件扫描,它会描并加载符合条件的组件(比如@Component和@Service等)或者bean定义,最终将这些bean定义加载到IoC容器中
可以使用basePackages 知道要扫描的路径
可以使用includeFilters 来过滤要包含的组件
可以使用excludeFilters 来过滤要排除的组件 -
@EnableAutoConfiguration
开启自动配置注解。它的作用是通过@Import,将所有符合自动配置条件的bean定义加载到IoC容器中。
点开它可以发现它包含了:@AutoConfigurationPackage、@Import({AutoConfigurationImportSelector.class})两个注解。我们重点来分析这两个注解。- @AutoConfigurationPackage
自动配置包,点进去看它包含一个**@Import({Registrar.class}) **注解,将Registrar作为bean导入Spring容器中
Registrar 是 AutoConfigurationPackages类(名字和上边的接口有点像☺)的一个内部静态类:
- @AutoConfigurationPackage
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
它实现了ImportBeanDefinitionRegistrar接口,并重写了registerBeanDefinitions方法,其调用AutoConfigurationPackages.register,将其同级包名作为bean定义注册到容器中,这样可以给@ConponentScan作为默认的basePackages来扫描。
其被调用过程如下:
SpringApplication.run()
=> refreshContext
// 这里web应用的时候创建的是AnnotationConfigServletWebServerApplicationContext
=> AnnotationConfigServletWebServerApplicationContext.refresh()
//这个比较熟悉吧?调用各个注册了的BeanFactoryPostProcessors
=>AbstractApplicationContext.invokeBeanFactoryPostProcessors()
=> PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()
//注释1.在这里调用到了ConfigurationClassPostProcessor这个后置处理器
=>ConfigurationClassPostProcessor.processConfigBeanDefinitions()
=>ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()
//遍历执行实现了ImportBeanDefinitionRegistrar接口的实例的registerBeanDefinitions方法
=>loadBeanDefinitionsFromRegistrars()
//注释2.调用到Registrar类的registerBeanDefinitions
=>AutoConfigurationPackages$Registrar.registerBeanDefinitions()
=>AutoConfigurationPackages.register()
注释1. 这里用到了ConfigurationClassPostProcessor后置处理器,这个类比较强大,用于启动过程中对配置类的发现和处理,注册bean定义。
注释2.调用到Registrar类的registerBeanDefinitions方法,其调用了AutoConfigurationPackages.register方法
register方法源码如下:
//定义Bean的名称
private static final String BEAN = AutoConfigurationPackages.class.getName();
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
//该bean是否已经注册
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
//将要注册包名称添加进去
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
} else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(2);
//注册bean
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
这里的BeanDefinitionRegistry 对象registry 其实是DefaultListableBeanFactory实例对象,对于DefaultListableBeanFactory类应该比较熟悉了吧。
所以,SpringBoot是通过这种方式,自动地扫描并注册了启动类同级以及子包下的组件bean定义,一般会把启动类放在根目录下。
- @Import({AutoConfigurationImportSelector.class})
接下来看这个注解,导入了AutoConfigurationImportSelector
查看AutoConfigurationImportSelector类的类图,如下:
其实现了ImportSelector接口,重新了selectImports方法,其源码如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 获取自动配置的信息
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
其被调用过程如下:
SpringApplication.run()
=> refreshContext
=> AnnotationConfigServletWebServerApplicationContext.refresh()
=>AbstractApplicationContext.invokeBeanFactoryPostProcessors()
=> PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()
//注释1. ConfigurationClassPostProcessor后置处理器被调用了
=>ConfigurationClassPostProcessor.processConfigBeanDefinitions()
//解析启动类
=>ConfigurationClassParser.parse()
=>parse()
//遍历查找configClass,并处理配置类
=>processConfigurationClass()
//真正做事的,处理configClass
=>doProcessConfigurationClass()
//处理这些improt的类,这里有个getImports()是获取import标签里的值
=>processImports()
//处理实现了DeferredImportSelector接口的类
//AutoConfigurationImportSelector实现了这接口,添加到deferredImportSelectors列表中
=>ConfigurationClassParser.DeferredImportSelectorHandler.handle()
//注释2.遍历deferredImportSelectors列表中的ImportSelector类,处理并注册搜索到的bean
//AutoConfigurationImportSelector类就是在这里被调用到并执行selectImports方法
=>ConfigurationClassParser.DeferredImportSelectorHandler.process()
注释1. ConfigurationClassPostProcessor后置处理器被调用了,ConfigurationClassPostProcessor比较重要,本文关注的是SpringBoot启动过程,这个类后面增加一篇详细解析一下
注释2. 我们需要关注的是AutoConfigurationImportSelector类被调用,这里就是它被调用并加载各类配置类的地方,它的selectImports方法被执行了。
知道了被调用的过程,我们继续跟踪getAutoConfigurationEntry方法,查看源码:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//注释1.自动配置所需的配置信息在这里获取
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
//最后会包装成其内部类AutoConfigurationEntry对象返回
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
注释1.通过getCandidateConfigurations获取自动配置的各种配置类
继续跟踪getCandidateConfigurations方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//注释. 这里加载自动配置相关的类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
注释. getSpringFactoriesLoaderFactoryClass获取的是EnableAutoConfiguration类,所以给SpringFactoriesLoader.loadFactoryNames方法传入的参数为EnableAutoConfiguration类,
SpringFactoriesLoader.loadFactoryNames,这里跟进去可以发现它是加载一个文件"META-INF/spring.factories",这个文件在哪呢,里边都有些什么内容?如下图所示:
可以发现这里有很多自动配置的类,可以自行点开其中一些类,可以发现它们都是带@Configuration注解的配置类,根据传入的类名org.springframework.boot.autoconfigure.EnableAutoConfiguration,会根据这个匹配这个key以下的配置类列表,返回这个配置类列表。
加载完这些,最终会回到ConfigurationClassPostProcessor类中,被loadBeanDefinitions方法执行,注册成bean定义。
最后容器完成这些bean的实例化初始化,容器刷新完成,并启动。
小结
观察整个启动过程,最重要的就是检索加载各个组件bean到Spring容器中的过程,
@Component(@Configuration、@Controller、@Service等也都是特殊的@Component),
@SpringBootApplication由三个组成:@SpringBootConfiguration、@EnableAutoConfiguration
、@ComponentScan
- @SpringBootConfiguration其也是一个@Configuration,它会把启动类作为一个配置类加到Spring容器中
- @ComponentScan会检索各类组件(@Component)加到Spring容器中
- @EnableAutoConfiguration由两个组成:@AutoConfigurationPackage(主要为@Import({Registrar.class}))、@Import({AutoConfigurationImportSelector.class})
- @AutoConfigurationPackage——@Import({Registrar.class}),会把启动类所在目录及其子包作为bean定义注册,供@ComponentScan自动扫描这些包下的组件。
- @Import({AutoConfigurationImportSelector.class}),其AutoConfigurationImportSelector实现了很多接口,重要的是DeferredImportSelector接口,它继承了ImportSelector接口,所以重写selectImports方法,会通过SpringFactoriesLoader类来加载"META-INF/spring.factories"文件,匹配"org.springframework.boot.autoconfigure.EnableAutoConfiguration"的列表会被反射成实例,这些都是@Configuration配置类,然后加入到Spring容器中。
核心的分析完毕,我们在关注一下Tomcat。
Tomcat
我们在使用SpringBoot的时候,可以在其里边直接启动内嵌的Tomcat,这是在什么地方实现的?
在之前分析refresh的时候,其中有个onRefresh方法,它是个模板方法,交给子类自己实现的。
AnnotationConfigServletWebServerApplicationContext 继承自ServletWebServerApplicationContext,在ServletWebServerApplicationContext有个onRefresh方法的实现
protected void onRefresh() {
super.onRefresh();
try {
//这里创建web服务
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
//这里获取ServletWebServerFactory ,这里会获取TomcatServletWebServerFactory
//实现这个接口的还有UndertowServletWebServerFactory、JettyServletWebServerFactory
ServletWebServerFactory factory = this.getWebServerFactory();
//注释1.从TomcatServletWebServerFactory获取WebServer
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
注释1.从TomcatServletWebServerFactory获取WebServer
来TomcatServletWebServerFactory类中看一下getWebServer方法源码:
public WebServer getWebServer(ServletContextInitializer... initializers) {
//创建一个Tomcat对象,然后下面开始设置配置信息(以后可以阅读一下Tomcat的源码)
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
//构建一个Connector对象,传入协议值:org.apache.coyote.http11.Http11NioProtocol
//如果要修改其默认值,需要借助BeanFactoryPostProcessor接口的postProcessBeanFactory
//这样可以通过获取TomcatServletWebServerFactory对象来修改其additionalTomcatConnectors值
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
//注释1. 获取TomcatWebServer实例
return this.getTomcatWebServer(tomcat);
}
注释1. 获取TomcatWebServer实例,传入参数为tomcat
继续跟踪getTomcatWebServer方法:
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
//创建TomcatWebServer对象
return new TomcatWebServer(tomcat, this.getPort() >= 0);
}
继续到TomcatWebServer类中看看其TomcatWebServer构造方法:
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
//设置各类属性值
this.monitor = new Object();
this.serviceConnectors = new HashMap();
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
//开始初始化
this.initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
synchronized(this.monitor) {
try {
//InstanceId加到EngineName中
this.addInstanceIdToEngineName();
Context context = this.findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && "start".equals(event.getType())) {
this.removeServiceConnectors();
}
});
//注释1. tomcat服务启动监听,对服务生命周期的监听,对应的还有个服务结束的监听器
this.tomcat.start();
this.rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
} catch (NamingException var5) {
}
//Tomcat线程都是守护线程。我们创建一个阻塞非守护线程来避免立即关闭
this.startDaemonAwaitThread();
} catch (Exception var6) {
this.stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", var6);
}
}
}
注释1.在这里开始对tomcat服务启动的监听,对应的还有个对服务结束的监听。tomcat的server属性因为其类型Server 继承了Lifecycle接口,会对服务的生命周期进行监听,start() 、stop()来自Lifecycle接口。
那在哪里要启动TomcatWebServer服务了呢?在refresh流程中,容器创建的最后一个步骤就是finishRefresh(不考虑resetCommonCaches方法哈),我们来看ServletWebServerApplicationContext的finishRefresh方法:
protected void finishRefresh() {
//执行父类AbstractApplicationContext的finishRefresh方法
super.finishRefresh();
//注释1. 启动了WebServer
WebServer webServer = this.startWebServer();
if (webServer != null) {
this.publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
//注释2. 这里就启动了web服务,调用TomcatWebServer的start方法
webServer.start();
}
return webServer;
}
注释1. 启动了WebServer
注释2. 这里就启动了web服务,调用TomcatWebServer的start方法
所以,到这里内嵌的tomcat服务就这样在容器初始化的最后阶段启动了。有start当然对应也会有stop停止的方法,具体的Tomcat相关的源码就不在这里展开了,以后阅读Tomcat的源码时再展开做笔记^^。
对于事件驱动的部分,可以参考之前Spring Ioc部分介绍的Spring 事件监听机制。
总结
SpringBoot启动原理的解析就讲到这里,最重要的还是其自动配置的部分,然后也了解了内嵌的Tomcat服务是如何被启动的。