《SpringBoot启动流程六》:SpringBoot自动装配时做条件装配的原理(万字图文源码分析)(含@ConditionalOnClass原理)

简介: 《SpringBoot启动流程六》:SpringBoot自动装配时做条件装配的原理(万字图文源码分析)(含@ConditionalOnClass原理)

@[TOC]

一、前言

我们前六篇博文,详细讨论了SpringBoot整个启动流程、自动装配。博文如下:

1> 《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息
2> 《SpringBoot启动流程二》:七千字源码分析SpringApplication构造阶段
3> 《SpringBoot启动流程三》:两万+字图文带你debug源码分析SpringApplication准备阶段(含配置文件加载时机、日志系统初始化时机)
4> 《SpringBoot启动流程四》:图文带你debug源码分析SpringApplication运行阶段和运行后阶段
5> 《SpringBoot启动流程五》:你确定你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)
6> SpringBoot自动装配机制原理

《SpringBoot启动流程五》:你确定你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)一文中聊了@EnableAutoConfiguration@Import注解是在何时被扫描的?所有的自动自动装配类是何时被扫描/加载的?也埋了一个坑:为什么127个自动装配类经过AutoConfigurationImportFilter过滤后只剩23个了?

本文我们就这个坑,聊一下Spring自动装配中自动装配类是如何实现条件装配的!!

注:Spring Boot版本:2.3.7.RELEASE。

二、入口

在这里插入图片描述
接着上篇博文《SpringBoot启动流程五》:你确定你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)中自动装配入口的代码执行流程图来看;核心在于AutoConfigurationImportSelectorgetAutoConfigurationEntry(AnnotationMetadata)方法中。

其负责拿到所有自动配置的节点,大致分为六步;

  1. 第一步,在getCandidateConfigurations()方法中利用Spring Framework工厂机制的加载器SpringFactoriesLoader,通过SpringFactoriesLoader#loadFactoryNames(Class, ClassLoader)方法读取所有META-INF/spring.factories资源中@EnableAutoConfiguration所关联的自动装配Class集合。
  2. 第二步,利用Set不可重复性对自动装配Class集合进行去重,因为自动装配组件存在重复定义的情况;
  3. 第三步,读取当前配置类所标注的@EnableAutoConfiguration注解的属性exclude和excludeName,并与spring.autoconfigure.exclude配置属性的值 合并为自动装配class排除集合
  4. 第四步,校验自动装配Class排除集合的合法性、并排除掉自动装配Class排除集合中的所有Class(不需要自动装配的Class)。
  5. 第五步,再次过滤后候选自动装配Class集合中不符合条件装配的Class成员;
  6. 最后一步,触发自动装配的导入事件。

第五步> getConfigurationClassFilter().filter(configurations) --> 过滤候选自动装配Class集合中不符合条件装配的Class成员:

这里分两步:

1)获取所有的Filter在这里插入图片描述

首先通过getConfigurationClassFilter()方法从所有META-INF/spring.factories文件中获取所有的自动装配过滤器AutoConfigurationImportFilter的实现类(一共有三个),然后实例化AutoConfigurationImportSelector类的内部类ConfigurationClassFilter,并将获取多的所有AutoConfigurationImportFilter的实现类集合赋值到ConfigurationClassFilter的filters属性中(后面会用到它做条件装配)。
在这里插入图片描述

2)执行条件装配

然后对获取到的所有自动装配类(最干净、最简单的SpringBoot程序有127个)执行过滤操作(条件装配)后,还剩23个自动装配类。
在这里插入图片描述
那么是如何做的条件装配呢?

三、条件装配原理

在这里插入图片描述
首先遍历在getConfigurationClassFilter()方法中获取到的三个过滤器:OnClassCondition、OnWebApplicationCondition、OnBeanCondition;分别执行他们的match()方法做条件装配,最后将符合条件的结果放入到一个List集合中返回。

下面以实际样例来看一下,这三个过滤器是如何对自动装配类做条件装配的?
在这里插入图片描述
无论是OnClassCondition、OnWebApplicationCondition 还是OnBeanCondition他们都继承自FilteringSpringBootCondition类,并且它们三个之中都没有重写FilteringSpringBootCondition#match(String[], AutoConfigurationMetadata)方法,所以条件装配判定逻辑的入口统一为FilteringSpringBootCondition#match(String[], AutoConfigurationMetadata)
在这里插入图片描述
其中调用的getOutcomes(String[],AutoConfigurationMetadata)方法里针对不同的FilteringSpringBootCondition子类而实现逻辑不同,下面分开看一下。

1、OnClassCondition

OnClassCondition#getOutComs()方法代码执行流程如下:
在这里插入图片描述
resolveOutcomesThreaded()方法源码解释如下:

private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    // 将自动装配类的条件装配一分为2进行处理,
    int split = autoConfigurationClasses.length / 2;
    // 这里会开启一个线程处理前半部分的自动装配类
    OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
            autoConfigurationMetadata);
    OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
            autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
    // 当前线程处理后半部分的自动装配类
    ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
    // 这里调用thread.join()方法等待上面开启的线程run()执行完毕(即:处理完前半部分自动装配类)
    ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
    // 最后将处理结果合并 并 返回。
    ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
    System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
    System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
    return outcomes;
}

这里挺有意思的,它首先将所有的自动装配类一分为二,前半部分开启一个线程进行处理、后半部分当前线程处理,后半部分处理完之后,调用thread.join()等待thread.join()方法等待上面开启的线程run()执行完毕(即:处理完前半部分自动装配类)。最后合并处理结果并返回。

1> 创建线程处理前半部分自动装配类:
在这里插入图片描述
2> 处理后半部分自动装配类:

从这里我们来看条件装配的细节;

1)条件装配逻辑判断

代码逻辑在OnClassCondition静态内部类StandardOutcomesResolver#resolveOutcomes()方法中:
在这里插入图片描述
在getOutcomes(String[],int,int,AutoConfigurationMetadata)方法中:

遍历传入的所有自动装配类名称;

  1. 首先获取自动装配类autoConfigurationClass上@ConditionalOnClass注解中的值;如果获取不到,继续下一个循环。
  2. 获取到相应的类,则通过类加载机制判断其是否存在,如果能加载到则返回。。。。 否则返回null。这里使用类加载机制判断Class是否存在时,不会对Class执行初始化操作。

org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration类为例,细看一下其@ConditionalOnClass条件装配逻辑;

在这里插入图片描述
JmxAutoConfiguration类上被@ConditionalOnClass({ MBeanExporter.class })注解标注,所以获取到的candidates局部变量值为:MBeanExporter

接着来看如何判断MBeanExporter.class类是否存在,代码执行流程如下:
在这里插入图片描述
代码流程解析:

  1. 根据当前ClassLoader使用反射Class.forNam()加载类:如果能加载到,则给最上层的getOutComes(String)方法返回FALSE;如果加载不到返回一个ConditionOutcome对象,内容为:@ConditionalOnClass did not find required class 'org.springframework.jms.core.JmsTemplate'。(JmsTemplate为某自动装配类,是一个变量)
  2. 如果@ConditionalOnClass中存在多个Class,则for循环判断,只要有一个Class不存在,则直接返回返回一个ConditionOutcome对象,内容为:@ConditionalOnClass did not find required class 'org.springframework.jms.core.JmsTemplate'。(JmsTemplate为某自动装配类,是一个变量)

JmxAutoConfiguration而言,其可以被自动装配。
在这里插入图片描述
最后返回到条件装配判定逻辑的入口统一FilteringSpringBootCondition#match(String[], AutoConfigurationMetadata)的体现为:
在这里插入图片描述
JmxAutoConfiguration而言,其在String[] autoConfigurationClasses数组中的下标位置为63,而返回的boolean[] match数组下标位置63处为TRUE,表示其可以被自动装配。

==此外:SpringBoot提供了两个基于Class的条件注解:一个是@ConditionalOnClass(类加载器中存在指定的类),另一个是@ConditionalOnMissingClass(类加载器中不存在指定的类),@ConditionalOnClass条件装配的原理我们知道了,@ConditionalOnMissingClass我们同样也就知道了。区别在于@ConditionalOnMissingClass加载到类之后给最上层的getOutComes(String)方法返回TRUE。==

OnClassCondition过滤完,候选的自动装配类还剩26个:
在这里插入图片描述
继续进入第二个过滤器OnWebApplicationCondition开始第二轮过滤;

2、OnWebApplicationCondition

OnWebApplicationCondition的执行逻辑和OnClassCondition一样,区别在于:

  1. OnClassCondition判断的是@ConditionalOnClass注解,OnWebApplicationCondition判断的是@ConditionalOnWebApplication注解。
  2. OnClassCondition依靠类加载机制判断@ConditionalOnClass注解value属性中的Class是否存在;OnWebApplicationCondition同样也是依靠类加载机制,但是判断的内容要少很多,其仅仅判断<ANY、SERVLET、REACTIVE>三种应用类型对应的类是否存在。

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration而言:
在这里插入图片描述
其没被@ConditionalOnWebApplication注解标注,所以在getOutcome(String type)方法中直接返回null,否者需要走应用类型的判断逻辑。
在这里插入图片描述
上图的两个常量如下:

// SERVLET应用特有类
private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";

// Reactive应用特有类
private static final String REACTIVE_WEB_APPLICATION_CLASS = "org.springframework.web.reactive.HandlerResult";

如果这两个类同时存在,优先走SERVLET应用类型,这个在之前的博文(SpringBoot应用分类?)聊过。

最后SpringApplicationAdminJmxAutoConfiguration类可以被自动装配;
在这里插入图片描述

OnWebApplicationCondition过滤完,候选的自动装配类还剩24个:
在这里插入图片描述
继续进入第三个过滤器OnBeanCondition开始第三轮过滤;

3、OnBeanCondition

OnBeanCondition#getOutcomes(String[],AutoConfigurationMetadata)方法执行逻辑如下:

遍历所有的(24个)候选自动装配类;

  1. 首先获取自动装配类autoConfigurationClass上@OnBeanCondition注解中的值;如果获取不到,继续下一个循环。
  2. 获取到相应的类,则通过类加载机制判断其是否存在,如果能加载到则返回。。。。 否则返回null。这里使用类加载机制判断Class是否存在时,不会对Class执行初始化操作。

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration类为例,细看一下其@OnBeanCondition条件装配逻辑;
在这里插入图片描述

CacheAutoConfiguration类上被@ConditionalOnBean(CacheAspectSupport.class)注解标注,所以获取到的onBeanTypes局部变量值为:CacheAspectSupport

接着来看如何判断CacheAspectSupport类的Bean实例是否存在,代码执行流程如下:
在这里插入图片描述

看到最后我人傻了,卧槽,这和我学的@ConditionalOnBean注解的含义不一样啊,它不是要判断指定的Class是否已经被实例化存在于Spring容器中吗??

==但是从代码debug流程上来看它居然也是判断当前类加载器是否可以加载到Class类。==

CacheAutoConfiguration而言,其可以被自动装配。
在这里插入图片描述
最后返回到条件装配判定逻辑的入口统一FilteringSpringBootCondition#match(String[], AutoConfigurationMetadata)的体现为:
在这里插入图片描述
CacheAutoConfiguration而言,其在String[] autoConfigurationClasses数组中的下标位置为4,而返回的boolean[] match数组下标位置4处为TRUE,表示其可以被自动装配。

1、验证加载自动装配类时@ConditionalOnBean注解是针对Class而不是Bean!!

==但是从代码debug流程上来看@ConditionalOnBean居然也是判断当前类加载器是否可以加载到Class类。 ==

虽然代码debug出来了,但我还是不敢相信!所以我要写个demo验证一下!!

首先自定义一个自动装配类:TestSaintAutoConfiguration

@Configuration
@ConditionalOnBean(MyConditionBean.class)
public class TestSaintAutoConfiguration {
    static {
        System.out.println("TestSaintAutoConfiguration类自动装配了");
    }
}

MyConditionBean类上不加任何注解,不让其注册到Spring容器中,仅使其可以在Class.forName时加载到。

public class MyConditionBean {
    public MyConditionBean() {
        System.out.println("MyConditionBean");
    }
}

整体项目目录如下:
在这里插入图片描述
如何自定义自动装配类,参考博文:SprinBoot自定义自动装配类与xxx-spring-boot-starter

经过上述过滤器过滤之后,我们的自动装配类TestSaintAutoConfiguration通过了过滤,”可以被自动装配“!!!
在这里插入图片描述
呀,真的可以自动装配了耶!!

执行运行程序并将自动装配的信息打出来试试看呢!

  • 首先要在application.yml文件中增加配置debug: true,打印自动装配信息。

控制台输出如下:
在这里插入图片描述
卧槽,卧槽,卧槽,不对呀!这里没自动装配TestSaintAutoConfiguration类啊。

推测肯定后面又做了某些处理!!!

代码一直往上返回,追到最上层获取所有自动装配类的地方 --> ConfigurationClassParser的内部类的processGroupImports()方法中:
在这里插入图片描述
又进到了ConfigurationClassParser#processConfigurationClass()方法中,你会发现这里ConfigurationPhasePARSE_CONFIGURATION而不是REGISTER_BEAN也就意味着,这里依旧会跳过条件装配,继续解析流程,啊啊啊啊,到底在哪呢?(此时博主已即将崩溃,但是博主没放弃,又找了好久好久,终于找到了)。

回到处理自动装配的核心逻辑ConfigurationClassPostProcessor#processConfigBeanDefinitions(BeanDefinitionRegistry)方法:
在这里插入图片描述

关于@ConditionalOnBean的处理逻辑,下篇文章<《SpringBoot启动流程七》:@Conditional条件装配实现原理(待补充博文链接)>中再做讨论。

下图为我们自定义的自动装配类TestSaintAutoConfiguratioin装配失败的表现:
在这里插入图片描述

相关文章
|
2月前
|
安全 Java 数据安全/隐私保护
SpringBoot实现二维码扫码登录的原理与详细步骤
SpringBoot实现二维码扫码登录的原理与详细步骤
118 1
|
2月前
|
XML Java 开发者
Spring Boot中的bean注入方式和原理
Spring Boot中的bean注入方式和原理
115 0
|
2月前
|
前端开发 搜索推荐 Java
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
|
7天前
|
XML Java 数据库
【SpringBoot:详解Bean装配】
【SpringBoot:详解Bean装配】
10 3
|
17天前
|
Java
SpringBoot之@Conditional衍生条件装配详解
SpringBoot之@Conditional衍生条件装配详解
|
17天前
|
Java Spring 容器
SpringBoot自动装配原理之@Import注解解析
SpringBoot自动装配原理之@Import注解解析
53 0
|
21天前
|
JSON Java Maven
Javaweb之SpringBootWeb案例之 SpringBoot原理的详细解析
Javaweb之SpringBootWeb案例之 SpringBoot原理的详细解析
45 0
Javaweb之SpringBootWeb案例之 SpringBoot原理的详细解析
|
1月前
|
Java 容器 Spring
Springboot自动配置原理
Springboot自动配置原理
|
2月前
|
Java API 开发者
springboot 多线程的使用原理与实战
在Spring Boot中实现多线程,主要依赖于Spring框架的@Async注解以及底层Java的并发框架。这里将深入剖析Spring Boot多线程的原理,包括@Async注解的工作方式、任务执行器的角色以及如何通过配置来调整线程行为。
52 5
|
2月前
|
消息中间件 NoSQL Java
springboot - 条件注解@ConditionalOnClass原理
springboot - 条件注解@ConditionalOnClass原理
springboot - 条件注解@ConditionalOnClass原理