目录
一、整体概述
(一)基本整体初步分析
(二)从启动来看整体过程图分析
(一)验证主配置类不为空并且保存主类
(二)推断项目的类型
(三)初始化initializers
(四)加载相关的listeners
(五)决定ApplicationClass主程序
三、SpringApplication启动过程分析
(一)监控器监听容器启动并进行图形化页面处理
(二)监听器SpringApplicationRunListeners开启监听
(三)environmentPrepared环境准备处理
(五)创建Spring应用上下文
(六)Spring应用上下文准备阶段
(七)Spring应用上下文刷新阶段
(八)Spring应用上下文收尾阶段
(九)回调工作处理
(十)SpringApplication启动异常处理
四、SpringBoot自动配置分析
(一)自动装配原理分析
(二)条件化自动装配
(三)自动配置原理举例:HttpEncodingAutoConfiguration(HTTP编码自动配置)
一、整体概述
(一)基本整体初步分析
Spring Boot 是一个用于构建独立的、生产级的 Spring 应用程序的框架,它提供了自动化的配置和约定优于配置的原则。在理解 Spring Boot 的启动配置原理之前,我们需要了解几个关键概念。
首先,Spring Boot 使用了基于约定的自动配置机制。它通过在 classpath 下查找特定的配置文件和类,根据应用程序所使用的依赖自动配置 Spring 应用程序的各种组件。这样可以大大简化开发者的工作,减少了手动配置的需求。
其次,Spring Boot 使用了条件化配置(Conditional Configuration)的机制。这意味着配置的应用取决于一组条件是否满足。条件可以基于多种因素,如 classpath 中存在特定的类、特定的 bean 是否存在等等。通过条件化配置,Spring Boot 可以根据不同的环境和需求进行动态的配置。
Spring Boot 的启动配置原理可以概括如下:
- 在启动过程中,Spring Boot 会加载并解析应用程序的配置文件,其中包括 application.properties 或 application.yml 文件等。这些文件中可以定义各种属性和配置信息,如数据库连接、日志级别等。
- Spring Boot 会自动扫描 classpath 下的特定包,寻找带有特定注解的类,如 @SpringBootApplication。这个注解标识了一个 Spring Boot 应用程序的入口点。
- 根据配置文件中的属性和条件化配置的机制,Spring Boot 自动配置应用程序的各种组件,包括数据库连接池、消息队列、Web 服务器等。如果需要进行自定义配置,可以使用专门的注解或编写自定义的配置类。
- 在应用程序启动时,Spring Boot 会初始化 Spring 容器,并根据配置进行相应的初始化工作。这包括创建和管理 bean、处理依赖注入等。
总的来说,Spring Boot 的启动配置原理是基于自动化的约定和条件化配置机制。它通过读取配置文件、扫描注解、自动配置组件等步骤,简化了应用程序的配置过程,并提供了灵活性和易用性。
(二)从启动来看整体过程图分析
每个SpringBoot程序都有一个主入口main方法,main里面调用SpringApplication.run()启动整个SpringBoot程序,该方法所在类需要使用@SpringBootApplication注解,例如如下
package org.zyf.javabasic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.***ponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* 描述:启动入口类
*
* @author yanfengzhang
* @date 2019-12-19 18:11
*/
@SpringBootApplication
@***ponentScan(basePackages = {"org.zyf.javabasic"})
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@EnableSwagger2
public class ZYFApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ZYFApplication.class, args);
}
其中对@SpringBootApplication进行展开分析:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower de***piler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.***ponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.***ponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@***ponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ***ponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ***ponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
@SpringBootApplication包括三个注解,功能如下:
- @SpringBootConfiguration(内部为@Configuration):被标注的类等于在spring的XML配置文件中(applicationContext.xml),装配所有bean事务,提供了一个spring的上下文环境
- @***ponentScan:组件扫描,可自动发现和装配Bean(比如@***ponent和@Configuration),默认扫描SpringApplication的run方法里类所在的包路径下所有文件
- @EnableAutoConfiguration:激活SpringBoot自动装配的特性
现在对主入口main方法进行展开来给出整体的流程图分析
二、SpringApplication构造过程分析
进入main里面的run方法,创建了一个SpringApplication实例,配置一些基本的环境变量、资源、构造器、监听器,进入这个SpringApplication有参构造函数
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.add***mandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
注意:在该构造方法内,做的工作就是把相关的类(主要是initializer和listener)加载进容器中,并没有执行
(一)验证主配置类不为空并且保存主类
(二)推断项目的类型
进入对应方法分析如下:
推断项目的类型可能为reactive、none、servlet三种类型,默认为servlet类型。其使用类加载器判断类型的逻辑如下
类型 |
判断情况 |
---|---|
reactive |
存在Spring WebFlux的DispatcherHandler存在,但是Spring MVC的DispatcherServlet不存在 |
none |
二者都不存在 |
servlet |
剩余所有情况 |
(三)初始化initializers
先进入ApplicationContextInitializer接口,这个接口只有一个方法initialize
package org.springframework.context;
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C var1);
}
查看实现了该这个接口的类进行分析
以SharedMetadataReaderFactoryContextInitializer实现类为例,跳转对应jar包,可以看到里面 spring.factories就在key为ApplicationContextInitializer中指定了对应的实现类,例如:
进入SharedMetadataReaderFactoryContextInitializer,其实现了ApplicationContextInitializer接口,并实现了里面的initialize方法
现在回到一开始再来看getSpringFactoriesInstance()方法,其核心为loadFactoryNames()方法
进入loadFactoryNames()方法,可以看到ApplicationContextInitializer的相关获取内容直接就是从文件“META-INF/spring.factories”中获取保存的
(四)加载相关的listeners
先进入ApplicationListener接口,这个接口只有一个方法onApplicationEvent
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
查看实现了该这个接口的类进行分析
以BackgroundPreinitializer实现类为例,跳转对应jar包,可以看到里面 spring.factories就在key为ApplicationListener中指定了对应的实现类:
进入BackgroundPreinitializer,其实现了ApplicationListener,并实现了里面的onApplicationEvent
方法如下:
现在回到一开始再来看getSpringFactoriesInstance()方法,其处理流程和上面的一样,即依然是从类路径下找到META-INF/spring.factories配置的所有ApplicationListener
(五)决定ApplicationClass主程序
进入deduceMainApplicationClass方法
三、SpringApplication启动过程分析
SpringBoot启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块以及后续的收尾回调等内容
(一)监控器监听容器启动并进行图形化页面处理
(二)监听器SpringApplicationRunListeners开启监听
直接开始分析ApringApplicationRunListeners,内含SpringApplicationRunListener的集合,其中starting方法就是对listeners进行遍历,对每个listener都调用starting方法
SpringApplicationRunListener和ApplicationListener都是SpringBoot中的事件监听器,但是它们所监听的事件和触发时机有所不同,其区别如下:
-
监听的事件不同
SpringApplicationRunListener主要监听SpringApplication运行时的各种事件,例如应用程序开始启动、应用程序启动失败、应用程序启动完成等事件。而ApplicationListener主要监听Spring容器中的各种事件,例如Bean加载完成、上下文刷新完成等事件。 -
触发时机不同
SpringApplicationRunListener在SpringApplication启动时就开始工作,可以接收到应用程序开始启动、应用程序启动失败、应用程序启动成功等各种事件。而ApplicationListener则是在Spring容器启动完成后,才能开始工作,监听的是Spring容器中的各种事件。 -
使用场景不同
在实际应用中,SpringApplicationRunListener主要用于监听SpringApplication的启动过程,例如在应用程序启动前后执行某些操作、监听应用程序启动失败事件并做出相应的操作等。而ApplicationListener则用于监听Spring容器中的各种事件,例如在Bean加载完成后做出相应的操作、在上下文刷新完成后更新一些状态等。
总之,尽管SpringApplicationRunListener和ApplicationListener都是SpringBoot中的事件监听器,但是它们所监听的事件、触发时机、使用场景等都有所不同,我们需要根据具体的应用需求,选择合适的监听器来完成应用程序的事件处理。
在进入看一下SpringApplicationRunListener这个类
package org.springframework.boot;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
public interface SpringApplicationRunListener {
void starting();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void started(ConfigurableApplicationContext context);
void running(ConfigurableApplicationContext context);
void failed(ConfigurableApplicationContext context, Throwable exception);
}
对应方法说明如下
监听方法 |
运行阶段说明 |
SpringBoot起始版本 |
---|---|---|
contextLoaded(ConfigurationApplicationEnvironment) |
ConfigurableApplicationContext完成加载,但仍未启动;通知监听器,ApplicationContext已经完成IoC配置 |
1.0 |
contextPrepared(ConfigurationApplicationEnvironment) |
ConfigurableApplicationContext准备妥当:通知监听器,ApplicationContext已经创建并初始化完成 |
1.0 |
environmentPrepared(ConfigurationEnvironment) |
ConfigurationEnvironment准备妥当,允许将其调整 |
1.0 |
failed(ConfigurationApplicationEnvironment,Throwable) |
Spring应用运行失败 |
2.0 |
running(ConfigurationApplicationEnvironment) |
Spring应用正在运行 |
2.0 |
started(ConfigurationApplicationEnvironment) |
ConfigurableApplicationContext已启动,此时SpringBean已初始化完成 |
2.0 |
starting() |
run方法执行的时候立马执行:通知监听器,SpringBoot开始执行 |
1.0 |
总的来说就是创建了应用的监听器SpringApplicationRunListeners并调用start()开始监听,先在getRunListeners中获取了所有的监听器,然后starting开启
(三)environmentPrepared环境准备处理
分析这段代码,进入准备环境的方法prepareEnvironment中,可以看到如下:
首先创建了一个环境ConfigurationEnvironment(有的话就获取,没有则创建)
回到该方法返回的地方,通过this.configureEnvironment对环境进行设置,接着如下:
环境配置好以后回调了SpringApplicationRunListener的environmentPrepared函数,进入该方法:
可以看到environmentPrepared环境准备中进行了通知监听器,Environment准备完成。
回到开始处,可以看到环境准备完成后通过bindToSpringApplication将环境绑定到程序中。
(四)banner打印
打印对应的banner信息
也就是启动后的改部分,见如下
实际中,改图可以进行替换,只需要在resources增加banner.txt信息即可,例如如下:
其中的原理可以继续点开对应后续代码分析,这个后续中讲解替换思路。
(五)创建Spring应用上下文
创建应用上下文即IOC过程,IOC容器是驱动整体SpringBoot应用组件的核心,进入该方法:
这里的创建是根据SpringApplication在构造阶段所推断的web应用类型进行IOC容器的创建,IOC容器就是run返回的内容。
(六)Spring应用上下文准备阶段
prepareContext方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联,对上下文对象进行进一步配置,进入该方法具体分析:
可以看到首先分别保存了刚才生成的environment、ApplicationContext,接下来的applyInitializers方法是执行初始化,进入该方法
方法内部遍历所有的initializer,然后依次回调里面所有的initialize方法(这些initializer就是在springboot刚启动时构造new springApplication时添加的),设置完当前环境并完成初始化之后,回调了所有Linsteners的contextPrepared方法
接下来将命令行参数和banner注册到IOC容器来,如下:
最后全部操作都完成后,这个方法回调了Listeners的contextLoaded方法,如上。
(七)Spring应用上下文刷新阶段
在该方法中首先注册了一个shutdownHook线程,用来实现SpringBean销毁生命周期回调
在执行完refresh之后的控制台,可以看到tomcat和一些IOC容器的bean都被加载进去了
(八)Spring应用上下文收尾阶段
其中afrerRefresh()并无内容处理,后续的版本中已经没有改方法了
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}
收尾计时器停止,同时调用监听器的started()。
(九)回调工作处理
进入该方法分析
ApplicationContext就是IOC容器,这个方法从IOC容器中获取了所有的ApplicationRunner和ConmmandLineRunner,接下来进行遍历和回调。
在callRunners使用的这两个类几乎可以等价,都是用于做一些客户自定义的工作,而且是整个流程完成之后才会调用用户自己定义的实现类的run方法,这两个run方法的实现方法都是在容器基本初始化好的时候调用的。
紧接着,如果无异常代码执行如下:
监听器回调running()方法代表SpringApplication正常启动结束。
(十)SpringApplication启动异常处理
发生异常主要是对异常的处理,我们进入该方法分析
可以看到这个异常报告类也是支持自定义并且自动配置的,配置结束后,Springboot做了一些基本的收尾工作,返回了应用环境上下文(IOC容器)。
四、SpringBoot自动配置分析
Spring Boot 的自动化配置模块是该框架的核心功能之一,它可以大大简化应用程序的配置工作。下面是对 Spring Boot 自动化配置模块的讲解和分析:
- 自动化配置的原理:Spring Boot 的自动化配置模块基于约定优于配置的原则。它通过在 classpath 下扫描依赖和配置,自动配置应用程序的各个组件。它使用条件化配置的机制,根据环境和条件自动选择适当的配置。
- 自动配置的实现方式:Spring Boot 自动化配置模块使用了 @Conditional 注解和条件注解来实现条件化配置。这些注解可以根据一组条件来决定是否启用某个配置。例如,@ConditionalOnClass 根据 classpath 中是否存在指定的类来判断是否启用配置。
- 自动配置的加载顺序:Spring Boot 的自动配置是通过在 classpath 下的 META-INF/spring.factories 文件中定义的自动配置类来实现的。这些自动配置类会被自动加载,并根据条件进行初始化和配置。根据条件的不同,可以有多个自动配置类被加载,它们会按照优先级顺序进行配置。
- 自动配置的自定义:Spring Boot 允许开发者对自动配置进行自定义。你可以使用 @Conditional 注解和条件注解来定义自定义的条件,从而影响自动配置的行为。你还可以使用 @EnableAutoConfiguration 注解来控制自动配置的启用或禁用。
- 自动配置的好处:Spring Boot 的自动化配置模块带来了很多好处。它大大减少了手动配置的工作量,提高了开发效率。它提供了合理的默认配置,减少了错误配置的风险。同时,它的条件化配置机制使得应用程序更具灵活性,能够根据不同的环境和需求进行动态的配置。
总的来说,Spring Boot 的自动化配置模块是该框架的重要特性之一。它通过约定优于配置的原则和条件化配置机制,实现了自动加载和配置应用程序的各个组件。这为开发者提供了便利和灵活性,并大大简化了应用程序的配置过程。
现在回到我们一开始的图示分析,该配置模块的主要使用到了SpringFactoriesLoader,即Spring工厂加载器,该对象提供了loadFactoryNames方法,入参为factoryClass和classLoader,即需要传入工厂类名称和对应的类加载器,方法会根据指定的classLoader,加载该类加器搜索路径下的指定文件,即spring.factories文件,传入的工厂类为接口,获取到这些实现类的类名后,loadFactoryNames方法返回类名集合,方法调用方得到这些集合后,再通过反射获取这些类的类对象、构造方法,最终生成实例。
(一)自动装配原理分析
从@SpringBootApplication中的@EnableAutoConfiguration注解中可以看到其import了一个自动配置导入选择器AutoConfigurationImportSelect
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
其类图如下
可以发现其最终实现了ImportSelector(选择器)和BeanClassLoaderAware(bean类加载器中间件),其中这个选择器的作用就是导入组件。
所有自动装配的逻辑都是在AutoConfigurationImportSelector里面的selectImports方法中实现的
进入getAutoConfigurationEntry()方法
进入getCandidateConfigurations()获取候选配置方法,可以看到核心的SpringFactoriesLoader的loadFactoryNames()方法
其中SpringFactoriesLoader是Spring Framework工厂机制的加载器,loadFactoryNames是其对应的加载方法,进入这个核心的loadFactoryNames方法中查看
该处加载原理如下:
- 扫描所有jar包路径下 META-INF/spring.factories,这里是通过类加载器生成对应的url路径
- 把扫描到的内容包装成properties对象,并对这个对象的内容进行遍历,返回map,map的key为接口的全类名,value为接口全部实现类列表(列表里元素去重,防止重复加载),这个value的信息后续会作为loadSpringFactories方法的返回值
- 在上一步返回的map之中查找并返回指定类名映射的实现类全类名列表
再看刚才getCandidateConfiguration方法中的getSpringFactoriesLoaderFactoryClass方法,返回的就是EnableAutoConfiguration类
也就是说要从刚才properties中再获取这个类对应的值,把它们加到容器中。
选取mybatis-spring-boot-autoconfigure下的spring.factories文件分析一下:
每一个 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中,用它们做自动配置;
只有进入到容器中,这些自动配置类才会起作用——进行自动配置功能
(二)条件化自动装配
对于使用@Configuration的自动配置类,其条件自动化装配以@condition为核心。在spring底层中的@conditional注解会根据不同的满足条件生效整个配置类里面的配置。
条件化装配可以分为以下几类:
Class条件注解
注解 |
说明 |
---|---|
@ConditionalOnClass |
指定类存在时生效 |
@ConditionalOnMissingClass |
指定类缺失时生效 |
Bean条件注解
注解 |
说明 |
---|---|
@ConditionalOnBean |
指定Bean存在时生效 |
@ConditionalOnMissingBean |
指定Bean缺失时生效 |
属性条件注解
注解 |
说明 |
---|---|
@ConditionalOnProperty |
使用属性(application.properties)的值判断是否生效 |
Web应用条件注解
注解 |
说明 |
---|---|
@ConditionalOnWebApplication |
是web类型时生效 |
@ConditionalOnNotWebApplication |
不是web类型时生效 |
其他条件注解
@Conditional扩展注解 |
作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava |
系统的java版本是否符合要求 |
@ConditionalOnExpression |
满足SpEL表达式指定 |
@ConditionalOnSingleCandidate |
容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnResource |
类路径下是否存在指定资源文件 |
@ConditionalOnJndi |
JNDI存在指定项 |
分析@ConditionalOnWebApplication
package org.springframework.boot.autoconfigure.condition;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnWebApplicationCondition.class})
public @interface ConditionalOnWebApplication {
ConditionalOnWebApplication.Type type() default ConditionalOnWebApplication.Type.ANY;
public static enum Type {
ANY,
SERVLET,
REACTIVE;
private Type() {
}
}
}
进入OnWebApplicationCondition类,里面有一个getMatchOut***e()方法,就是判断是否符合当前配置条件
该方法首先判断是否使用了这个注释,然后使用isWebApplication来判断当前是否是web应用,这些match的方法判断指定的条件成立,才会给容器添加组件,配置内容才会生效。
(三)自动配置原理举例:HttpEncodingAutoConfiguration(HTTP编码自动配置)
可以看到以下信息:
- @Configuration 代表这是一个配置类,类似编写的配置文件,也可以给容器中添加组件。
- @EnableConfigurationProperties 启用指定类的ConfigurationProperties功能,将配置文件application.properties的值和ServerProperties绑定起来,并把ServerProperties加入到IOC容器中。
- @ConditionalOnClass用来判断当前项目是否含有这个类,这里CharacterEncodingFilter的作用就是springMVC乱码解决的过滤器(以前在spring的xml文件中配置的),如果有这个过滤器则配置生效。
- @ConditionalOnProperty判断这个配置是否存在,matchIfMissing = true代表即使配置文件中不存在这个属性也是默认生效的。
需要注意的是,在spring.factories中的自动配置类不是都能生效的,都有各自的生效条件。根据当前不同条件判断,来决定这个配置类是否生效;一旦配置类生效,这个配置类就会给容器添加各种组件,这些组件的属性从对应的properties中获取,这些类里面的每一个属性又是和配置文件绑定的。