跨境互联网 跨境互联网
首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)

Revin

首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)
  • Spring Data JPA
  • MyBatis

  • Spring

  • SpringBoot

    • SpringBoot高级使用

    • SpringBoot源码剖析

      • 介绍
      • 源码剖析-依赖管理
      • 源码剖析-自动配置
        • @SpringBootApplication
        • @SpringBootConfiguration
        • @EnableAutoConfiguration
          • @AutoConfigurationPackage
          • @Import(AutoConfigurationImportSelector.class)
          • 深入 getCandidateConfigurations 方法
          • 关于条件注解的讲解
          • 以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理
          • 精髓
        • @ComponentScan注解
          • @ComponentScan使用
      • 源码剖析-Run方法执行流程
      • 源码剖析-自定义Starter
      • 源码剖析-内嵌Tomcat
      • 源码剖析-自动配置SpringMVC
    • 资料
  • Jdk

  • Tomcat

  • Netty

  • 若依

  • Traefik

  • Openresty

  • 开源框架
  • SpringBoot
  • SpringBoot源码剖析
Revin
2023-06-15
目录

源码剖析-自动配置

自动配置:根据我们添加的jar包依赖,会自动将一些配置类的bean注册进ioc容器,我们可以需要的地方使用@Autowired或者@Resource等注解来使用它。

问题:Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?

Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,

@SpringBootApplication:SpringBoot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main()方法启动SpringBoot应用。

# @SpringBootApplication

下面,查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下

@Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时
@Documented //表示注解可以记录在javadoc中
@Inherited  //表示可以被子类继承该注解
@SpringBootConfiguration     // 标明该类为配置类
@EnableAutoConfiguration     // 启动自动配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {


    // 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};


    // 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};


    // 指定扫描包,参数是包名的字符串数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};


    // 扫描特定的包,参数类似是Class类型数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};


}
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

从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解,关于这三个核心注解的相关说明具体如下

# @SpringBootConfiguration

@SpringBootConfiguration:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类。

查看@SpringBootConfiguration注解源码,核心代码具体如下。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented


@Configuration // 配置类的作用等同于配置文件,配置类也是容器中的一个对象
public @interface SpringBootConfiguration {
}
1
2
3
4
5
6
7
8

从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已

# @EnableAutoConfiguration

package org.springframework.boot.autoconfigure;


// 自动配置包
@AutoConfigurationPackage


// Spring的底层注解@Import,给容器中导入一个组件;
// 导入的组件是AutoConfigurationPackages.Registrar.class 
@Import(AutoConfigurationImportSelector.class)


// 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
public @interface EnableAutoConfiguration {


    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";


    // 返回不会被导入到 Spring 容器中的类
    Class<?>[] exclude() default {};


    // 返回不会被导入到 Spring 容器中的类名
    String[] excludeName() default {};


}
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

Spring 中有很多以Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的Bean,并加载到IOC容器。

@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。

# @AutoConfigurationPackage

package org.springframework.boot.autoconfigure;


@Import(AutoConfigurationPackages.Registrar.class) // 导入Registrar中注册的组件
public @interface AutoConfigurationPackage {


}
1
2
3
4
5
6
7
8

@AutoConfigurationPackage:自动配置包,它也是一个组合注解,其中最重要的注解是@Import(AutoConfigurationPackages.Registrar.class),它是Spring框架的底层注解,它的作用就是给容器中导入某个组件类,例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中,可查看Registrar类中registerBeanDefinitions方法:

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 将注解标注的元信息传入,获取到相应的包名
    register(registry, new PackageImport(metadata).getPackageName());
}
1
2
3
4
5

我们对new PackageImport(metadata).getPackageName()进行检索,看看其结果是什么?

可以发现,结果是:com.itheima 是包名

再看register方法

    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        // 这里参数 packageNames 缺省情况下就是一个字符串,是使用了注解
        // @SpringBootApplication 的 Spring Boot 应用程序入口类所在的包
        
        if (registry.containsBeanDefinition(BEAN)) {
            // 如果该bean已经注册,则将要注册包名称添加进去
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition
                    .getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0,
                    addBasePackages(constructorArguments, packageNames));
        }
        else {
            //如果该bean尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
                    packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

AutoConfigurationPackages.Registrar这个类就干一个事,注册一个Bean,这个Bean就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages,它有一个参数,这个参数是使用了@AutoConfigurationPackage这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给JPA entity扫描器用来扫描开发人员通过注解@Entity定义的entity类。

# @Import(AutoConfigurationImportSelector.class)

​ @Import({AutoConfigurationImportSelector.class}):将AutoConfigurationImportSelector这个类导入到Spring容器中,AutoConfigurationImportSelector可以帮助Springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IOC容器(ApplicationContext)中。

image-20201211151020700

可以看到AutoConfigurationImportSelector重点是实现了DeferredImportSelector接口和各种Aware接口,然后DeferredImportSelector接口又继承了ImportSelector接口。

其不光实现了ImportSelector接口,还实现了很多其它的Aware接口,分别表示在某个时机会被回调。

确定自动配置实现逻辑的入口方法:

跟自动配置逻辑相关的入口方法在**DeferredImportSelectorGrouping类的getImports**方法处,因此我们就从DeferredImportSelectorGrouping类的getImports方法来开始分析SpringBoot的自动配置源码好了。

先看一下getImports方法代码:

// ConfigurationClassParser.java


public Iterable<Group.Entry> getImports() {
    // 遍历DeferredImportSelectorHolder对象集合deferredImports,deferredImports集合装了各种ImportSelector,当然这里装的是AutoConfigurationImportSelector
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        // 【1】,利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类(这个是我们分析的重点,自动配置的逻辑全在这了)
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                deferredImport.getImportSelector());
    }
    // 【2】,经过上面的处理后,然后再进行选择导入哪些配置类
    return this.group.selectImports();
}
1
2
3
4
5
6
7
8
9
10
11
12
13

标【1】处的的代码是我们分析的重中之重,自动配置的相关的绝大部分逻辑全在这里了。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());主要做的事情就是在**this.group即AutoConfigurationGroup对象的process**方法中,传入的AutoConfigurationImportSelector对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情。

注:
AutoConfigurationGroup:是AutoConfigurationImportSelector的内部类,主要用来处理自动配置相关的逻辑,拥有process和selectImports方法,然后拥有entries和autoConfigurationEntries集合属性,这两个集合分别存储被处理后的符合条件的自动配置类,我们知道这些就足够了;
AutoConfigurationImportSelector:承担自动配置的绝大部分逻辑,负责选择一些符合条件的自动配置类;
metadata:标注在SpringBoot启动类上的@SpringBootApplication注解元数据
标【2】的this.group.selectImports的方法主要是针对前面的process方法处理后的自动配置类再进一步有选择的选择导入
1
2
3
4
5

再进入到AutoConfigurationImportSelector$AutoConfigurationGroup的process方法:


image-20201209184632370

通过图中我们可以看到,跟自动配置逻辑相关的入口方法在process方法中

分析自动配置的主要逻辑

// AutoConfigurationImportSelector$AutoConfigurationGroup.java


// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
public void process(AnnotationMetadata annotationMetadata,
        DeferredImportSelector deferredImportSelector) {
    Assert.state(
            deferredImportSelector instanceof AutoConfigurationImportSelector,
            () -> String.format("Only %s implementations are supported, got %s",
                    AutoConfigurationImportSelector.class.getSimpleName(),
                    deferredImportSelector.getClass().getName()));
    // 【1】,调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
            .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                    annotationMetadata);
    // 【2】,又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
    this.autoConfigurationEntries.add(autoConfigurationEntry); 
    // 【3】,遍历刚获取的自动配置类
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        // 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
        this.entries.putIfAbsent(importClassName, annotationMetadata); 
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

上面代码中我们再来看标【1】的方法getAutoConfigurationEntry,这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑。直接上代码:

// AutoConfigurationImportSelector.java


// 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    // 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
    // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
    // 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 【1】得到spring.factories文件配置的所有自动配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
    // 利用LinkedHashSet移除重复的配置类
    configurations = removeDuplicates(configurations);
    // 得到要排除的自动配置类,比如注解属性exclude的配置类
    // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
    // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
    checkExcludedClasses(configurations, exclusions);
    // 【2】将要排除的配置类移除
    configurations.removeAll(exclusions);
    // 【3】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤
    // 注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,后面会重点分析一下
    configurations = filter(configurations, autoConfigurationMetadata);
    // 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
    // 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
    // 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
    return new AutoConfigurationEntry(configurations, exclusions); 
}
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

# 深入 getCandidateConfigurations 方法

这个方法中有一个重要方法loadFactoryNames,这个方法是让SpringFactoryLoader去加载一些组件的名字。

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
     // 这个方法需要传入两个参数getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader()
     // getSpringFactoriesLoaderFactoryClass()这个方法返回的是EnableAutoConfiguration.class
     // getBeanClassLoader()这个方法返回的是beanClassLoader(类加载器)
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
1
2
3
4
5
6
7
8
9

继续点开loadFactory方法

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        
        //获取出入的键
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }


    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
              
                //如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装 为Enumeration类对象
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();


                //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();


                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;


                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }


                cache.put(classLoader, result);
                return result;
       }
   }
}
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

从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。

spring.factories里面保存着springboot的默认提供的自动配置类。

META-INF/spring.factories

image-20201120105556835

AutoConfigurationEntry方法主要做的事情就是获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费。我们下面总结下AutoConfigurationEntry方法主要做的事情:

【1】从spring.factories配置文件中加载EnableAutoConfiguration自动配置类),获取的自动配置类如图所示。

【2】若@EnableAutoConfiguration等注解标有要exclude的自动配置类,那么再将这个自动配置类排除掉;

【3】排除掉要exclude的自动配置类后,然后再调用filter方法进行进一步的过滤,再次排除一些不符合条件的自动配置类;

【4】经过重重过滤后,此时再触发AutoConfigurationImportEvent事件,告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类;

【5】 最后再将符合条件的自动配置类返回。

总结了AutoConfigurationEntry方法主要的逻辑后,我们再来细看一下AutoConfigurationImportSelector的filter方法:

// AutoConfigurationImportSelector.java


private List<String> filter(List<String> configurations,
            AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    // 将从spring.factories中获取的自动配置类转出字符串数组
    String[] candidates = StringUtils.toStringArray(configurations);
    // 定义skip数组,是否需要跳过。注意skip数组与candidates数组顺序一一对应
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    // getAutoConfigurationImportFilters方法:拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition
    // 然后遍历这三个条件类去过滤从spring.factories加载的大量配置类
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        // 调用各种aware方法,将beanClassLoader,beanFactory等注入到filter对象中,
        // 这里的filter对象即OnBeanCondition,OnClassCondition或OnWebApplicationCondition
        invokeAwareMethods(filter);
        // 判断各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的
        // @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配,
        // 注意candidates数组与match数组一一对应
        /**********************【主线,重点关注】********************************/
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        // 遍历match数组,注意match顺序跟candidates的自动配置类一一对应
        for (int i = 0; i < match.length; i++) {
            // 若有不匹配的话
            if (!match[i]) {
                // 不匹配的将记录在skip数组,标志skip[i]为true,也与candidates数组一一对应
                skip[i] = true;
                // 因为不匹配,将相应的自动配置类置空
                candidates[i] = null;
                // 标注skipped为true
                skipped = true; 
            }
        }
    } 
    // 这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和OnWebApplicationCondition过滤后,全部都匹配的话,则全部原样返回
    if (!skipped) {
        return configurations;
    }
    // 建立result集合来装匹配的自动配置类
    List<String> result = new ArrayList<>(candidates.length); 
    for (int i = 0; i < candidates.length; i++) {
        // 若skip[i]为false,则说明是符合条件的自动配置类,此时添加到result集合中
        if (!skip[i]) { 
            result.add(candidates[i]);
        }
    }
    // 打印日志
    if (logger.isTraceEnabled()) {
        int numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
                + " ms");
    }
    // 最后返回符合条件的自动配置类
    return new ArrayList<>(result);
}
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

AutoConfigurationImportSelector的filter方法主要做的事情就是调用AutoConfigurationImportFilter接口的match方法来判断每一个自动配置类上的条件注解(若有的话)@ConditionalOnClass,@ConditionalOnBean或@ConditionalOnWebApplication是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。

我们现在知道AutoConfigurationImportSelector的filter方法主要做了什么事情就行了,现在先不用研究的过深

# 关于条件注解的讲解

@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。

  • @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
  • @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式的条件判断。
  • @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
  • @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
  • @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
  • @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
  • @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
  • @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
  • @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
  • @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
  • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。

有选择的导入自动配置类

this.group.selectImports方法是如何进一步有选择的导入自动配置类的。直接看代码:

// AutoConfigurationImportSelector$AutoConfigurationGroup.java


public Iterable<Entry> selectImports() {
    if (this.autoConfigurationEntries.isEmpty()) {
        return Collections.emptyList();
    } 
    // 这里得到所有要排除的自动配置类的set集合
    Set<String> allExclusions = this.autoConfigurationEntries.stream()
            .map(AutoConfigurationEntry::getExclusions)
            .flatMap(Collection::stream).collect(Collectors.toSet());
    // 这里得到经过滤后所有符合条件的自动配置类的set集合
    Set<String> processedConfigurations = this.autoConfigurationEntries.stream() 
            .map(AutoConfigurationEntry::getConfigurations)
            .flatMap(Collection::stream)
            .collect(Collectors.toCollection(LinkedHashSet::new));
    // 移除掉要排除的自动配置类
    processedConfigurations.removeAll(allExclusions); 
    // 对标注有@Order注解的自动配置类进行排序,
    return sortAutoConfigurations(processedConfigurations,
            getAutoConfigurationMetadata())
                    .stream()
                    .map((importClassName) -> new Entry(
                            this.entries.get(importClassName), importClassName))
                    .collect(Collectors.toList());
}
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

可以看到,selectImports方法主要是针对经过排除掉**exclude的和被AutoConfigurationImportFilter接口过滤后的满足条件的自动配置类再进一步排除exclude的自动配置类**,然后再排序

最后,我们再总结下SpringBoot自动配置的原理,主要做了以下事情:

  1. 从spring.factories配置文件中加载自动配置类;
  2. 加载的自动配置类中排除掉@EnableAutoConfiguration注解的exclude属性指定的自动配置类;
  3. 然后再用AutoConfigurationImportFilter接口去过滤自动配置类是否符合其标注注解(若有标注的话)@ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication的条件,若都符合的话则返回匹配结果;
  4. 然后触发AutoConfigurationImportEvent事件,告诉ConditionEvaluationReport条件评估报告器对象来分别记录符合条件和exclude的自动配置类。
  5. 最后spring再将最后筛选后的自动配置类导入IOC容器中

image-20201211113449795

# 以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理

// 表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
@Configuration


// 启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;
@EnableConfigurationProperties({HttpEncodingProperties.class}) 


// Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效。
// 判断当前应用是否是web应用,如果是,当前配置类生效。并把HttpEncodingProperties加入到 ioc 容器中
@ConditionalOnWebApplication


// 判断当前项目有没有这个CharacterEncodingFilter : SpringMVC中进行乱码解决的过滤器
@ConditionalOnClass({CharacterEncodingFilter.class})


// 判断配置文件中是否存在某个配置 spring.http.encoding.enabled 如果不存在,判断也是成立的
// matchIfMissing = true 表示即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
    
    // 它已经和SpringBoot配置文件中的值进行映射了
    private final HttpEncodingProperties properties;
    
    // 只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
        this.properties = properties;
    }
    
    @Bean   //给容器中添加一个组件,这个组件中的某些值需要从properties中获取
    @ConditionalOnMissingBean({CharacterEncodingFilter.class})  //判断容器中没有这个组件
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
        return filter;
    }
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

根据当前不同的条件判断,决定这个配置类是否生效。

​ 一旦这个配置类生效,这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的。

# 我们能配置的属性都是来源于这个功能的properties类
spring.http.encoding.enabled=true
spring.http.encoding.charset=utf-8
spring.http.encoding.force=true
1
2
3
4

所有在配置文件中能配置的属性都是在 xxxProperties 类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类。

// 从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
1
2
3
4

# 精髓

  1. SpringBoot 启动会加载大量的自动配置类
  2. 我们看我们需要实现的功能有没有SpringBoot默认写好的自动配置类
  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们有我们要用的组件,我们就不需要再来配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们就可以在配置文件中指定这些属性的值。

​ xxxAutoConfiguration:自动配置类,用于给容器中添加组件从而代替之前我们手动完成大量繁琐的配置。

​ xxxProperties : 封装了对应自动配置类的默认属性值,如果我们需要自定义属性值,只需要根据xxxProperties寻找相关属性在配置文件设值即可。

# @ComponentScan注解

# @ComponentScan使用

主要是从定义的扫描路径中,找出标识了需要装配的类自动装配到spring 的bean容器中。

常用属性如下:

  • basePackages、value:指定扫描路径,如果为空则以@ComponentScan注解的类所在的包为基本的扫描路径
  • basePackageClasses:指定具体扫描的类
  • includeFilters:指定满足Filter条件的类
  • excludeFilters:指定排除Filter条件的类

includeFilters和excludeFilters 的FilterType可选:ANNOTATION=注解类型 默认、ASSIGNABLE_TYPE(指定固定类)、ASPECTJ(ASPECTJ类型)、REGEX(正则表达式)、CUSTOM(自定义类型),自定义的Filter需要实现TypeFilter接口

@ComponentScan的配置如下:

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
1
2

借助excludeFilters将TypeExcludeFillter及FilterType这两个类进行排除

当前@ComponentScan注解没有标注basePackages及value,所以扫描路径默认为@ComponentScan注解的类所在的包为基本的扫描路径(也就是标注了@SpringBootApplication注解的项目启动类所在的路径)

抛出疑问:@EnableAutoConfiguration注解是通过@Import注解加载了自动配置固定的bean

		@ComponentScan注解自动进行注解扫描
                那么真正根据包扫描,把组件类生成实例对象存到IOC容器中,又是怎么来完成的?
1
上次更新: 2025/04/03, 11:07:08
源码剖析-依赖管理
源码剖析-Run方法执行流程

← 源码剖析-依赖管理 源码剖析-Run方法执行流程→

最近更新
01
tailwindcss
03-26
02
PaddleSpeech
02-18
03
whisper
02-18
更多文章>
Theme by Vdoing | Copyright © 2019-2025 跨境互联网 | 豫ICP备14016603号-5 | 豫公网安备41090002410995号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式