SpringBoot应用启动简要流程图
点击查看【processon】
run方法主要做了什么操作?
- 创建一个计时器StopWatch并执行start方法,这个类主要记录任务的执行时间
2. 在文件META-INF\spring.factories中获取SpringApplicationRunListener接口的实现类EventPublishingRunListener,主要发布SpringApplicationEvent 并启动监听
3. 把输入参数转成DefaultApplicationArguments类 创建Environment并设置比如环境信息,系统属性,输入参数和打印Banner信息
4. 创建Application的上下文,根据WebApplicationType来创建Context类
5.在文件META-INF\spring.factories中获取SpringBootExceptionReporter接口的实现类FailureAnalyzers
6. 刷新应用上下文前的准备阶段
7. 刷新上下文,在这里真正加载bean到容器中。如果是web容器,会在onRefresh方法中创建一个Server并启动。
8. 刷新应用上下文后的扩展接口 和 发布容器启动完成事件一、依赖管理
SpringBoot中部分dependency时不需要指定版本
SpringBoot项目中的pom.xml文件中都引用父级依赖spring-boot-starter-parent
<!-- Spring Boot父项目依赖管理 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.9.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理, 进入并查看spring-boot-starter-parent底层源文件,先看spring-bootstarter-parent做了哪些事首先看 spring-boot-starter-parent 的 properties 节点
<properties>
<main.basedir>${basedir}/../../..</main.basedir>
<java.version>1.8</java.version>
<resource.delimiter>@</resource.delimiter>
<!-- delimiter that doesn't clash with Spring ${} placeholders -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF- 8</project.reporting.outputEncoding>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
在这里 spring-boot-starter-parent 定义了:
1. 工程的Java版本为 1.8 。
2. 工程代码的编译源文件编码格式为 UTF-8
3. 工程编译后的文件编码格式为 UTF-8
4. Maven打包编译的版本
spring-boot-starter-parent 的「build」节点接下来看POM的 build 节点,分别定义了 resources 资源和pluginManagement 。 resources 节点,里面定义了资源过滤,针对 application 的 yml 、 properties 格式进行了过滤,可以支持支持不同环境的配置,比如 application-dev.yml、application-test.yml、application-dev.properties、 application-dev.properties 等等。pluginManagement 则是引入了相应的插件和对应的版本依赖最后来看spring-boot-starter-parent的父依赖 spring-boot-dependencies
spring-boot-dependencies的properties节点 我们看定义POM,这个才是SpringBoot项目的真正管理依赖的项目,里面定义了SpringBoot相关的版本
二、自动配置
2.1 应用分析
自动配置:根据我们添加的jar包依赖,会自动将一些配置类的bean注册进ioc容器,我们可以需要的地方使用@autowired或者@resource等注解来使用它。
Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?
SpringBoot应用核心启动类为注解@SpringBootApplication配置标识的类
查看注解:@SpringBootApplication, 其标注的有@SpringBootConfiguration注解
@SpringBootConfiguration注解上标的为@Configuration注解,即被@SpringBootApplication标注的类也是一个核心应用配置类
由此可以得出 @SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类
@EnableAutoConfiguration
此注解是实现自定配置的核心注解,它也是一个组合注解
Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的Bean ,并加载到 IOC 容器。@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。
@ComponentScan
主要是从定义的扫描路径中,找出标识了需要装配的类自动装配到spring 的bean容器中。
2.2 注解原理分析
下面依次分析两个核心注解@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)以及注解扫描@ComponentScan
2.2.1 @AutoConfigurationPackage
@AutoConfigurationPackage :自动配置包,它也是一个组合注解,其中最重要的注解是@Import(AutoConfigurationPackages.Registrar.class) ,它是 Spring 框架的底层注解,它的作用就是给容器中导入某个组件类,它将Registrar组件类导入IoC容器中
=> AutoConfigurationPackages.Registrar
在此方法中会获取启动类和其所在的包,在register进行自定配置
=> AutoConfigurationPackages#register
AutoConfigurationPackages.Registrar这个类就干一个事,注册一个 Bean ,这个 Bean 就是
org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有一个参数,这个参数是使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给 JPA entity 扫描器用来扫描开发人员通过注解 @Entity 定义的 entity类。
=>进入DefaultListableBeanFactory#registerBeanDefinition(registry为IoC容器初始化时默认的BeanFactory即DefaultListableBeanFactory)。在此方法中检测AutoConfigurationPackages.BasePackages是否被注册过或者正在被创建中,如果没有就将其加入beanDefinitionMap(IoC容器初始化用来存储需要实例化bean的基础信息的map,从xml和注解加载的bean首先会被封装成BeanDefinition,之后进行实例化放入IoC容器)中。
2.2.2 @Import(AutoConfigurationImportSelector.class)
将AutoConfigurationImportSelector 这个类导入到 Spring 容器中,AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。
AutoConfigurationImportSelector实现的接口:
可以看到 AutoConfigurationImportSelector 重点是实现了 DeferredImportSelector 接口和各种Aware 接口,然后 DeferredImportSelector 接口又继承了 ImportSelector 接口。其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被回调。
autoconfigure.AutoConfigurationImportSelector
确定自动配置实现逻辑的入口方法:
跟自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping (org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping)类的 getImports 方法处,因此我们就从 DeferredImportSelectorGrouping 类的 getImports 方法来开始分析SpringBoot的自动配置源码好了。
DeferredImportSelectorGrouping内部引用了DeferredImportSelector.Group接口,内部通过调用持有的group.process进行筛选自定装配类
注:AutoConfigurationGroup:是AutoConfigurationImportSelector的内部类,主要用来处理自动配 置相关的逻辑,
拥有process和selectImports方法,然后拥有entries和 autoConfigurationEntries集合属性,
这两个集合分别存储被处理后的符合条件的自动配置类,我们知道这些就足够了;
AutoConfigurationImportSelector:承担自动配置的绝大部分逻辑,负责选择一些符合条件的自动配置类;
metadata:标注在SpringBoot启动类上的@SpringBootApplication注解元数据
标【2】的this.group.selectImports的方法主要是针对前面的process方法处理后的自动配置类再进一步有选择的选择导入
之后返回group.selectImports()方法的返回值。group为接口,具体实现逻辑由子类负责。
通过断点可以看出在第一步group.process调用中,传递的importSelector组件为 AutoConfigurationImportSelectorgroup类型为 AutoConfigurationImportSelector$AutoConfigurationGroup,
即执行的process方法为: org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process 方法参数传入的 AutoConfigurationImportSelector对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类。
进入process方法:
依次分析上图相关对象和步骤,
首先是元数据和自动配置组件,根据上面调用结果可以知道 deferredImportSelector为AutoConfigurationImportSelector,
元数据AnnotationMetadata为注解了@SpringBootApplication的启动类
然后依次分析后续步骤
第一步,通过延迟导入选择器(deferrImportSelector)调用getAutoConfigurationEntry()方法 获取需要自动配置的类列表 封装到AutoConfigurationEntry对象中。执行结果如下所示:=>分析AutoConfigurationImportSelector#getAutoConfigurationEntry方法,
看它的子流程是如和获取到需要自动配的类列表
// 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
//获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解 数据
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//【1】得到spring.factories文件配置的所有自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//利用LinkedHashSet移除重复的配置类
configurations = removeDuplicates(configurations);
// 得到要排除的自动配置类,比如注解属性exclude的配置类
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//// 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
checkExcludedClasses(configurations, exclusions);
// 【2】将要排除的配置类移除
configurations.removeAll(exclusions);
// 【3】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,
// 因此这里需要进行过滤注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合
// @ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,后面会重点分 析一下
configurations = filter(configurations, autoConfigurationMetadata);
// 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
// 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
// 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处 理器时触发
fireAutoConfigurationImportEvents(configurations, exclusions);
// 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
return new AutoConfigurationEntry(configurations, exclusions);
}
在getAutoConfigurationEntry方法中,先是获取了需要排除自动配置的类:即@SpringBootApplication(exclude = xx.class)
在接下来的流程中开始检查自动配置类
【1】调用getCandidateConfigurations 得到spring.factories文件配置的所有自动配置类,
=>getCandidateConfigurations():=>SpringFactoriesLoader#loadFactoryNames
在此方法中获取了工厂类型:EnableAutoConfiguration=>然后调用SpringFactoriesLoader#loadSpringFactories
从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。
搜索一个spring.factories文件,spring.factories里面保存着springboot的默认提供的自动配置类。根据上述流程中SpringFactoriesLoader#loadFactoryNames中的方法loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); 由于factoryTypeName为EnableAutoConfiguration,所以此方法返回是key为EnableAutoConfiguration的所有工厂类,即为上图中的列表。
至此流程【1】结束
根据代码流程分析:
【1】从 spring.factories 配置文件中加载 EnableAutoConfiguration 自动配置类),获取的自动配置类如图所示。
【2】根据获取到的需要排除的配置类列表 排除不需要自动配置的类(若 @EnableAutoConfiguration 等注解标有要 exclude 的自动 配置类,那么再将这个自动配置类排除掉;)
【3】排除掉要 exclude 的自动配置类后,然后再调用 filter 方法进行进一步的过滤,再次排除一些不符合条件的自动配置类;
自动配置工厂类中一般都配置有触发条件等 如:@ConditionalOnClass @ConditionalOnProperty等
上图表示classpath路径下存在RabbitTemplate和Channel类才加载此自动配置类(也就是有依赖RabbitMQ时才有这两个类)
AutoConfigurationImportSelector 的 filter 方法主要做的事情就是调用 AutoConfigurationImportFilter 接口的 match 方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnClass , @ConditionalOnBean 或 @ConditionalOnWebApplication 是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。
【4】经过重重过滤后,此时再触发 AutoConfigurationImportEvent 事件,告诉ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类;
【5】 最后再将符合条件的自动配置类返回。
关于条件注解的讲解
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。
@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
@ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
@ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式的条件判断。
@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
@ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
@ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
@ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
@ConditionalOnResource:当类路径下有指定的资源时触发实例化。
@ConditionalOnJndi:在JNDI存在的条件下触发实例化。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。
第二步,将封装了自动配置类的autoConfigurationEntry对象装进 autoConfigurationEntries集合
第三步,将封装的自动配置类名称取出来作为key,以annotationMetaData元数据作为值存入entries中
entries是一个LinkHashMap
至此三步, process()方法调用完成。接着执行:=>org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports方法
同样是位AutoConfigurationImportSelector.AutoConfigurationGroup中
selectImports 方法主要是针对经过排除掉 exclude 的和被AutoConfigurationImportFilter 接口过滤后的满足条件的自动配置类再进一步排除 exclude 的自动配置类,然后再排序:
经过此次过滤排序,需要自动配置的类被筛选出来。
最后,我们再总结下SpringBoot自动配置的原理,主要做了以下事情:
1. 从spring.factories配置文件中加载自动配置类;
2. 加载的自动配置类中排除掉 @EnableAutoConfiguration 注解的 exclude 属性指定的自动配置类;
3. 然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的条件,若都符合的话则返回匹 配结果;
4. 然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来分别记录符 合条件和 exclude 的自动配置类。
5. 最后spring再将最后筛选后的自动配置类导入IOC容器中
2.2.3 @ComponentScan
常用属性如下:
basePackages、value:指定扫描路径,如果为空则以@ComponentScan注解的类所在的包为基本的扫描路径
basePackageClasses:指定具体扫描的类
includeFilters:指定满足Filter条件的类
excludeFilters:指定排除Filter条件的类
includeFilters和excludeFilters 的FilterType可选:ANNOTATION=注解类型 默认、ASSIGNABLE_TYPE(指定固定类)、ASPECTJ(ASPECTJ类型)、REGEX(正则表达式)、CUSTOM(自定义类型),自定义的Filter需要实现TypeFilter接口
@SpringBootApplication:
借助excludeFilters将TypeExcludeFillter及FilterType这两个类进行排除,当前@ComponentScan注解没有标注basePackages及value,所以扫描路径默认为@ComponentScan注解的类所在的包为基本的扫描路径(也就是标注了@SpringBootApplication注解的项目启动类所在的路径)。因此@SpringBootApplication标注的类一般放在包根路径。
2.3 自动配置属性加载案例分析
以 HttpEncodingAutoConfiguration ( Http 编码自动配置)为例解释自动配置加载配置文件原理
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration:
根据当前不同的条件判断,决定这个配置类是否生效。一旦这个配置类生效,这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties 类中获取的,这些类里面的每一个属性又是和配置文件绑定的。
HttpProperties:
# 我们能配置的属性都是来源于这个功能的properties类
spring.http.encoding.enabled=true
spring.http.encoding.charset=utf-8
spring.http.encoding.force=true
所有在配置文件中能配置的属性都是在 xxxProperties 类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类。
总结:
1. SpringBoot 启动会加载大量的自动配置类
2. 我们看我们需要实现的功能有没有 SpringBoot 默认写好的自动配置类
3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们有我们要用的组件,我们就不需要再来配置了)
4. 给容器中自动配置类添加组件的时候,会从 properties 类中获取某些属性,我们就可以在配置文件中指定这些属性的值。
xxxAutoConfiguration :自动配置类,用于给容器中添加组件从而代替之前我们手动完成大量繁琐的配置。
xxxProperties : 封装了对应自动配置类的默认属性值,如果我们需要自定义属性值,只需要根据xxxProperties 寻找相关属性在配置文件设值即可
三、Run方法分析
根据前两节分析自动配置原理得出自动配置使用@SpringBootAppliaction上的组合注解加载组件类和包扫描来完成
@EnableAutoConfiguration注解是通过@Import注解加载了自动配置固定的bean(配置类中@Bean标注的方法什么时间执行 的,即Bean什么时间生成的)
@ComponentScan注解自动进行注解扫描 (何时被扫描的然后将包及其子包中的类生成对象放入IoC容器中)
那么真正根据包扫描,把组件类生成实例对象存到IOC容器中,又是怎么来完成的?
3.1 SpringApplication初始化
org.springframework.boot.SpringApplication#run():
SpringApplication()构造方法:
SpringApplication 实例化过程,首先是进入带参数的构造方法,最终回来到两个参数的构造方法。
=>1.1 推断应用类型WebApplicationType#deduceFromClasspath
在此方法中判断应用的类型
NONE: 应用程序不是web应用,也不应该用web服务器去启动
SERVLET: 应用程序应作为基于servlet的web应用程序运行,并应启动嵌入式servlet
web(tomcat)服务器。
REACTIVE: 应用程序应作为 reactive web应用程序运行,并应启动嵌入式 reactive web服
务器。
经此方法返回的为Servlet类型
=>1.2 SpringApplication#setInitializers,
通过调用*getSpringFactoriesInstances(ApplicationContextInitializer.class)获取7个初始化器的的实例和一个监听器
=>SpringApplication#getSpringFactoriesInstances()
其内部调用了SpringFactoriesLoader.loadFactoryNames(type,classLoalder)方法获取类的全路径
此处调用与自动配置中调用类似,由第二节自动配置调用分析知道type是EnableAutoConfiguration,而此处是Spring应用上下文初始化器(ApplicationContextInitializer)因此在此方法中获取以spring.factories文件中定义的以ApplicationContextInitializer为key的所有初始化器类全限定名。每个jar包下的spring.factories都会加载,两个文件一个加载这8个初始化器全路径
获取到上述8个初始化器的全限定名之后,通过反射拿进行实例化,存入集合中再返回。
然后将上述返回的实例列表通过setInitializers赋值到List中 org.springframework.boot.SpringApplication#setInitializers
ApplicationContextInitializer 是Spring框架的类, 这个类的主要目的就是在ConfigurableApplicationContext 调用refresh()方法之前,回调这个类的initialize方法。通过 ConfigurableApplicationContext 的实例获取容器的环境Environment,从而实现对配置文件的修改完善等工作。
=>1.3 初始化classpath下所有已配置的 ApplicationListener
此处 ApplicationListener 的加载过程和上面的 ApplicationContextInitializer 类的加载过程是一样的,不同是它获取的是spring.factories中applicationlistener为key的事件监听器全限定名,然后进行反射实例化。
初始化classpath下 META-INF/spring.factories中已配置的 ApplicationListener。 ApplicationListener 是spring的事件监听器,典型的观察者模式,通过ApplicationEvent 类和 ApplicationListener 接口,可以实现对spring容器全生命周期的监听,当然也可以自定义监听事件
=>1.4根据调用栈,推断出 main 方法的类名
通过这4个步骤 SpringApplication对象初始化完成。
总结
关于 SpringApplication 类的构造过程,到这里就梳理完了。纵观 SpringApplication 类的实例化过程,我们可以看到,合理的利用该类,我们能在spring容器创建之前做一些预备工作,和定制化的需求。比如,自定义SpringBoot的Banner,比如自定义事件监听器,再比如在容器refresh之前通过自定义ApplicationContextInitializer 修改配置一些配置或者获取指定的bean都是可以的
3.2 Run方法执行
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
* 运行spring应用,并刷新一个新的ApplicationContext Spring的上下文
* ConfigurableApplicationContext是ApplicationContext接口的子接口,在ApplicationContext基础山增加了
* 配置上下文的工具 ConfigurableApplicationContext是容器的高级接口
*/
public ConfigurableApplicationContext run(String... args) {
//记录程序运行事件
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//
ConfigurableApplicationContext context = null;//Spring 的上下文
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//从META-INF/spring.factories中获取监听器
// 1、获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//2、构造应用上下文环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//处理需要忽略的Bean
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
///3、初始化应用上下文
context = createApplicationContext();
//实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//4、刷新应用上下文前的准备阶段
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//5、刷新应用上下文
refreshContext(context);
//刷新应用上下文后的扩展接口
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;
}
在以上的代码中,启动过程中的重要步骤共分为六步
第一步:获取并启动监听器
第二步:构造应用上下文环境
第三步:初始化应用上下文
第四步:刷新应用上下文前的准备阶段
第五步:刷新应用上下文
第六步:刷新应用上下文后的扩展接口
3.2.1 获取监听器并启动
org.springframework.boot.SpringApplication#getRunListeners
查看SpringApplicatinRunListeners构造方方法
需要传入一个监听器列表,因此getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)获取SpringApplicationRunListener实例对象列表,此监听器是用来在不同阶段广播不同的消息,传递给ApplicationListener监听器实现
=>getSpringFactoriesInstances 从spring.factories文件中获取SpringApplicationRunListener对应的监听器全限定名,然后通过反射获取监听器的实例
由此可见 SpringApplicationRunListeners listeners = getRunListeners(args) 获取的是 EventPublishlingRunListener
它是事件发布运行监听器,主要用来发布事件通知的,通知其他监听器实现。
listeners.starting(); 开启了监听事件。
例如加载配置文件的监听器:
3.2.2 构造应用上下文环境
org.springframework.boot.SpringApplication#prepareEnvironment
=>创建并配置环境.SpringApplication#getOrCreateEnvironment,环境类型在SpringApplication构造方法时根据classpath下存在的类推断应用了类型,在这里获取到应用类型后构建了StandardServletEnvironment(标准的Web应用Servlet环境对象)
=>SpringApplication#configureEnvironment 配置应用环境,
在spring的启动参数中指定了参数:—spring.profiles.active=prod
可以启动多实例
configurePropertySources(environment, args);中将args封装成了SimpleCommandLinePropertySource并加入到了environment中。
configureProfiles(environment, args); 根据启动参数激活了相应的配置文件。
通过环境设置我们可以看到获取的系统的环境信息:
=>SpringApplicationRunListeners#environmentPrepared 获取监听器
=>进入到方法一路跟下去最终调用到 org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent()
查看getApplicationListeners(event, type)执行结果,发现一个重要的监听器ConfigFileApplicationListener。先看看这个类的注释
这个监听器默认的从注释中标签所示的几个位置加载配置文件,并将其加入 上下文的 environment变量中。当然也可以通过配置指定。
构建完环境信息后,可以看到Environment对象中系统环境信息和用户配置文件信息都被加载进入环境中
3.2.3 初始化应用上下文
SpringApplication#createApplicationContext ->返回 ConfigurableApplicationContext接口的实现类对象
ConfigurableApplicationContext继承关系如下图所示
createApplicationContext
通过上两个步骤指定应用类型为Servlet,此方法通过反射获取AnnotationConfigServletWebServerApplicationContext对象实例
AnnotationConfigServletWebServerApplicationContext的继承关系如下所示
应用上下文可以理解成IoC容器的高级表现形式,应用上下文确实是在IoC容器的基础上丰富了一些高级功能。
=> BeanUtils#instantiateClass(java.lang.Class
=>通过 AnnotationConfigServletWebServerApplicationContext类的构造函数创建实例对象
构造函数在调用时首先调用了父级构造函数:
GenericApplicationContext类
通过上面的类图可以知道AnnotationConfigServletWebServerApplicationContext继承至GenericApplicationContext类。
beanFactory正是在AnnotationConfigServletWebServerApplicationContext继承的类GenericApplicationContext中定义的。在上面createApplicationContext()方法中的, BeanUtils.instantiateClass(contextClass) 这个方法中,调用构造函数进行实例化对象,因此不但初始化了AnnotationConfigServletWebServerApplicationContext类,也就是我们的上下文context,同样也触发了GenericApplicationContext类的构造函数,从而IoC容器也创建了。
在IoC源码分析中知道 DefaultListableBeanFactory是 ConfigurableListableBeanFactory的默认实现,用来管理所有Bean。
由此可以看出应用上下文对IoC容器是持有的关系。他的一个属性beanFactory就是IoC容器(DefaultListableBeanFactory)。所以他们之间是持有,和扩展的关系。
通过断点分析:
获取到应用上下文后,接着获取了应用异常报告器
3.2.4 刷新应用上下文前的准备阶段
准备阶段主要做两件事
第一 完成相关属性的设置
第二 完成bean信息的封装注入
首先看prepareContext()方法。SpringApplication#prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//设置容器环境
context.setEnvironment(environment);
//执行容器后置处理
postProcessApplicationContext(context);
//执行容器中的 ApplicationContextInitializer 包括spring.factories和通过三种方式自定义的
applyInitializers(context);
//向各个监听器发送容器已经准备好的事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//将main函数中的args参数封装成单例Bean,注册进容器
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
//将 printedBanner 也封装成单例,注册进容器
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载我们的启动类,将启动类注入容器
load(context, sources.toArray(new Object[0]));
//发布容器已加载事件
listeners.contextLoaded(context);
}
=> 设置环境属性后 执行容器后置处理逻辑,在内部设置转换器服务 SpringApplication#postProcessApplicationContext
=>SpringApplication#applyInitializers开始执行在SpringApplication初始化时拿到初始化器
=> 容器属性设置完成,向监听器发送容器准备好的事件
EventPublishingRunListener向SpringAppplicationListener监听器发送消息通知
=> 然后将args参数 banner对象封装成单例,设置一些bean的属性是否被覆盖和是否懒加载属性,
接下来getAllSources() 获取到启动类:
=> SpringApplication#load 开始加载启动类,将启动类(封装成beanDefinition对象)注入容器
子流程开始,bean属性信息的创建流程(以SpringBoot启动类为例)
SpringApplication#load
查看 getBeanDefinitionRegistry()源码
这里将我们前文创建的上下文强转为BeanDefinitionRegistry,他们之间是有继承关系的。
BeanDefinitionRegistry定义了很重要的方法registerBeanDefinition(),该方法将BeanDefinition注册进DefaultListableBeanFactory容器的beanDefinitionMap中。 通过IoC源码分析我们知道Spring容器在启动是,Bean生命周期开始之前,回家类解析成Spring内部的beanDefinition结构对象,并将beanDefinition存入DefaultListableBeanFactory的map中
如下图所示:
-> 继续查看createBeanDefinitionLoader(),最终进入了BeanDefinitionLoader类的构造方法
在此设置了三个属性 分别是注解bean定义读取器、 xml定义bean读取器和类路径扫描器。
上面三个属性在BeanDefinition的Resource定位,和BeanDefinition的注册中到了很重要的作用。
-> 创建BeanDefinitionLoader之后,执行load方法,跟踪调用最终进行入
BeanDefinitionLoader#load(java.lang.Class<?>)
isComponent(source)判断主类是不是存在@Component注解,主类@SpringBootApplication是一个组合注解,包含@Component。
->跟进register()方法,最终进到AnnotatedBeanDefinitionReader类的doRegisterBean()方法。
-> org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition 将BeanDefinition注册进beanDefinitionMap
->DefaultListableBeanFactory#registerBeanDefinition
将启动类的beanDefinition存入了Ioc容器的Map中. 在registerBeanDefinition(),首先会检查是否已经存在,如果存在并且不允许
被覆盖则直接抛出异常。不存在的话就直接注册进beanDefinitionMap中
至此加载主类的信息的子流程结束
=> SpringApplicationRunListeners#contextLoaded,启动类加载完成之后,监听器发布上下文已经加载事件,至此应用上下文刷新的准备工作完成。
经过此步prepareContext()方法,可以看到,启动类的BeanDefinition已经注册进来了
3.2.5 刷新应用上下文
org.springframework.boot.SpringApplication#refresh
=>org.springframework.context.support.AbstractApplicationContext#refresh
此处我们在初始化应用上下文时已经创建了BeanFactory,隐藏在obtainFreshBeanFactory()方法,其实就是拿到我们之前创建的beanFactory。
先看一下refreshBeanFactory()方法,跟下去来到GenericApplicationContext类的refreshBeanFactory()发现也没做什么。
TIPS:
1,AbstractApplicationContext类有两个子类实现了refreshBeanFactory(),但是在前面第三步初始化上下文的时候,实例化了GenericApplicationContext类,所以没有进入 AbstractRefreshableApplicationContext中的refreshBeanFactory()方法。
2,this.refreshed.compareAndSet(false, true)
这行代码在这里表示:GenericApplicationContext只允许刷新一次
这行代码,很重要,不是在Spring中很重要,而是这行代码本身。
首先看一下this.refreshed 属性:
private final AtomicBoolean refreshed = new AtomicBoolean();
java J.U.C并发包中很重要的一个原子类AtomicBoolean。
通过该类的compareAndSet()方法 可以实现一段代码绝对只实现一次的功能。
postProcessBeanFactory(beanFactory);
postProcessBeanFactory()方法向上下文中添加了一系列的Bean的后置处理器。后置处理器工作的时机是在所有的beanDenifition加载完成之后,bean实例化之前执行。简单来说Bean的后置处理器可以修改BeanDefinition的属性信息。
之后进入invokeBeanFactoryPostProcessors()
invokeBeanFactoryPostProcessors(beanFactory); 执行的主要流程
IoC容器的初始化过程包括三个步骤,在invokeBeanFactoryPostProcessors()方法中完成了IoC容器初始化过程的三个步骤。
1,第一步:Resource定位
在SpringBoot中,我们都知道他的包扫描是从主类所在的包开始扫描的,prepareContext()方法中,会先将主类解析BeanDefinition,然后在refresh()方法的invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition获取basePackage的路径。这样就完成了定位的过程。其次SpringBoot的各种starter是通过SPI扩展机制实现的自动装配,SpringBoot的自动装配同样也是在invokeBeanFactoryPostProcessors()方法中实现的。还有一种情况,在SpringBoot中有很多的@EnableXXX注解,细心点进去看的应该就知道其底层是@Import注解,在invokeBeanFactoryPostProcessors()方法中也实现了对该注解指定的配置类的定位加载。
常规的在SpringBoot中有三种实现定位,第一个是主类所在包的,第二个是SPI扩展机制实现的自动装配(比如各种starter),第三种就是@Import注解指定的类。(对于非常规的不说了)
2,第二步:BeanDefinition的载入
在第一步中说了三种Resource的定位情况,定位后紧接着就是BeanDefinition的分别载入。所谓的载入就是通过上面的定位得到的basePackage,SpringBoot会将该路径拼接成:classpath:com/lagou/**/.class这样的形式(物理路径),然后一个叫做xPathMatchingResourcePatternResolver的类会将该路径下所有的.class文件都加载进来,然后遍历判断是不是有@Component注解,如果有的话,就是我们要装载的BeanDefinition。大致过程就是这样的了。
TIPS:
@Configuration,@Controller,@Service等注解底层都是@Component注解,只不过包装 了一层罢了。
3、第三个过程:注册BeanDefinition
这个过程通过调用上文提到的BeanDefinitionRegister接口的实现来完成。这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。通过上文的分析,我们可以看到,在IoC容器中将BeanDefinition注入到一个ConcurrentHashMap中,IoC容器就是通过这个HashMap来持有这些BeanDefinition数据的。比如DefaultListableBeanFactory 中的beanDefinitionMap属性。
接下来我们通过代码看看具体是怎么实现的。
=>PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()
=>ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
=>ConfigurationClassPostProcessor#processConfigBeanDefinitions,在此方法内生成一个Configuration解析器,用来解析@Configuration标注的class
跟踪调用栈,来到ConfigurationClassParser类的parse()方法。
*ConfigurationClassParser#parse() 流程开始**
=>ConfigurationClassParser#parse()
如果是SpringBoot项目进来的,bd其实就是前面主启动类封装成的 AnnotatedGenericBeanDefinition(AnnotatedBeanDefinition接口的实现类)。this.deferredImportSelectorHandler.process()则是开始执行自动装配的逻辑 参考第二节源代码分析
================================parse()流程开始==============================================
看上面的注释,在前面的prepareContext()方法中,我们详细介绍了我们的主类是如何一步步的封装成AnnotatedGenericBeanDefinition,并注册进IoC容器的beanDefinitionMap中的。
继续沿着parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
递归的调用doProcessConfigurationClass,递归处理Bean,如果有父类,递归处理,直到顶层父类
方法跟下去看doProcessConfigurationClass()方法。(SpringBoot的包扫描的入口方法,重点)
=>org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
在此方法中,可以看到处理了@ComponentScan注解 获取@ComponentScan注解标识的属性,然后在parse中解析每个属性,之后开始进行包扫描
———————————————————this.componentScanParser.parse() 开始———————————————-
其中有解析basePackages方法:
=> ComponentScanAnnotationParser#parse
将启动类所在包作为基础包路径进行扫描Bean。 (这就是SpringBoot为什么从主类开始包扫描)
到这里呢IoC容器初始化三个步骤的第一步,Resource定位就完成了,成功定位到了主类所在的包。
接着执行扫描逻辑
=>org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
-> 子流程开始: Set candidates = findCandidateComponents(basePackage); 从basePackage中扫描类并解析成BeanDefinition
->findCandidateComponents内部调用scanCandidateComponents方法
进入ClassPathScanningCandidateComponentProvider#scanCandidateComponents
可以看出通过拼接包路径获得物理路径获取所有class文件
接着通过读取器将标注的bean封装成BeanDefinition对象
然后将封装的Set<_BeanDefinition_>返回, 流程返回到doScan中,
子流程结束**
经过上述子流程后 获取到了BeanDefinition的集合,然后遍历集合将其注册进容器
=>ClassPathBeanDefinitionScanner#registerBeanDefinition
=>注册进入容器,此时@Service @Controller @RestController等标注的bean都注册到了容器中,(此处逻辑与之前将主启动类注册进来方法是一样的)
=>DefaultListableBeanFactory#registerBeanDefinition
在前面介绍prepareContext()方法时,我们详细介绍了主类的BeanDefinition是怎么一步一步的注册进DefaultListableBeanFactory的beanDefinitionMap中的。完成了BeanDefinition的注册,就完成了IoC容器的初始化过程。此时,在使用的IoC容器DefaultListableFactory中已经建立了整个Bean的配置信息,而这些BeanDefinition已经可以被容器使用了。他们都在BeanbefinitionMap里被检索和使用。容器的作用就是对这些信息进行处理和维护。这些信息是容器建立依赖反转的基础。
到此在这些方法中完成了IoC容器初始化过程的第二三步,BeanDefinition的载入,和BeanDefinition的注册。
当然这只是针对SpringBoot的包扫描的定位方式的BeanDefinition的定位,加载,和注册过程。前面我们说过,还有两种方式@Import和SPI扩展实现的starter的自动装配。
———————————————————this.componentScanParser.parse() 结束———————————————-
回到前面的=>ConfigurationClassParser#doProcessConfigurationClass方法,parse方法结束,执行循环,判断通过包扫描获取的bean上是否有配置@Import()注解需要导入的Bean
=>接下来 @Import注解的解析过程
ConfigurationClassParser#doProcessConfigurationClass方法中调用processImports()方法
此方法需要一个参数getImports(sourceClass),此时TestController上未标注@Import. 通过主启动类调试:
由第二节自动配置分析知道主启动类上配置的@SpringBootApplication是组合注解它标注上了@Import注解,首先进入getImports(sourceClass)方法:
->ConfigurationClassParser#collectImports
第一次递归:sourceClass:主启动类
第二次递归调用:sourceClass:SpringBootApplication 此注解上组合有多个注解,依次进行校验调用
此处执行结束可以得到主启动类上@SpringBootApplication注解上的@Import值
由自动配置章节分析自动 上图两个组件都是@SpringBootApplication注解上的@Import标注的值
获取到组件之后继续执行processImports()方法,方法内部并没有执行两个组件的逻辑,内部实现略过,继续往下跟踪执行
================================parse()流程结束==============================================
回到了方法ConfigurationClassParser#parse(Set configCandidates)中的parse()方法
在parse()方法执行完成之后,调用this.deferredImportSelectorHandler.process() 进行自动装配的逻辑
=>ConfigurationClassParser.DeferredImportSelectorHandler#process 继续跟踪
=>ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports
方法执行到此处可以发现调用的是group.getImports()方法,此处与自动配置中分析源码衔接起来。
由此处分析可以知道 @SpringBootApplication中@Import导入的组件在此处开始执行,从Spring.frocties文件中加载需要自动配置类
此时自动配置相关的类列表全限定名已经获取到了。this.deferredImportSelectorHandler.process()执行完毕,
接着ConfigurationClassParser#parse()执行完成
*ConfigurationClassParser#parse() 流程结束**
回到方法 ConfigurationClassPostProcessor#processConfigBeanDefinitions
执行this.reader.loadBeanDefinitions(configClasses) 将上述步骤中获取的自动配置类或其他组件工厂类等都注入进map中,等待后续的实例化
执行this.reader.loadBeanDefinitions(configClasses) 后IoC容器中beanDefinitionMap中数据:
为什么说上述还没实例化,因为在refresh方法中调用finishBeanFactoryInitialization(beanFactory);才开始单例bean的实例化
以mvc错误信息自动配置为例,其自动配置工厂类为:
在IoC容器遍历BeanDefinition列表进行实例化时beanNames值:由此可见上述逻辑将自动配置工厂类和其中定义的@Bean都读取封装成了BeanDefinition对象,供此处IOC容器进行实例生成
3.2.6 刷新应用上下文后的扩展接口
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
四、内嵌Tomcat分析
4.1 SpringBoot底层Web容器
Spring Boot默认支持Tomcat,Jetty,和Undertow作为底层容器。而Spring Boot默认使用Tomcat,一旦引入spring-boot-starter-web模块,就默认使用Tomcat容器。
切换servlet容器
那如果我么想切换其他Servlet容器呢,只需如下两步:将tomcat依赖移除掉,然后引入其他Servlet容器依赖
引入jetty:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<!--移除spring-boot-starter-web中的tomcat-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<!--引入jetty-->
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
4.2 启动内置tomcat流程
在自动配置章节知道@SpringBootApplication注解上有使用@Import注解对AutoConfigurationImportSelector 类进行了引入,该类中首先调用selectImport()方法,在该方法中调用了getAutoConfigurationEntry()方法,在之中又调用getCandidateConfigurations()方法,getCandidateConfigurations()方法就去META-INF/spring.factory配置文件中加载相关配置类
继续打开spring.factories配置文件,找到tomcat所在的类,tomcat加载在ServletWebServerFactoryAutoConfiguration配置类中
进入该类,里面也通过@Import注解将EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow等嵌入式容器类加载进来了,springboot默认是启动嵌入式tomcat容器,如果要改变启动jetty或者undertow容器,需在pom文件中去设置。如下图
@EnableConfigurationProperties(ServerProperties.class)定义了可自定义配置的属性
继续进入EmbeddedTomcat类中,见下图: 由于依赖的Tomcat相关的jar所以导入条件可以满足因此 Spring IoC容器会生成TomcatServletWebServerFactory对象
进入TomcatServletWebServerFactory类,里面的getWebServer()是关键方法,如图:
继续进入getTomcatWebServer()等方法,一直往下跟到tomcat初始化方法,调用tomcat.start()方法,tomcat就正式开启运行,见图
走到这里tomcat在springboot中的配置以及最终启动的流程就走完了。
4.3 getWebServer()的调用分析
此时肯定有一个疑问,上节上图中的getWebServer()方法是在哪里调用的呢?上面的代码流程并没有发现getWebServer()被调用的地方。因为getWebServer()方法的调用根本就不在上面的代码流程中,它是在另外一个流程中被调用的。
首先进入SpringBoot启动类的run方法:
SpringApplication.run(HppaApplication.class, args);
这个会最终调用到一个同名方法run(String… args)
public ConfigurableApplicationContext run(String... args) {
//StopWatch主要是用来统计每项任务执行时长,例如Spring Boot启动占用总时长。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//第一步:获取并启动监听器 通过加载META-INF/spring.factories 完成了 SpringApplicationRunListener实例化工作
SpringApplicationRunListeners listeners = getRunListeners(args);
//实际上是调用了EventPublishingRunListener类的starting()方法
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//第二步:构造容器环境,简而言之就是加载系统变量,环境变量,配置文件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//设置需要忽略的bean
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//第三步:创建容器
context = createApplicationContext();
//第四步:实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context);
//第五步:准备容器 这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作: 将启动类注入容器,为后续开启自动化配置奠定基础。
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//第六步:刷新容器 springBoot相关的处理工作已经结束,接下的工作就交给了 spring。 内部会调用spring的refresh方法,
// refresh方法在spring整个源码体系中举足轻重,是实现 ioc 和 aop的关键。
refreshContext(context);
//第七步:刷新容器后的扩展接口 设计模式中的模板方法,默认为空实现。
//如果有自定义需 求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//发布应用已经启动的事件
listeners.started(context);
/** 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
* 我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对 SpringBoot的启动过程进行扩展。
*/
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;
}
根据之前关于Run方法分析知道这个方法大概做了以下几件事
1. 获取并启动监听器 通过加载META-INF/spring.factories 完成了SpringApplicationRunListener实例化工作
2. 构造容器环境,简而言之就是加载系统变量,环境变量,配置文件
3. 创建容器
4. 实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
5. 准备容器
6. 刷新容器
7. 刷新容器后的扩展接口
那么内置tomcat启动源码,就是隐藏在上诉第六步:refreshContext方法里面,该方法最终会调用到AbstractApplicationContext类的refresh()方法
进入refresh()方法,如图:
内部会调用onRefresh(),然后会调用到ServletWebServerApplicationContext中的createWebServer()
=>org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
createWebServer()就是启动web服务,但是还没有真正启动Tomcat,既然webServer是通过ServletWebServerFactory来获取的,那就来看看这个工厂的真面目。
可以看到,tomcat,Jetty都实现了这里的getWebServer方法
=>ServletWebServerApplicationContext#getWebServerFactory 在此可以看出返回的是TomcatServletWebServerFactory
=>进入TomcatServletWebServerFactory中的getWebServer(ServletContextInitializer… initializers).
最终就调用了TomcatServletWebServerFactory类的getWebServer()方法。至此Tomcat容器启动完成。
接上面refresh方法中 onfresh()执行完成后,在末尾会执行finishRefresh()方法完成刷新,由于AbstractApplicationContext#finishRefresh 它是抽象类方法,子类会进行重写
=>ServletWebServerApplicationContext#finishRefresh 服务器启动时需要实例化Servlet(例如 DispatcherServlet)
=>org.springframework.boot.web.embedded.tomcat.TomcatWebServer#start
内嵌Tomcat启动流程
五、自动配置SpringMVC
5.1 Servlet3.0补充知识点
在一个普通的WEB项目中如何去使用SpringMVC,我们首先就是要在web.xml中配置如下配置
<servlet>
<description>spring mvc servlet</description>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
注意:servlet实例要添加(注册)到像tomcat这样的servletContext中,才能提供请求服务
在SpringBoot中,我们没有了web.xml文件,我们如何去配置一个 Dispatcherservlet 呢?
其实Servlet3.0规范中规定,要添加一个Servlet,除了采用xml配置的方式,还有一种通过代码的方式,伪代码如下
servletContext.addServlet(name, this.servlet);
那么也就是说,如果我们能动态往web容器中添加一个我们构造好的 DispatcherServlet 对象,就实现自动装配SpringMVC了。
Servlet3.0规范的诞生为SpringBoot彻底去掉了xml(web.xml)奠定了了理论基础
当实现了Servlet3.0规范的容器(比如Tomcat7及以上版本)启动时,通过SPI扩展机制自动扫描所以已添加的jar包下的META-INF/services/javax.servlet.ServletContainerInitializer中指定的全路径类,并实例化该类,然后回调META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的ServletContainerInitializer的实现类的onStartup方法
其是通过ContextConfig监听器遍历每个jar包或web根目录的META-INF/services/javax.servlet.ServletContainerInitializer文件,根据读到的类路径实例化每个ServletContainerInitializer。内嵌Tomcat启动时需注册这个监听器
5.2 SpringMVC自动配置
5.2.1 自动配置类
Springboot的自动配置基于SPI机制,实现自动配置的核心要点就是添加一个自动配置的类,
SpringBoot MVC的自动配置自然也是相同原理。
所以,先找到springmvc对应的自动配置类。查找自动配置类:spring.factories文件 DispatcherServletAutoConfiguration为SpringMVC自动配置类
DispatcherServletAutoConfiguration自动配置类
1、首先注意到,@Configuration表名这是一个配置类,将会被spring给解析。2、@ConditionalOnWebApplication意味着当时一个 web项目,且是Servlet项目的时候才会被解析。
3、@ConditionalOnClass指明DispatcherServlet这个核心类必须存在才解析该类。
4、@AutoConfigureAfter指明在ServletWebServerFactoryAutoConfiguration(内置Tomcat启动时自动配置类)这个类之后再解析, 设定了一个顺序。
总的来说,这些注解表明了该自动配置类的会解析的前置条件需要满足。
其次,DispatcherServletAutoConfiguration类主要包含了两个内部类,分别是
1、DispatcherServletConfiguration
2、DispatcherServletRegistrationConfiguration
顾名思义,前者是配置DispatcherServlet,后者是配置DispatcherServlet的注册类。什么是注册类?我们知道Servlet实例是要被添加(注册)到如tomcat这样的ServletContext里的,这样才能够提供请求服务。所以,DispatcherServletRegistrationConfiguration将生成一个Bean,负责将DispatcherServlet给注册到ServletContext中。
DispatcherServletConfiguration类:
dispatcherServlet方法将生成一个DispatcherServlet的Bean对象。比较简单,就是获取一个实例,然后添加一些属性设置。
multipartResolver方法主要是把你配置的MultipartResolver的Bean给重命名一下,防止你不是用multipartResolver这个名字作为Bean的名字。
DispatcherServletRegistrationConfiguration类:
内部只有一个方法,生成了DispatcherServletRegistrationBean。核心逻辑就是实例化了一个Bean,设置了一些参数,如dispatcherServlet、loadOnStartup等
总结
Springboot mvc的自动配置类是DispatcherServletAutoConfigration,主要做了两件事:
1)配置DispatcherServlet
2)配置DispatcherServlet的注册Bean(DispatcherServletRegistrationBean)
5.2.2 注册DispatcherServlet到ServletContext
DispatcherServletRegistrationBean负责将DispatcherServlet注册到ServletContext当中
DispatcherServletRegistrationBean的类图
注册DispatcherServlet流程
ServletContextInitializer
我们看到,最上面是一个ServletContextInitializer接口。我们可以知道,实现该接口意味着是用来初始化ServletContext的。看看该接口
public interface ServletContextInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
RegistrationBean
看看RegistrationBean是怎么实现onStartup方法的
register是抽象方法,找到实现类方法
=>org.springframework.boot.web.servlet.DynamicRegistrationBean#register
找到addRegistration具体实现:
=>org.springframework.boot.web.servlet.ServletRegistrationBean#addRegistration
在此处将servlet注入了servletContext中。
那么上面的onStart()方法是什么时间调用的?
在上个章节中分析内嵌Tomcat启动源码时,我们知道创建内嵌的Tomcat容器时先获取嵌入式Servlet容器工厂如下图
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
factory.getWebServer(getSelfInitializer())方法执行获取Web容器
=>进入getSelfInitializer()方法
通过调试,知道 getServletContextInitializerBeans() 返回的是一个ServletContextInitializer 集合,集合中有以上几个Bean,
然后依次去调用对象的 onStartup 方法,那么对于上图标红的对象来说,就是会调用到DispatcherServletRegistrationBean 的 onStartup 方法,这个类并没有这个方法,所以最终会调用到父类 RegistrationBean 的 onStartup 方法,该方法代码如下
这边 register(description, servletContext); 会调用到 DynamicRegistrationBean 的 register 方法,代码如下
addRegistration(description, servletContext) 又会调用到 ServletRegistrationBean中的 addRegistration 方法,代码如下
看到了关键的 servletContext.addServlet 代码了,我们通过调试,即可知到 this.servlet 就 是 dispatcherServlet到此与上小节流程吻合,到这里dispatcherServlet就被注册到内嵌的Servlet容器中了
六、数据源自动配置
6.1 连连接池自动配置
SpringBoot数据源的自动配置与上面MVC和Tomcat同理
所以,先找到数据源对应的自动配置类。查找自动配置类:spring.factories文件DataSourceAutoConfiguration 为数据源自动配置类
进入DataSourceAutoConfiguration类
@Conditional(PooledDataSourceCondition.class) 根据判断条件,实例化这个类,指定了配置文件中,必须有type这个属性,另外springboot 默认支持 type 类型设置的数据源;
进入DataSourceConfiguration类:内部包含的Hikari类
createDataSource 方法创建数据源
DataSourceBuilder 类设置type
根据设置type的选择类型
根据注释当type为空时 此处默认选择Hikari
getType=>org.springframework.boot.jdbc.DataSourceBuilder#findType 如果type为空,则从数组中默认选择第一个
那么证实在没有指定Type的情况下,默认类型为com.zaxxer.hikari.HikariDataSource数据源自动配置到此完成。
6.2 Mybatis自动配置源码
自动配置类:
1、springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplication中的@EnableAutoConfiguration
2、EnableAutoConfiguration主要是通过AutoConfigurationImportSelector类来加载mybatis自动配置,*selector通过反射加载spring.factories中指定的java类,也就是加载MybatisAutoConfiguration类(该类有Configuration注解,属于配置类)
点进MybatisAutoConfiguration类
①类中有个MybatisProperties类,该类对应的是mybatis的配置文件
②类中有个sqlSessionFactory方法,作用是创建SqlSessionFactory类、Configuration类 (mybatis最主要的类,保存着与mybatis相关的东西)
③SqlSessionTemplate,作用是与mapperProoxy代理类有关sqlSessionFactory主要是通过创建了一个SqlSessionFactoryBean,这个类实现了FactoryBean接口,所以在Spring容器就会注入这个类中定义的getObject方法返回的对象
1、先看MybatisAutoConfiguration#sqlSessionFactory方法
此方法主要完成全局配置的创建和封装
->SqlSessionFactoryBean#getObject 创建SqlSessionFactory
跟踪调用进入SqlSessionFactoryBean#afterPropertiesSet
接着进入org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
在此方法内封装了configuration
下面逻辑接着给configuration设置属性值,然后判断是否要解析核心配置文件
看一下xmlConfigBuilder.parse() 这里调用就会走到Mybatis内解析核心配置文件的源码
SpringBoot 中已经没有核心配置xml文件,绕过此步 接着往下跟踪
调用sqlSessionFactoryBuilder.build(configuration)进行构建sqlSessionFactory对象,在第一模块的Mybatis源码分析中我们是通过xml文件的InputSteam流来构建的sqlSessionFactory, 这里是通过配置configuration对象构建 进入build,这里就直接返回了默认的sqlSessionFactory对象
到这里可以看出来
MybatisAutoConfiguration#sqlSessionFactory主要就是封装配置 然后通过配置创建SQLSessionFactory存入到IoC容器中
2、查看SqlSessionTemplate方法
直接返回SqlSessionTemplate对象,可以看出SqlSessionTemplate是SqlSession的一个实现类,不同于DefaultSqlSession是线程不安全的,SqlSessionTemplate是线程安全的
由此可以看出MybatisAutoConfiguration主要是生成了SqlSessionFactory对象和SqlSessionTemplate对象存入容器中,同时封装好了Mybatis全局的Configuration放在SqlSessionFactory中
扫描相关的Mapper接口,产生代理对象
这个需要看这个注解
@MapperScan(basePackages = “com.mybatis.mapper”),
通过@Import的方式会导入MapperScannerRegistrar类,MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,那么在spring实例化之前就会调用到registerBeanDefinitions方法
接下向下执行可以看出将MapperScan定义的路径放入了basePackages中
最后在basePackages中路径进行扫描
跟进doScan 进入到了org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan,此方法是Spring框架扫描指定包下所有bean并封装成BeanDefinitionHolder的方法
跟进findCandidateComponents 是通过拼接路径去获取相应class文件然后读取bean
由此可以看出这个方法主要就注册扫描basePackages路径下的mapper接口,然后封装成一个BeanDefinition后加入到spring容器中
doScan执行完毕之后,会调用beanDefinition后置处理器
进入org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
跟踪到到definition.setBeanClass(this.mapperFactoryBean.getClass()); beanClass类型为UserMapper
方法执行后 beanClass类型变成了org.mybatis.spring.mapper.MapperFactoryBean
因此在后置处理器中修改了mapper的beanClass类型为MapperFactoryBean
MapperFactoryBean实现了FactoryBean接口,所以当spring从待实例化的bean容器中遍历到这个bean并开始执行实例化时返回的对象实际上是getObject方法中返回的对象。
进入getMapper方法 一路跟踪到了org.apache.ibatis.binding.MapperRegistry#getMapper
然后调用生成代理对象的方法:
到此,mapper接口现在也通过动态代理生成了实现类,并且注入到spring的bean容器中了,之后使用者就可以通过@Autowired或者getBean等方式,从spring容器中获取到了。
上述几步主要是完成通过
@MapperScan(basePackages = “com.mybatis.mapper”)这个定义,扫描指定包下的mapper接口,然后设置每个mapper接口的beanClass属性为MapperFactoryBean类型并加入到spring的bean容器中。
七、缓存自动配置
在springBoot中所有的自动配置都是 …AutoConfiguration 所以我们去搜CacheAutoConfiguration 这个类在这个类中有一个静态内部类 CacheConfigurationImportSelector 他有一个 selectImport 方法是用来给容器中添加一些缓存要用的组件;
自动配置类CacheAutoConfiguration: 它导入了CacheConfigurationImportSelector组件,跟踪进去CacheConfigurationImportSelector类:它实现了ImportSelector类 重写了selectImports方法 跟踪进去,我们在这里打上断点,debug调试一下看看 imports 中有哪些缓存组件
我们可以看到这里总共有十个缓存组件;这十个组件是从上到下生效的,我们随便去看一个会发现在他的注解上表明了什么时候使用这个组件;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class) // classpath下要存在对应的 class文件才会进行配置
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {}
每个类上都标注有@ConditionalOnMissingBean(CacheManager.class),当CacheManager不存在时,组件类才会生效,当每一个类生效时都会创建一个CacheManager,所以上述组件在自动配置时从上到下有一个生效,之后的都不会生效。
十个缓存组件:最终会发现只有 SimpleCacheConfiguration 是被使用的,所以也就说明默认情况下使用 SimpleCacheConfiguration ;然后我们进入到 SimpleCacheConfiguration 中:
ConcurrentMapCacheManager 实现了 CacheManager 接口再来看 ConcurrentMapCacheManager 的getCache方法
@Override
@Nullable
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
getCache 方法使用了双重锁校验(这种验证机制一般是用在单例模式中)我们可以看到如果没有 Cache 会调用cache = this.createConcurrentMapCache(name); 我们可以知道创建的Cache类型是ConcurrentMapCache
这个 ConcurrentMapCache 这个就是我们说的 Cache :
上图中可以知道Cache有三个重要的属性,ConcurrentMap
总结 :@Cacheable标注的方法在执行之前会先检查缓存中有没有这个数据,默认按照参数的值为key查询缓存,如果没有就运行方法并将结果放入缓存,以后再来调用时直接使用缓存中的数据。核心:
1、使用CacheManager(ConcurrentMapCacheManager)按照名字得到Cache(ConcurrentMapCache)组件
2、key使用keyGenerator生成,默认使用SimpleKeyGenerator