@Conditional是基于条件的自动化配置注解, 由Spring 4框架推出的新特性。
在一个服务工程, 通常会存在多个配置环境, 比如常见的DEV(开发环境)、SIT(系统内部集成测试环境)、UAT(用户验收测试环境)、PRD(生产环境)等。在Spring3系列版本中通过@Profile实现,传入对应的环境标识, 系统自动加载不同环境的配置。spring4版本正式推出Condition功能, 在spring5版本, @Profile做了改进,底层是通过Condition实现, 看下Condition接口的UML结构:
可以看到两个抽象类应用实现了Condition接口, 一个是Spring Context下的ProfileCondition, 另一个就是SpringBootCondition。
SpringBootCondition下面有很多实现类,也是满足Spring
Boot的各种Condition需要, 图中只是列出了部分实现, 每个实现类下面, 都会有对应的注解来协助处理。
2. Conditional条件化系列注解介绍SpringBootCondition下面包含的主要条件化注解说明:
@ConditionalOnBean: 当Spring容器存在某个Bean则触发实现。@ConditionalOnMissingBean: 当Spring容器不存在某个Bean则不触发。@ConditionalOnSingleCandidate: 当Spring容器中只有一个指定Bean,或者多个时是首选 Bean。@ConditionalOnClass: 当环境路径下有指定的类, 则触发实现。@ConditionalOnMissingClass: 当环境路径下没有指定类则不触发实现。@ConditionalOnProperty: 判断属性如果存在指定的值则触发实现。@ConditionalOnResource: 判断存在指定的资源则触发实现。@ConditionalOnExpression: 基于 某个SpEL 表达式作判断实现。@ConditionalOnJava:基于JDK的版本作判断实现。@ConditionalOnJndi:基于指定的 JNDI 作判断实现。@ConditionalOnNotWebApplication:判断当前项目定义如果不是 Web 应用则不触发实现。@ConditionalOnWebApplication:判断当前项目定义如果是 Web 应用则触发实现。它们内部都是基于@Conditional实现。
3. Conditional条件化注解的实现原理上面看到, Spring Boot 有很多内置的多条件化注解, 都是基于@Conditional实现,
那么@Conditionnal又是如何实现? 它的作用范围是什么? 是如何生效的?
Conditional源码
@Target标示它的作用范围是在类或方法上。它是如何被调用生效的? 我们来写下测试类, 进行调试,
分析调用栈。
自定义Conditional
创建com.mirson.spring.boot.research.condition.CustomerMatchCondition
创建引用该Condition的配置类,
com.mirson.spring.boot.research.startup.CusomterConditional
启动调试,分析调用栈:
可以看到, 先从第一步调用refresh调用容器初始化,再到第二步处理Bean配置定义信息, 最后调用注解的doScan扫描方法,这样就能够找到我们自定义的CustomerMatchCondition,调用Condtion定义的matches接口实现, 决定是否要执行CustomerConditional 的newObject方法。
4. Conditional核心之matches匹配接口matchs方法是做规则校验处理, SpringBootCondition源码:
获取使用了Conditional的类或方法名称信息。根据Conditional条件规则判断, 获取返回处理结果。判断是否开启日志记录功能,打印处理结果。记录处理结果至ConditionEvaluationReport的outcomes属性中。最后返回布尔值的处理结果。它是通过 ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法调用, 可以看到它是在Bean创建之前就先调用,归属Bean配置定义信息的逻辑处理,且在validate方法之前处理。调用机制要理解清楚,我们管理配置。 5. Conditional核心之条件化注解具体实现以ConditionalOnBean为例, 进行分析, 源码:
采用Conditional注解, 具体条件判断逻辑在OnBeanCondition类中实现, 源码:
OnBeanCondition类的作用是判断容器中有无指定的Bean实例, 如果存在, 则条件生效。
它实现了抽象类FilteringSpringBootCondition的getOutcomes方法,同时实现了SpringBootCondition的getMatchOutcome方法, 两个核心方法接口,一个是获取定义的匹配条件,一个是返回匹配的结果信息, OnBeanCondition子类去实现具体的判断逻辑, 根据定义的条件输出判断结果。
getOutcomes方法
方法源码:
该方法作用是扫描在META-INF的spring.factories文件中定义的配置类, 检测是否包含对应的条件标注,
也就是是否使用了@OnBeanCondition标注,存在则会记录, 进入后续方法逻辑处理。
可以看到, 通过outcomes数组来记录所有采用了Conditional的Autoconfiguration配置类。
扩展分析:
我们讲解的OnBeanCondition只是其中一个条件注解, 跟踪代码分析, 同组的还有OnClassConditional和OnWebApplicationCondition条件注解,启动处理顺序是:
OnClassConditional->OnWebApplicationCondition->OnBeanCondition,
spring.factories中大部份配置的Autoconfiguration都是采用OnClassConditional来作依赖类的条件判断。
getMatchOutcomes方法
上面的getOutcomes方法记录了需要匹配处理的条目,该方法是作具体判断实现。 这里支持三种条件注解: ConditionalOnBean、ConditionalOnSingleCandidate和ConditionalOnMissingBean。实际内部逻辑都会调用getMatchingBeans方法。处理完成之后, 返回ConditionMessage对象,最后通过ConditionOutcome包装返回处理结果。
getMatchingBeans方法
该方法是做具体检测是否符合条件注解所配置的信息,主要包含三种类型判断,
一种是Bean Type 也就是class类型, 第二种是annotation标注, 最后一种是Name属性判断。
1) 首先会判断搜寻策略,是否需要搜寻父容器上下文, 支持三种模式,CURRENT: 当前上下文; ANCESTORS: 所有父容器的上下文定义; ALL: 就是支持以上两种搜寻策略。
2) 其次就是根据注解的定义信息, 按三种方式进行判断, 内部按这三种, 类型、注解和名称做处理,如果是父级搜索,会采用递归调用, 检测是否存在, 进行匹配判断。方法调用层级:
getBeanNamesForType(…) -》collectBeanNamesForType(…)
getBeanNamesForAnnotation(…) -》collectBeanNamesForAnnotation(…)
以上就是以ConditionalOnBean为例, 对ConditionOnXXX的实现原理做了剖析, SpringBootCondition的其他实现类还有很多, 本章只抽取代表性常见的条件注解作分析,大家有兴趣可再研究其他条件注解的实现机制, 这里就不一一例举。
6. 总结基于Conditional条件的自动化配置, 从SpringBootCondition实现原理到OnBeanCondition、AutoConfigurationImportFilter的剖析, 综合可以看出Spring Boot对于条件化注解的实现, 无论从层次结构, 还是内部逻辑处理的关联性, 都比较清晰明了,值得借鉴的是它的良好的扩展性设计,比如策略模式, 模板模式等,抽象类的合理运用设计, 没有出现接口泛滥, 强耦合性等问题, 也便于Spring Boot后续版本的功能扩展。
到此这篇关于Spring Boot 详细分析Conditional自动化配置注解的文章就介绍到这了,更多相关Spring Boot Conditional内容请搜索七叶笔记以前的文章或继续浏览下面的相关文章希望大家以后多多支持七叶笔记!