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 */@SpringBootApplicationpublic 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 */@SpringBootTestpublic 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)@Documentedpublic @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 */@SpringBootTestpublic 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) // 此时存在一个 Selectorpublic @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;@FunctionalInterfacepublic 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); 实现应用的启动。