1 自动配置
1.1 自动装配简介
1.1.1 概述
- 在早期使用 Spring 开发框架的时候,如果要想进行某些服务的整合,常规的做法就是引入服务有关的依赖库,而后配置一些 XML 文件以及进行组件的定义,但是在 SpringBoot 中会出现很多的 starter 。
- 项目的开发要求是不断进化的,随着时间以及技术的推移,一个项目中除了基本的编程语言之外,还需要进行大量的应用服务整合,例如:在项目中会使用到 MySQL 数据库进行持久化存储,同时会利用 Redis 实现分布式缓存,以及使用 RabbitMQ 实现异构系统整合服务,这些都需要通过 Gradle 构建工具引入相关的依赖库之后才可以整合到项目之中,为项目提供应有的服务支持。

- 在之前曾经引入过 Redis 的相关依赖,本次继续通过这个依赖来观察。
1.1.2 microboot 项目
- 修改 build.gradle 的配置,引入 Redis 的依赖:
project(':microboot-web') { // 设置子项目的配置,独享配置
dependencies { // 配置子模块依赖
implementation(project(':microboot-common')) // 引入其他子模块
// 引入 SpringBoot 的 web 依赖
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis'
implementation group: 'org.apache.commons', name: 'commons-pool2'
}
// processResources {
// apply from: "src/main/profiles/${env}/profile.gradle"
// rootProject.ext["env"] = env
// expand(project.properties)
// }
gradle.taskGraph.whenReady { // 在所有的操作准备好之后触发
tasks.each { task ->
if (task.name.contains('javadoc')) { // 如果发现有 javadoc 任务,就跳过
task.enabled = false // 当前任务不执行
}
}
}
}
1.1.3 microboot-web 子模块
- 一旦在项目之中引入相关的 starter ,那么就会出现一系列的自动配置处理类,如:在 Redis 里面它所提供的自动配置类 RedisAutoConfiguration 。
package org.springframework.boot.autoconfigure.data.redis;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration(proxyBeanMethods = false) // 定义配置 Bean
@ConditionalOnClass(RedisOperations.class) // 在当前应用中存在 RedisOperations 类的时候可以初始化
@EnableConfigurationProperties(RedisProperties.class) // 配置的属性(application.yml)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean // 向容器中注册 Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 在没有 redisTemplate 的时候注册 Bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean // 向容器中注册 Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
- 不同的模块定义一些自动配置类,而后这些配置类需要结合 application.yml 配置生效,实现最终的 Bean 的注册,当注册完毕后就可以在 Spring 容器中直接使用了。
1.2 @EnableConfigurationProperties
注解
1.2.1 概述
- 在前面的章节中讲解了如果基于配置文件(application.yml)实现 Bean 注册的操作管理,但是在 SpringBoot 里面实际上还存在有一个自动装配的注解,这个注解就是本次所要讲解的
@EnableConfigurationProperties
注解。
1.2.2 microboot 项目
- 考虑到后面会有一系列的模块整合的操作,此时需要创建一个新的模块 microboot-autoconfig-starter ,随后编辑 buid.gradle 文件进行核心依赖库的配置:
project(':microboot-autoconfig-starter') { // 设置子项目的配置,独享配置
dependencies { // 配置子模块依赖
implementation 'org.springframework.boot:spring-boot-starter-web'
// spring-boot-configuration-processor
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
}
}
1.2.3 microboot-autoconfig-starter 子模块
- 既然要实现 Bean 注册,那么首先需要创建一个类,然后设计其配置的前缀的信息项:
package com.github.fairy.era.vo;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author 许大仙
* @version 1.0
* @since 2022-01-18 14:04
*/
@Data
@ConfigurationProperties(prefix = "dept")
public class Dept {
private String deptNo;
private String deptName;
private String location;
}
- 按照以前的讲解,如果一个 Bean 类的定义上出现了
@ConfigurationProperties
注解,那么就应该定义为一个组件,否则程序将出现错误,但是此时的配置的启用并不是通过 @Component
注解完成的,而是通过另外的注解实现的,所以即使此时有错误,也请先搁置。 - 创建一个自动的装配类:
package com.github.fairy.era.config;
import com.github.fairy.era.vo.Dept;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 自动装配类
*
* @author 许大仙
* @version 1.0
* @since 2022-01-18 14:14
*/
@Configuration
@EnableConfigurationProperties(Dept.class)
public class XxxAutoConfiguration {
}
package com.github.fairy.era;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author 许大仙
* @version 1.0
* @since 2022-01-18 14:16
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 创建 application.yml 配置,进行属性的定义:
dept:
dept-no: 1
dept-name: 开发部
location: 上海
package com.github.fairy.era;
import com.github.fairy.era.vo.Dept;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author 许大仙
* @version 1.0
* @since 2022-01-18 14:18
*/
@SpringBootTest
public class ApplicationTest {
@Autowired
private Dept dept;
@Test
public void test() {
System.out.println("dept = " + dept);
}
}

- 此时程序之中所有对应的 Bean 的属性内容就已经成功的保存。
1.3 @Import
注解
1.3.1 概述
- 之前已经分析过了
@EnableConfigurationProperties
注解的使用,下面需要查看这个注解的源代码:
package org.springframework.boot.context.properties;
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.Bean;
import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class) // @Import 注解
public @interface EnableConfigurationProperties {
String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
Class<?>[] value() default {};
}
- 可以知道,此时使用
@Import
注解就是将 Bean 注册到 Spring 的容器中,需要注意的是,@Import
注解支持三种不同的处理形式:类导入、ImportSelector 导入 、ImportBeanDefinitionRegistrar 导入。
package org.springframework.context.annotation;
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;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import { // 此注解相当于早期 XML 时代的 <import/>标签,此注解也可以进行大面积 Bean 的导入
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
1.3.2 方式一
package com.github.fairy.era.config;
import com.github.fairy.era.vo.Dept;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 自动装配类
*
* @author 许大仙
* @version 1.0
* @since 2022-01-18 14:14
*/
@Configuration
@Import(Dept.class)
public class XxxAutoConfiguration {
}
dept = Dept(deptNo=1, deptName=开发部, location=上海)
- 对于此时的 Dept 类来说,并没有实现任何的 Bean 定义,但是由于使用了
@Import
注解,所以也实现了 Bean 的注册。
1.3.3 方式二
- 如果此时需要注入的 Bean 很多,如果按照上面的方式去编写就需要写上所有注册的 Bean 的名称,这样就可以使用 ImportSelector 来进行简化。
package com.github.fairy.era.selector;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author 许大仙
* @version 1.0
* @since 2022-01-18 14:50
*/
public class XxxImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.github.fairy.era.vo.Dept"}; // 导入类的全名称
}
}
package com.github.fairy.era.config;
import com.github.fairy.era.selector.XxxImportSelector;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 自动装配类
*
* @author 许大仙
* @version 1.0
* @since 2022-01-18 14:14
*/
@Configuration
@Import(XxxImportSelector.class)
public class XxxAutoConfiguration {
}
dept = Dept(deptNo=1, deptName=开发部, location=上海)
1.3.4 方式三
- 以上的操作实际上都是由 Spring 容器负责了 Bean 的注册,如果开发者希望可以自己来进行一些 Bean 注册处理,则可以使用 ImportBeanDefinitionRegistrar 。
package com.github.fairy.era.registrar;
import com.github.fairy.era.vo.Dept;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author 许大仙
* @version 1.0
* @since 2022-01-18 15:01
*/
public class DefaultImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Dept.class);
registry.registerBeanDefinition("dept", rootBeanDefinition);
}
}
package com.github.fairy.era.config;
import com.github.fairy.era.registrar.DefaultImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 自动装配类
*
* @author 许大仙
* @version 1.0
* @since 2022-01-18 14:14
*/
@Configuration
@Import(DefaultImportBeanDefinitionRegistrar.class)
public class XxxAutoConfiguration {
}
dept = Dept(deptNo=1, deptName=开发部, location=上海)
1.4 自定义 starter 组件
1.4.1 概述
- 在之前分析了很多的程序实现注解,包括自动提示配置,实际上最终的目的就是希望将当前的模块交由其他的模块去使用,这就是 starter 的目的所在。
- 利用自动配置类可以在容器启动的时候自动的进行 Bean 的注册,由于不同的项目会存在不同的自动配置类,所以为了便于其他模块的引用,就需要将配置类在 spring.factories 之中进行注册管理。
1.4.2 microboot-autoconfig-starter 子模块
- 考虑到整合的操作形式,建议多追加一个 Bean 的注册。
package com.github.fairy.era.config;
import com.github.fairy.era.vo.Dept;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* 自动装配类
*
* @author 许大仙
* @version 1.0
* @since 2022-01-18 14:14
*/
@Configuration
@EnableConfigurationProperties(Dept.class)
public class XxxAutoConfiguration {
/**
* 自定义注册 Bean 对象
*
* @return
*/
@Bean(name="books")
public List<String> books() {
List<String> books = new ArrayList<>();
books.add("Java 从入门到入土");
books.add("C 从入门到精通");
books.add("SpringBoot 就业编程实战");
books.add("SpringCloud 从入门到精通");
return books;
}
}
- 如果要想让自动配置类生效,还需要一个特定的配置文件 src/main/resources/META/spring.factories :
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.fairy.era.config.XxxAutoConfiguration
plugins {
id 'idea'
id 'java'
}
jar {
enabled = true // 允许当前的模块打包为 *.jar 文件
}
javadocTask {
enabled = false // 不启用 javadoc 任务
}
javadocJar {
enabled = false // 不启用 javadoc 的 *.jar 文件
}
bootJar {
enabled = false // 不执行 SpringBoot 的打包任务
}
1.4.3 microboot 项目
- 修改 build.gradle 配置文件,为 microboot-web 子模块添加新的依赖支持:
project(':microboot-web') { // 设置子项目的配置,独享配置
dependencies { // 配置子模块依赖
implementation(project(':microboot-common')) // 引入其他子模块
implementation(project(':microboot-autoconfig-starter')) // 引入其他子模块
// 引入 SpringBoot 的 web 依赖
implementation 'org.springframework.boot:spring-boot-starter-web'
}
gradle.taskGraph.whenReady { // 在所有的操作准备好之后触发
tasks.each { task ->
if (task.name.contains('javadoc')) { // 如果发现有 javadoc 任务,就跳过
task.enabled = false // 当前任务不执行
}
}
}
}
- 修改 application.yml 配置文件,给 Dept 对象设置属性:
dept:
dept-no: 1
dept-name: 开发部
location: 北京
package com.github.fairy.era;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* @author 许大仙
* @version 1.0
* @since 2022-01-04 09:36
*/
@SpringBootTest
public class ApplicationTest {
@Autowired
private List<String> books;
@Test
public void test() {
System.out.println("books = " + books);
}
}
books = [Java 从入门到入土, C 从入门到精通, SpringBoot 就业编程实战, SpringCloud 从入门到精通]
2 SpringBoot 启动分析
2.1 SpringBoot 启动核心类
2.1.1 概述
- 为什么在 SpringBoot 程序开发之中,只需要按照规定的结构(指的是包结构)进行代码的编写,就可以实现自动的装配处理;而早期的 Spring 开发框架往往需要通过手工配置的模式来进行扫描包的处理,此时就需要对 SpringBoot 的运行有一个基本的了解。
- 只要是 SpringBoot 应用程序,都需要在根包下创建一个应用的启动类,而常用的启动类的形式如下:
package com.github.fairy.era;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动类
*
* @author 许大仙
* @version 1.0
* @since 2021-12-31 09:14
*/
@SpringBootApplication // 注解部分
public class Application {
public static void main(String[] args) {
// 程序部分
SpringApplication.run(Application.class, args);
}
}
- 如果要去理解 SpringBoot 的启动流程,实际上就需要分为两个阶段来理解:第一个阶段是注解操作部分,另外一个阶段才是具体的程序的部分。
2.1.2 @SpringBootApplication
注解
- 本次首先研究一个注解的基本使用形式,打开
@SpringBoot
注解的源代码来进行观察:
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.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.repository.Repository; // 这边有错误
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // SpringBoot 的配置注解
@EnableAutoConfiguration // 启用自动配置
@ComponentScan(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 = ComponentScan.class, attribute = "basePackages") // 定义别名
String[] scanBasePackages() default {}; // 定义扫描包
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") // 定义别名
Class<?>[] scanBasePackageClasses() default {}; // 定义扫描类
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator") // 定义别名
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class) // 定义别名
boolean proxyBeanMethods() default true; // 扫描模式
}
- 在 SpringBoot 启动类的注解之中就会发现两个主要的注解:
@SpringBootConfiguration
注解:SpringBoot 的启动代理模式配置。@EnableAutoConfiguration
注解:启用自动装配。
2.1.3 @SpringBootConfiguration
注解
- 观察
@SpringBootConfiguration
注解,其源码如下:
package org.springframework.boot;
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.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
- 可以发现
@SpringBootConfiguration
注解和 @Configuration
等效,所以被 @SpringBootConfiguration
注解标注的类就是一个配置类。
2.1.4 @EnableAutoConfiguration
注解。
- 观察
@EnableAutoConfiguration
注解,其源码如下:
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.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.SpringFactoriesLoader;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 自动配置包
@Import(AutoConfigurationImportSelector.class) // 此时存在一个 Selector
public @interface EnableAutoConfiguration {
/**
* 定义了一个所需要的环境属性名称
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 排除
*/
Class<?>[] exclude() default {};
/**
* 排除
*/
String[] excludeName() default {};
}
- 在这个注解之中可以发现有一个
@AutoConfigurationPackage
(主要进行包扫描配置处理的) 和 @Import
的注解(导入的是 AutoConfigurationImportSelector)。
2.1.5 @AutoConfigurationPackage
注解
- 观察
@AutoConfigurationPackage
注解,其源码如下:
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
@Import(AutoConfigurationPackages.Registrar.class) // 设置了 Registrar 导入器
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
- 可以发现自动导入了 Registrar 导入器,打开 AutoConfigurationPackages.Registrar 的源代码,内容如下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) { // 判断是否有指定 BEAN
BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
// 将 Bean 对应的包进行扫描配置
beanDefinition.addBasePackages(packageNames);
}
else {
registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
}
}
private static final String BEAN = AutoConfigurationPackages.class.getName();
- 通过上面的分析可以发现自动扫描的时候可以通过一系列的 Selector 实现最终扫描包的配置,但是如果要想继续研究需要观察 AutoConfigurationImportSelector 的源代码,这个类会集成大量的父类,如果要想研究其核心,肯定要以 Selector 为主,观察这个类的继承结构。
package org.springframework.boot.autoconfigure;
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
// 其他略
}
- 此时重点观察的是 DeferredImportSelector 接口,因为该类主要用于
@Import
注解之中,所以只需要观察其如何配置扫描 Bean 注册即可,观察核心方法重写:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 通过指定的 Annotation 获取对应属性的内容
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);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
// 其他略
}
- 最终所有的扫描配置实际上都是通过这个类实现管理的,即开发者只需要在启动类中配置
@SpringBootApplication
注解会由具体的 Selector 来负责排除以及扫描包的定义。
2.2 SpringApplication 构造方法
2.2.1 概述
- 在 SpringBoot 里面除了注解之外,随后最为重要的部分就是 SpringBoot 的启动方法:
SpringApplication.run(Application.class, args);
- 此时就需要打开这个方法来观察,此时是通过 SpringApplication 类中提供的静态方法 run() 方法来完成的:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
- SpringApplication 类的对象实例是由 run 方法来完成实例化的处理,对于开发者来讲首先要关注的就是这个类的构造方法的具体定义了。
2.2.2 SpringApplication 类的构造方法
// primarySources = Application ,就是启动类
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
// primarySources = Application ,就是启动类
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader; // 获取资源加载器:加载 classpath 、文件、网络资源
Assert.notNull(primarySources, "PrimarySources must not be null"); // 断言检查
// 将传递过来的程序启动类的 Class 对象按照顺序保存在一个集合之中
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
private ResourceLoader resourceLoader; // ResourceLoader 接口实例
private Set<Class<?>> primarySources; // 所有的主要类加载源
private WebApplicationType webApplicationType; // WEB 应用类型
private List<Bootstrapper> bootstrappers; // 所有的类加载器
private Class<?> mainApplicationClass; // 获取程序的主类
2.2.3 分析程序的应用主类
private Class<?> deduceMainApplicationClass() { // 分析应用主类
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) { // 程序的启动方法
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
- 根据整个类的结构来对程序的结构进行分析,如果发现有主方法(main 方法)存在,则直接返回该类的 Class 对象,最终获取到了程序启动的应用主类。
2.2.4 WebApplicationType
- SpringBoot 应用开发可以有两种主要的处理形式:一种是基于传统的 WebServlet 编程实现的,另外一种是基于 Reactive 编程实现的。为了可以区分出不同的运行模式,就创建了一个 WebApplicationType 的枚举类型,其源代码如下:
public enum WebApplicationType {
NONE, // 没有运行的容器,程序没有执行,这点一般不考虑
SERVLET, // 采用传统的 WebServlet 模式运行
REACTIVE; // 采用响应式编程的模式运行
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet"; // SpringMVC 启动类
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler"; // Reactor 启动类
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE; // 采用 Reactive 模式运行
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET; // 采用 Servlet 模式运行
}
// 其他略
}
- 这个类会通过一系列的而处理方法进行各种逻辑的判断,来推断出程序是以 Servlet 模式还是以 Reactive 模式运行。
- 在 SpringApplication 构造方法里面有一个最为重要的方法:
getSpringFactoriesInstances(Bootstrapper.class)
,其主要功能是进行所有的自动配置的加载(META-INF/spring.factories)。
2.2.5 getSpringFactoriesInstances
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 获取类加载器
ClassLoader classLoader = getClassLoader();
// 自动配置类的加载
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
2.2.6 loadFactoryNames
- 对 META-INF/spring.factories 中的自动配置类读取。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
2.2.7 getSpringFactoriesInstances
- 将 META-INF/spring.factories 中的自动配置类注册到 Spring 容器中。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
2.2.8 总结
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
- 通过以上的分析就可以得到以结论:SpringApplication 类中的构造方法主要完成了如下的几件事情:
- ① 获取 ResourceLoader 接口实例,获取此接口实例的目的是为了便于类的加载操作。
- ② 获取所有主源类,只要一个是主类,还需要对主类的结构进行分析。
- ③ 会自动分析当前的运行模式是 Servlet 还是 ReactIve 。
- ⑤ 获取所有的类加载器(包括 AutoConfiguration + Starter 配置的类加载器)。
- ⑥ 初始化所有的 Bean 对象并且自动进行 Bean 注册。
- ⑦ 设置所有的监听操作,包括事件监听等。
2.3 SpringApplication.run() 方法
2.3.1 概述
- 在整个 SpringBoot 类名除了使用 SpringApplication 进行构造方法的初始化之外,最重要的一项就是 run() 方法,这个方法可以实现所有的相关核心处理,于是开始分析 run() 方法。
SpringApplication.run(Application.class, args);
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
2.3.2 run() 方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); // ① 实现任务的启动耗时统计
stopWatch.start(); // 任务启动时执行
DefaultBootstrapContext bootstrapContext = createBootstrapContext(); // ② 创建类加载上下文
ConfigurableApplicationContext context = null;
configureHeadlessProperty(); // ③ 属性配置
SpringApplicationRunListeners listeners = getRunListeners(args); // ④ 获取全部的监听器
listeners.starting(bootstrapContext, this.mainApplicationClass);// ⑤ 启动全部监听器
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // ⑥ Profile 环境配置
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment); // ⑦ 打印 Banner 信息
context = createApplicationContext(); // ⑧ 创建应用上下文
context.setApplicationStartup(this.applicationStartup); // ⑨ 启动上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 容器启动前置处理
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, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
(step) -> {
if (mainApplicationClass != null) {
step.tag("mainApplicationClass", mainApplicationClass.getName());
}
});
}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
configureAdditionalProfiles(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
- 总结:通过以上的程序分析可以发现,所有在 SpringBoot 之中的容器的启动都是通过 run() 方法实现的,而 run() 方法在实现处理的时候一定会自动启动 Spring 容器。
2.4 启动内置 WEB 容器
2.4.1 概述
- SpringBoot 应用一旦启动之后,就会启动内置的 WEB 服务器,如:Tomcat、Jetty、Undertow,容器的启动实际上都是由 run() 方法完成的。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 分析重点
context = createApplicationContext();
// 分析重点
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
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, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
- 如果要想启动内置的 WEB 容器,肯定是依靠 createApplicationContext() 方法完成的,本次观察这个方法的具体实现。
private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
2.4.2 ApplicationContextFactory
- 容器的启动主要依据的是 ApplicationContextFactory 的对象实例,其源代码如下:
package org.springframework.boot;
import java.util.function.Supplier;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@FunctionalInterface
public interface ApplicationContextFactory {
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch (webApplicationType) { // 判断 WEB 应用类型
case SERVLET: // 普通的 Servlet 模式
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE: // 响应式 Reactive 模式
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
}
catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
};
ConfigurableApplicationContext create(WebApplicationType webApplicationType);
static ApplicationContextFactory ofContextClass(Class<? extends ConfigurableApplicationContext> contextClass) {
return of(() -> BeanUtils.instantiateClass(contextClass));
}
static ApplicationContextFactory of(Supplier<ConfigurableApplicationContext> supplier) {
return (webApplicationType) -> supplier.get();
}
}
- 在使用 DEFAULT 内置的实例化对象创建应用程序的时候,会判断当前的应用类型是 SERVLET 还是 REACTIVE,本次就针对于 SERVLET 进行分析。
2.4.3 AnnotationConfigServletWebServerApplicationContext
package org.springframework.boot.web.servlet.context;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigRegistry;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.AnnotationScopeMetadataResolver;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ScopeMetadataResolver;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
implements AnnotationConfigRegistry {
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
private final Set<Class<?>> annotatedClasses = new LinkedHashSet<>();
private String[] basePackages;
public AnnotationConfigServletWebServerApplicationContext() { // ① 无参构造方法
this.reader = new AnnotatedBeanDefinitionReader(this); // 读取注解配置的 Bean
this.scanner = new ClassPathBeanDefinitionScanner(this); // 读取 Classpath 扫描的 Bean
}
public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
/**
*/
public AnnotationConfigServletWebServerApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}
/**
*/
public AnnotationConfigServletWebServerApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
/**
* Profile 配置项
*/
@Override
public void setEnvironment(ConfigurableEnvironment environment) {
super.setEnvironment(environment);
this.reader.setEnvironment(environment);
this.scanner.setEnvironment(environment);
}
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { // Bean 的配置
this.reader.setBeanNameGenerator(beanNameGenerator);
this.scanner.setBeanNameGenerator(beanNameGenerator);
getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
}
public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) {
this.reader.setScopeMetadataResolver(scopeMetadataResolver);
this.scanner.setScopeMetadataResolver(scopeMetadataResolver);
}
@Override
public final void register(Class<?>... annotatedClasses) { // 注册管理
Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
this.annotatedClasses.addAll(Arrays.asList(annotatedClasses));
}
@Override
public final void scan(String... basePackages) { // 扫描处理
Assert.notEmpty(basePackages, "At least one base package must be specified");
this.basePackages = basePackages;
}
@Override
protected void prepareRefresh() { // 刷新处理
this.scanner.clearCache();
super.prepareRefresh();
}
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
super.postProcessBeanFactory(beanFactory);
if (this.basePackages != null && this.basePackages.length > 0) {
this.scanner.scan(this.basePackages);
}
if (!this.annotatedClasses.isEmpty()) {
this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
}
}
}
- 所有启动上下文环境初始化完成之后,随后就需要进行最终的启动处理了,而在 run() 方法里面通过以下方法
context.setApplicationStartup(this.applicationStartup);
实现应用的启动。