SpringBoot 源码解析笔记
作者笔记仓库:https://github.com/seazean/javanotes
欢迎各位关注我的笔记仓库,clone 仓库到本地后使用 Typora 阅读效果更好。
如果大家只关注 SpringBoot 如何自动装配,可以只看“注解分析”和“装配流程”两个小节
启动流程
应用启动:
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
// 启动代码
SpringApplication.run(BootApplication.class, args);
}
}
SpringApplication 构造方法:
this.resourceLoader = resourceLoader
:资源加载器,初始为 nullthis.webApplicationType = WebApplicationType.deduceFromClasspath()
:判断当前应用的类型,是响应式还是 Web 类this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories()
:获取引导器- 去
META-INF/spring.factories
文件中找 org.springframework.boot.Bootstrapper - 寻找的顺序:classpath → spring-beans → boot-devtools → springboot → boot-autoconfigure
- 去
setInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class))
:获取初始化器- 去
META-INF/spring.factories
文件中找 org.springframework.context.ApplicationContextInitializer
- 去
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))
:获取监听器- 去
META-INF/spring.factories
文件中找 org.springframework.context.ApplicationListener
- 去
this.mainApplicationClass = deduceMainApplicationClass()
:获取出 main 程序类
SpringApplication#run(String... args):
StopWatch stopWatch = new StopWatch()
:停止监听器,监控整个应用的启停stopWatch.start()
:记录应用的启动时间bootstrapContext = createBootstrapContext()
:创建引导上下文环境bootstrapContext = new DefaultBootstrapContext()
:创建默认的引导类环境this.bootstrapRegistryInitializers.forEach()
:遍历所有的引导器调用 initialize 方法完成初始化设置
configureHeadlessProperty()
:让当前应用进入 headless 模式listeners = getRunListeners(args)
:获取所有 RunListener(运行监听器)- 去
META-INF/spring.factories
文件中找 org.springframework.boot.SpringApplicationRunListener
- 去
listeners.starting(bootstrapContext, this.mainApplicationClass)
:遍历所有的运行监听器调用 starting 方法applicationArguments = new DefaultApplicationArguments(args)
:获取所有的命令行参数environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments)
:准备环境environment = getOrCreateEnvironment()
:返回或创建基础环境信息对象switch (this.webApplicationType)
:根据当前应用的类型创建环境case SERVLET
:Web 应用环境对应 ApplicationServletEnvironmentcase REACTIVE
:响应式编程对应 ApplicationReactiveWebEnvironmentdefault
:默认为 Spring 环境 ApplicationEnvironment
configureEnvironment(environment, applicationArguments.getSourceArgs())
:读取所有配置源的属性值配置环境ConfigurationPropertySources.attach(environment)
:属性值绑定环境信息sources.addFirst(ATTACHED_PROPERTY_SOURCE_NAME,..)
:把 configurationProperties 放入环境的属性信息头部
listeners.environmentPrepared(bootstrapContext, environment)
:运行监听器调用 environmentPrepared(),EventPublishingRunListener 发布事件通知所有的监听器当前环境准备完成DefaultPropertiesPropertySource.moveToEnd(environment)
:移动 defaultProperties 属性源到环境中的最后一个源bindToSpringApplication(environment)
:与容器绑定当前环境ConfigurationPropertySources.attach(environment)
:重新将属性值绑定环境信息sources.remove(ATTACHED_PROPERTY_SOURCE_NAME)
:从环境信息中移除 configurationPropertiessources.addFirst(ATTACHED_PROPERTY_SOURCE_NAME,..)
:把 configurationProperties 重新放入环境信息
configureIgnoreBeanInfo(environment)
:配置忽略的 beanprintedBanner = printBanner(environment)
:打印 SpringBoot 标志context = createApplicationContext()
:创建 IOC 容器switch (this.webApplicationType)
:根据当前应用的类型创建 IOC 容器case SERVLET
:Web 应用环境对应 AnnotationConfigServletWebServerApplicationContextcase REACTIVE
:响应式编程对应 AnnotationConfigReactiveWebServerApplicationContextdefault
:默认为 Spring 环境 AnnotationConfigApplicationContext
context.setApplicationStartup(this.applicationStartup)
:设置一个启动器prepareContext()
:配置 IOC 容器的基本信息postProcessApplicationContext(context)
:后置处理流程applyInitializers(context)
:获取所有的初始化器调用 initialize() 方法进行初始化listeners.contextPrepared(context)
:所有的运行监听器调用 environmentPrepared() 方法,EventPublishingRunListener 发布事件通知 IOC 容器准备完成listeners.contextLoaded(context)
:所有的运行监听器调用 contextLoaded() 方法,通知 IOC 加载完成
refreshContext(context)
:刷新 IOC 容器- Spring 的容器启动流程
invokeBeanFactoryPostProcessors(beanFactory)
:实现了自动装配onRefresh()
:创建 WebServer 使用该接口
afterRefresh(context, applicationArguments)
:留给用户自定义容器刷新完成后的处理逻辑stopWatch.stop()
:记录应用启动完成的时间callRunners(context, applicationArguments)
:调用所有 runnerslisteners.started(context)
:所有的运行监听器调用 started() 方法listeners.running(context)
:所有的运行监听器调用 running() 方法获取容器中的 ApplicationRunner、CommandLineRunner
AnnotationAwareOrderComparator.sort(runners)
:合并所有 runner 并且按照 @Order 进行排序callRunner()
:遍历所有的 runner,调用 run 方法
handleRunFailure(context, ex, listeners)
:处理异常,出现异常进入该逻辑handleExitCode(context, exception)
:处理错误代码listeners.failed(context, exception)
:运行监听器调用 failed() 方法reportFailure(getExceptionReporters(context), exception)
:通知异常
注解分析
SpringBoot 定义了一套接口规范,这套规范规定 SpringBoot 在启动时会扫描外部引用 jar 包中的 META-INF/spring.factories
文件,将文件中配置的类型信息加载到 Spring 容器,并执行类中定义的各种操作,对于外部的 jar 包,直接引入一个 starter 即可
@SpringBootApplication 注解是 @Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合
@SpringBootApplication 注解
@Inherited
@SpringBootConfiguration //代表 @SpringBootApplication 拥有了该注解的功能
@EnableAutoConfiguration //同理
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
// 扫描被 @Component (@Service,@Controller)注解的 bean,容器中将排除TypeExcludeFilter 和 AutoConfigurationExcludeFilter
public @interface SpringBootApplication { }
@SpringBootConfiguration 注解:
@Configuration // 代表是配置类
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@AliasFor 注解:表示别名,可以注解到自定义注解的两个属性上表示这两个互为别名,两个属性其实是同一个含义相互替代
@ComponentScan 注解:默认扫描当前包及其子级包下的所有文件
@EnableAutoConfiguration 注解:启用 SpringBoot 的自动配置机制
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@AutoConfigurationPackage:将添加该注解的类所在的 package 作为自动配置 package 进行管理,把启动类所在的包设置一次,为了给各种自动配置的第三方库扫描用,比如带 @Mapper 注解的类,Spring 自身是不能识别的,但自动配置的 Mybatis 需要扫描用到,而 ComponentScan 只是用来扫描注解类,并没有提供接口给三方使用
@Import(AutoConfigurationPackages.Registrar.class) // 利用 Registrar 给容器中导入组件
public @interface AutoConfigurationPackage {
String[] basePackages() default {}; //自动配置包,指定了配置类的包
Class<?>[] basePackageClasses() default {};
}
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]))
:注册 BDnew PackageImports(metadata).getPackageNames()
:获取添加当前注解的类的所在包registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames))
:存放到容器中new BasePackagesBeanDefinition(packageNames)
:把当前主类所在的包名封装到该对象中
@Import(AutoConfigurationImportSelector.class):首先自动装配的核心类
容器刷新时执行:invokeBeanFactoryPostProcessors() → invokeBeanDefinitionRegistryPostProcessors() → postProcessBeanDefinitionRegistry() → processConfigBeanDefinitions() → parse() → process() → processGroupImports() → getImports() → process() → AutoConfigurationImportSelector#getAutoConfigurationEntry()
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取注解属性,@SpringBootApplication 注解的 exclude 属性和 excludeName 属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取所有需要自动装配的候选项
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去除重复的选项
configurations = removeDuplicates(configurations);
// 获取注解配置的排除的自动装配类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
// 移除所有的配置的不需要自动装配的类
configurations.removeAll(exclusions);
// 过滤,条件装配
configurations = getConfigurationClassFilter().filter(configurations);
// 获取 AutoConfigurationImportListener 类的监听器调用 onAutoConfigurationImportEvent 方法
fireAutoConfigurationImportEvents(configurations, exclusions);
// 包装成 AutoConfigurationEntry 返回
return new AutoConfigurationEntry(configurations, exclusions);
}
AutoConfigurationImportSelector#getCandidateConfigurations:获取自动配置的候选项
List<String> configurations = SpringFactoriesLoader.loadFactoryNames()
:加载自动配置类参数一:
getSpringFactoriesLoaderFactoryClass()
获取 @EnableAutoConfiguration 注解类参数二:
getBeanClassLoader()
获取类加载器factoryTypeName = factoryType.getName()
:@EnableAutoConfiguration 注解的全类名return loadSpringFactories(classLoaderToUse).getOrDefault()
:加载资源urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
:获取资源类FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
:加载的资源的位置
return configurations
:返回所有自动装配类的候选项
从 spring-boot-autoconfigure-2.5.3.jar/META-INF/spring.factories 文件中获取自动装配类,进行条件装配,按需装配
装配流程
Spring Boot 通过 @EnableAutoConfiguration
开启自动装配,通过 SpringFactoriesLoader 加载 META-INF/spring.factories
中的自动配置类实现自动装配,自动配置类其实就是通过 @Conditional
注解按需加载的配置类(JVM 类加载机制),想要其生效必须引入 spring-boot-starter-xxx
包实现起步依赖
- SpringBoot 先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类进行条件装配,默认都会绑定配置文件指定的值(xxxProperties 和配置文件进行了绑定)
- SpringBoot 默认会在底层配好所有的组件,如果用户自己配置了以用户的优先
- 定制化配置:
- 用户可以使用 @Bean 新建自己的组件来替换底层的组件
- 用户可以去看这个组件是获取的配置文件前缀值,在配置文件中修改
以 DispatcherServletAutoConfiguration 为例:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 类中的 Bean 默认不是单例
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
// 条件装配,环境中有 DispatcherServlet 类才进行自动装配
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
// 注册的 DispatcherServlet 的 BeanName
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
// 绑定配置文件的属性,从配置文件中获取配置项
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
// 给容器注册一个 DispatcherServlet,起名字为 dispatcherServlet
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
// 新建一个 DispatcherServlet 设置相关属性
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// spring.mvc 中的配置项获取注入,没有就填充默认值
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
// ......
// 返回该对象注册到容器内
return dispatcherServlet;
}
@Bean
// 容器中有这个类型组件才进行装配
@ConditionalOnBean(MultipartResolver.class)
// 容器中没有这个名字 multipartResolver 的组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
// 方法名就是 BeanName
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// 给 @Bean 标注的方法传入了对象参数,这个参数就会从容器中找,因为用户自定义了该类型,以用户配置的优先
// 但是名字不符合规范,所以获取到该 Bean 并返回到容器一个规范的名称:multipartResolver
return resolver;
}
}
}
//将配置文件中的 spring.mvc 前缀的属性与该类绑定
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties { }
内嵌容器
(补充内容,WebServer)
SpringBoot 嵌入式 Servlet 容器,默认支持的 webServe:Tomcat、Jetty、Undertow
配置方式:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion> <!--必须要把内嵌的 Tomcat 容器-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
创建 Web 容器:
SpringApplication.run(BootApplication.class, args)
:应用启动ConfigurableApplicationContext.run()
:context = createApplicationContext()
:创建容器applicationContextFactory = ApplicationContextFactory.DEFAULT
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch (webApplicationType) {
case SERVLET:
// Servlet 容器,继承自 ServletWebServerApplicationContext
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
// 响应式编程
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
// 普通 Spring 容器
return new AnnotationConfigApplicationContext();
}
} catch (Exception ex) {
throw new IllegalStateException();
}
}
applicationContextFactory.create(this.webApplicationType)
:根据应用类型创建容器
refreshContext(context)
:容器启动
内嵌容器工作流程:
- Web 应用启动,SpringBoot 导入 Web 场景包 tomcat,创建一个 Web 版的 IOC 容器 ServletWebServerApplicationContext
ServletWebServerApplicationContext 容器启动时进入 refresh() 逻辑,Spring 容器启动逻辑中,在实例化非懒加载的单例 Bean 之前有一个方法 onRefresh(),留给子类去扩展,该容器就是重写这个方法创建 WebServer
protected void onRefresh() {
//省略....
createWebServer();
}
private void createWebServer() {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
}
获取 WebServer 工厂 ServletWebServerFactory,并且获取的数量不等于 1 会报错,Spring 底层有三种:
TomcatServletWebServerFactory
、JettyServletWebServerFactory
、UndertowServletWebServerFactory
自动配置类 ServletWebServerFactoryAutoConfiguration 导入了 ServletWebServerFactoryConfiguration(配置类),根据条件装配判断系统中到底导入了哪个 Web 服务器的包,创建出服务器并启动
默认是 web-starter 导入 tomcat 包,容器中就有 TomcatServletWebServerFactory,创建出 Tomcat 服务器并启动,
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
// 初始化
initialize();
}
初始化方法 initialize 中有启动方法:
this.tomcat.start()
SpringBoot 源码解析笔记的更多相关文章
- 【spring-boot 源码解析】spring-boot 依赖管理梳理图
在文章 [spring-boot 源码解析]spring-boot 依赖管理 中,我梳理了 spring-boot-build.spring-boot-parent.spring-boot-depen ...
- SpringBoot源码解析系列文章汇总
相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的SpringBoot源码解析系列文章的汇总,当你使用SpringBoot不仅仅满足于基本使用时.或者出去面试被面试官虐了时.或者说想要深入了解一下 ...
- springboot源码解析-管中窥豹系列之总体结构(一)
一.简介 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...
- springboot源码解析-管中窥豹系列之项目类型(二)
一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...
- springboot源码解析-管中窥豹系列之Runner(三)
一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...
- springboot源码解析-管中窥豹系列之Initializer(四)
一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...
- springboot源码解析-管中窥豹系列之排序(五)
一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...
- springboot源码解析-管中窥豹系列之aware(六)
一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...
- springboot源码解析-管中窥豹系列之web服务器(七)
一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...
随机推荐
- ClickHouse学习系列之五【系统库system说明】
背景 之前介绍过ClickHouse相关的系列文章,现在ClickHouse已经能正常使用起来了,包括副本和分片.因为ClickHouse已经可以提供服务了,现在需要关心的就是服务期间该数据库的各项性 ...
- python之循环与判断
1,for 循环,语法 举例: for i in range(1, 5, 2): # 0,1,2,3,4 print(i) for a in range(5):# --(0,5,1) 0,1,2,3, ...
- 13、linux中用户和用户组
linux是多用户多进程的系统: 每个文件和进程都需要应对一个用户和用户组: linux系统通过uid和gid来识别用户和组的: 一个用户必须要有唯一的uid和一个主组来识别身份,不同的用户可以使用同 ...
- idea debug无法启动 Error running 'Tomcat8': Unable to open debugger port (127.0.0.1:50168): java.net.SocketException "socket closed
在日志里显示在 event log 里的 Error running 'server_web': Address localhost:1099 is already in use 显示1099单口已被 ...
- mui 移动端网页双击事件处理
使用场景:需要在动态生成li列表数据中添加双击事件 定义事件 : var date1=null; function listdb(myKey) { var date2 = new Date(); if ...
- 【译】GO语言:管理多个错误
原文:https://medium.com/a-journey-with-go/go-multiple-errors-management-a67477628cf1 关于开发者使用Go遇到的最大挑 ...
- elk 日志收集 filebeat 集群搭建 php业务服务日志 nginx日志 json 7.12版本 ELK 解决方案
难的不是技术,难的是业务.熟悉业务流程才是最难的. 其实搜索进来的每一个人的需求不一样,希望你能从我的这篇文章里面收获到. 建议还是看官方文档,更全面一些. 一.背景 1,收集nginx acces ...
- 修改MySQL时区
说明: 1.Windows版本暂无发现问题 2.CentOS-Docker版本需要修改时区 通过sql命令临时修改 mysql> set global time_zone = '+8:00'; ...
- 10 shell test命令
0.test命令的用法 1.与数值比较相关的test选项 2.与字符串判断相关的 test 选项 3.与文件检测相关的test选项 4.与逻辑运算相关的test选项 5.注意点与总结 1.test中变 ...
- mysql 的基础操作
1.建表 create table 表名( 字段一 数据类型 [列属性] , 字段二 数据类型 [列属性], ......... )[表类型][表字符集][注释]; 注意:MySQL命令终止符为分号 ...