SpringBoot原理深入剖析

SpringBoot原理深入剖析

依赖管理

构建spring boot工程时,统一引入的依赖spring-boot-starter-parent

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent<11./artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

代码底层引入了spring-boot-dependencies ,核心代码如下

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>

继续深究底层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<properties>
<activemq.version>5.15.11</activemq.version>
...
<solr.version>8.2.0</solr.version>
<mysql.version>8.0.18</mysql.version>
<kafka.version>2.3.1</kafka.version>
<spring-amqp.version>2.2.2.RELEASE</spring-amqp.version>
<spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
<spring-retry.version>1.2.4.RELEASE</spring-retry.version>
<spring-security.version>5.2.1.RELEASE</spring-security.version>
<spring-session-bom.version>Corn-RELEASE</spring-session-bom.version>
<spring-ws.version>3.0.8.RELEASE</spring-ws.version>
<sqlite-jdbc.version>3.28.0</sqlite-jdbc.version>
<sun-mail.version>${jakarta-mail.version}</sun-mail.version>
<tomcat.version>9.0.29</tomcat.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-dataattribute.version>
...
</properties>

从这可以看出, 该文件通过变迁对一些常用技术框架的依赖文件进行了统一管理,以至于我们在开发过程中,不用自己引入对应的依赖

自动配置

Spring Boot应用程序入口是@SpringBootApplication 注解标注类的main()方法, @SpringBootApplication 能够扫描Spring组件并自动配置, 下面看@SpringBootApplication 内部源码剖析

1
2
3
4
5
6
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Target(ElementType.TYPE)    //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) ///表示注解的生命周期,Runtime运行时
@Documented ////表示注解可以记录在javadoc中
@Inherited //表示可以被子类继承该注解

@SpringBootConfiguration //// 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan(excludeFilters = { // 包扫描器 <context:component-scan base-package="com.xxx.xxx"/>
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}

从上述源码可以看出,@SpringBootApplication 注解是一个组合注解,主要功能由@SpringBootConfiguration̵ @EnableAutoConfiguration̵ @ComponentScan 三个核心注解组成

@SpringBootConfiguration 注解表示Spring Boot配置类, 实际是对 @Configuration 注解一个名称的改写,底层用的还是Spring提供的功能

1
2
3
4
5
6
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //配置IOC容器
public @interface SpringBootConfiguration {
}

@EnableAutoConfiguration 注解表示开启自动配置功能,该注解也是最重要的注解, 也是实现了自动化配置的注解, 核心代码如下:

1
2
3
4
5
6
7
8
9
10
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@AutoConfigurationPackage //自动配置包 : 会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中
@Import(AutoConfigurationImportSelector.class) //可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中
public @interface EnableAutoConfiguration {
...
}

可以发现,它也是一个组合注解,Spring中有很多以Enable开头的注解,其作用就算借助@Import来收集并注册特定场景相关的bean,并加载导IoC容器中.

其核心两个注解分别是

  • @AutoConfigurationPackage

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited

    //spring框架的底层注解,它的作用就是给容器中导入某个组件类,
    //例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中
    @Import(AutoConfigurationPackages.Registrar.class) // 默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中
    public @interface AutoConfigurationPackage {

    }

    其中@Import(AutoConfigurationPackages.Registrar.class) 就是将Registrar类,加载导容器中去,查看Registrar 类的registerBeanDefinitions ,就算导入过程的具体实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 获取的是项目主程序启动类所在的目录
    //metadata:注解标注的元数据信息
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    //默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
    register(registry, new PackageImport(metadata).getPackageName());
    }
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
    return Collections.singleton(new PackageImport(metadata));
    }
  • @Import({AutoConfigurationImportSelector.class})

    将AutoConfigurationImportSelector 这个类导入导容器中, AutoConfigurationImportSelector 可以借助SpringBoot应用程序将所有符合调价的@Configuration 配置加载到容器中, 继续深究AutoConfigurationImportSelector源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
    //判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
    if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
    }
    //1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
    //作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
    // SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
    // 自动配置的类全名.条件=值
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

    深究loadMetadata方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    //重载方法
    return loadMetadata(classLoader, PATH);
    }

    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
    try {
    //1.读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
    // 获得 PATH 对应的 URL 们
    Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
    // 遍历 URL 数组,读取到 properties 中
    Properties properties = new Properties();

    //2.解析urls枚举对象中的信息封装成properties对象并加载
    while (urls.hasMoreElements()) {
    properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
    }
    // 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象

    //根据封装好的properties对象生成AutoConfigurationMetadata对象返回
    return loadMetadata(properties);
    } catch (IOException ex) {
    throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
    }
    }

    回到AutoConfigurationImportSelector类 深究getAutoConfigurationEntry

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    // 1. 判断是否开启注解。如未开启,返回空串
    if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
    }
    // 2. 获得注解的属性
    AnnotationAttributes attributes = getAttributes(annotationMetadata);

    // 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
    // spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
    // 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
    // 将这些值作为自动配置类导入到容器中,自动配置类就生效了
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);


    // 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
    configurations = removeDuplicates(configurations);
    // 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
    // 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
    //找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
    checkExcludedClasses(configurations, exclusions);
    // 4.2 从 configurations 中,移除所有不希望自动配置的配置类
    configurations.removeAll(exclusions);

    // 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类

    //@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
    //@ConditionalOnMissingClass : classpath中不存在该类时起效
    //@ConditionalOnBean : DI容器中存在该类型Bean时起效
    //@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
    //@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
    //@ConditionalOnExpression : SpEL表达式结果为true时
    //@ConditionalOnProperty : 参数设置或者值一致时起效
    //@ConditionalOnResource : 指定的文件存在时起效
    //@ConditionalOnJndi : 指定的JNDI存在时起效
    //@ConditionalOnJava : 指定的Java版本存在时起效
    //@ConditionalOnWebApplication : Web应用环境下起效
    //@ConditionalOnNotWebApplication : 非Web应用环境下起效

    //总结一下判断是否要加载某个类的两种方式:
    //根据spring-autoconfigure-metadata.properties进行判断。
    //要判断@Conditional是否满足
    // 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
    configurations = filter(configurations, autoConfigurationMetadata);


    // 6. 将自动配置导入事件通知监听器
    //当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
    // 并触发fireAutoConfigurationImportEvents事件。
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 7. 创建 AutoConfigurationEntry 对象
    return new AutoConfigurationEntry(configurations, exclusions);
    }

    @EnableAutoConfiguration 就是从classpath中搜寻META-INF/spring.factories 配置文件,并将其中的的org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射,加载到容器中.

    总结

    Spring Boot底层实现自动装配的步骤是:

    1. 程序启动
    2. @SpringBootApplication 起作用
    3. @EnableAutoConfiguration
    4. @AutoConfigurationPackage 主要是@Import(AutoConfigurationPackages.Registrar.class) 通过将Registrar 类导入容器中,而Registrar 主要作用是将主配置类同级目录及子包,并将相应的组件导入到容器中
    5. @Import(AutoConfigurationImportSelector.class) 它通过将AutoConfigurationImportSelector 导入容器中, AutoConfigurationImportSelector 通过selectImports 方法的执行, 会将内部工具类SpringFactoriesLoader, 查找classpath上所有jar包中的META-INF/spring.factories 惊醒加载,并通过反射将配置类夹给SpringFactory 加载器进行一系列的容器创建过程

执行原理

下面我们查看run()方法内部的源码,核心代码具体如下:

1
2
3
4
5
6
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
//SpringApplication的启动由两部分组成:
//1. 实例化SpringApplication对象
//2. run(args):调用run方法
return new SpringApplication(primarySources).run(args);
}

从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,分别是SpringApplication实 例的初始化创建和调用run()启动项目,这两个阶段的实现具体说明如下:

实例初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = 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");

//项目启动类 SpringbootDemoApplication.class设置为属性存储起来
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
this.webApplicationType = WebApplicationType.deduceFromClasspath();

// 设置初始化器(Initializer),最后会调用这些初始化器
//所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

// 设置监听器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
this.mainApplicationClass = deduceMainApplicationClass();
}

从上述源码可以看出,SpringApplication的初始化过程主要包括4部分,具体说明如下。

  • (1) this.webApplicationType = WebApplicationType.deduceFromClasspath()

用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路 径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传 统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)

  • (2) this.setInitializers(this.getSpringFactorieslnstances(ApplicationContextlnitializer.class))

用于Spr ingApplication应用的初始化器设置。在初始化器设置过程中,会使用Sp ring类加载器

Spr ingFacto riesLoade r 从 META-INF/sp ring .facto ries 类路径下的 META-INF 下的 spr ing .facto res 文件中 获取所有可用的应用初始化器类ApplicationContextlnitialize r。

  • (3) this.setListeners(this.getSpringFactorieslnstances(ApplicationListener.class))

用于Spr ingApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样, 也是使用 Spr ingFacto riesLoade r 从 META-INF/sp ring .facto ries 类路径下的 META-INF 下的 spr ing .facto res文件中获取所有可用的监听器类ApplicationListene r。

  • (4) this.mainApplicationClass = this.deduceMainApplicationClass()

用于推断并设置项目main()方法启动的主程序启动类

项目初始化

分析完(new Spr ingApplication(p rimarySou rces)) .run()(args)源码前一部分 Spr ingApplication 实例对象 的初始化创建后,查看r un(a rgs)方法执行的项目初始化启动过程,核心代码具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public ConfigurableApplicationContext run(String... args) {
// 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 初始化应用上下文和异常报告集合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置 headless 属性
configureHeadlessProperty();


// (1)获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 创建 ApplicationArguments 对象 初始化默认应用参数类
// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

//(2)项目运行环境Environment的预配置
// 创建并配置当前SpringBoot应用将要使用的Environment
// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

configureIgnoreBeanInfo(environment);
// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = printBanner(environment);

// (3)创建Spring容器
context = createApplicationContext();
// 获得异常报告器 SpringBootExceptionReporter 数组
//这一步的逻辑和实例化初始化器和监听器的一样,
// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);


// (4)Spring容器前置处理
//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);

// (5):刷新容器
refreshContext(context);

// (6):Spring容器后置处理
//扩展接口,设计模式中的模板方法,默认为空实现。
// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
afterRefresh(context, applicationArguments);
// 停止 StopWatch 统计时长
stopWatch.stop();
// 打印 Spring Boot 启动的时长日志。
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// (7)发出结束执行的事件通知
listeners.started(context);

// (8):执行Runners
//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

// (9)发布应用上下文就绪事件
//表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
// 这样整个Spring Boot项目就正式启动完成了。
try {
listeners.running(context);
} catch (Throwable ex) {
// 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
//返回容器
return context;
}

从上述源码可以看出,项目初始化启动过程大致包括以下部分:

  • 第一步:获取并启动监听器

    this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication 实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。

  • 第二步:根据SpringApplicationRunListeners以及参数来准备环境

    this.prepareEnvironment(listeners, applicationArguments)方法主要用于对项目运行环境 进行预设置,同时通过this.configurelgnoreBeanlnfo(environment)方法排除一些不需要的运行环境

  • 第三步:创建Spring容器

    根据webApplicationType进行判断,确定容器类型,如果该类型为SERVLET类型,会通过反射装载对 应的字节码,也就是 A nnotationConfigServletWebServerApplicationContext,接着使用之前初 始化设置的context (应用上下文环境)、environment (项目运行环境)、listeners (运行监听 器)、applicationArguments (项目参数)和printedBanner (项目图标信息)进行应用上下文的组 装配置,并刷新配置

  • 第四步:Spring容器前置处理

    这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操 作:将启动类注入容器,为后续开启自动化配置奠定基础

  • 第五步:刷新容器

    开启刷新spring容器,通过refresh方法对整个IOC容器的初始化(包括bea n资源的定位,解析,注册等 等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭

  • 第六步:Spring容器后置处理

    扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启 动结束log,或者一些其它后置处理。

  • 第七步:发出结束执行的事件

    获取EventPublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器传进 去了,创建—ApplicationStartedEvent 事件,并执行 ConfigurableApplicationContext 的 publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布 事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启动事件。

  • 第八步:执行Runners

    用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中, Spring Boot提供的执行器接口有A pplicationRunner和CommandLineRunner两种,在使用时只需 要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后Spring Boot项目启动后会 立即执行这些特定程序