Spring Boot 详细分析Conditional自动化配置注解
作者:麦神-mirson 发布时间:2021-11-25 21:56:14
1. Spring Boot Condition功能与作用
@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条件化系列注解介绍
Conditional的注解 | Conditional的处理类 | Conditional的说明 |
---|---|---|
@ConditionalOnBean | OnBeanCondition | Spring容器中是否存在对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找) |
@ConditionalOnClass | OnClassCondition | 类加载器中是否存在对应的类。可以通过Class指定(value属性)或者Class的全名指定(name属性)如果是多个类或者多个类名的话,关系是”与”关系,也就是说这些类或者类名都必须同时在类加载器中存在 |
@ConditionalOnExpression | OnExpressionCondition | 判断SpEL 表达式是否成立 |
@ConditionalOnMissingBean | OnBeanCondition | Spring容器中是否缺少对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找) |
@ConditionalOnMissingClass | OnClassCondition | 跟ConditionalOnClass的处理逻辑一样,只是条件相反,在类加载器中不存在对应的类 |
@ConditionalOnProperty | OnPropertyCondition | 应用环境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing属性。prefix表示属性名的前缀,name是属性名,havingValue是具体的属性值,matchIfMissing是个boolean值,如果属性不存在,这个matchIfMissing为true的话,会继续验证下去,否则属性不存在的话直接就相当于匹配不成功 |
@ConditionalOnResource | OnResourceCondition | 是否存在指定的资源文件。只有一个属性resources,是个String数组。会从类加载器中去查询对应的资源文件是否存在 |
@ConditionalOnSingleCandidate | OnBeanCondition | Spring容器中是否存在且只存在一个对应的实例。只有3个属性value、type、search。跟ConditionalOnBean中的这3种属性值意义一样 |
@ConditionalOnWebApplication | OnWebApplicationCondition | 应用程序是否是Web程序,没有提供属性,只是一个标识。会从判断Web程序特有的类是否存在,环境是否是Servlet环境,容器是否是Web容器等 |
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({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* contion条件的具体实现类, 必须实现Condition接口
*/
Class<? extends Condition>[] value();
}
@Target标示它的作用范围是在类或方法上。它是如何被调用生效的? 我们来写下测试类, 进行调试,
分析调用栈。
自定义Conditional
创建com.mirson.spring.boot.research.condition.CustomerMatchCondition
@Log4j2
public class CustomerMatchCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
log.info("Process in CustomerMatchCondition.matches method. ");
return false;
}
}
创建引用该Condition的配置类,
com.mirson.spring.boot.research.startup.CusomterConditional
@Configuration
@Conditional(CustomerMatchCondition.class)
@Log4j2
public class CusomterConditional {
public Object newObj() {
log.info("Process in CusomterConditional.newObj method.");
return new Object();
}
}
启动调试,分析调用栈:
可以看到, 先从第一步调用refresh调用容器初始化,再到第二步处理Bean配置定义信息, 最后调用注解的doScan扫描方法,这样就能够找到我们自定义的CustomerMatchCondition,调用Condtion定义的matches接口实现, 决定是否要执行CustomerConditional 的newObject方法。
4. Conditional核心之matches匹配接口
matchs方法是做规则校验处理, SpringBootCondition源码:
public abstract class SpringBootCondition implements Condition {
private final Log logger = LogFactory.getLog(getClass());
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 根据注解信息, 获取类或方法名称
String classOrMethodName = getClassOrMethodName(metadata);
try {
// 获取实现类的处理匹配结果
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// 日志打印匹配结果
logOutcome(classOrMethodName, outcome);
// ConditionEvaluationReport中记录处理结果信息
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
+ ex.getMessage() + " not " + "found. Make sure your own configuration does not rely on "
+ "that class. This can also happen if you are "
+ "@ComponentScanning a springframework package (e.g. if you "
+ "put a @ComponentScan in the default package by mistake)", ex);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
}
}
...
}
获取使用了Conditional的类或方法名称信息。
根据Conditional条件规则判断, 获取返回处理结果。
判断是否开启日志记录功能,打印处理结果。
记录处理结果至ConditionEvaluationReport的outcomes属性中。最后返回布尔值的处理结果。它是通过 ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法调用, 可以看到它是在Bean创建之前就先调用,归属Bean配置定义信息的逻辑处理,且在validate方法之前处理。调用机制要理解清楚,我们管理配置。
5. Conditional核心之条件化注解具体实现
以ConditionalOnBean为例, 进行分析, 源码:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
...
}
采用Conditional注解, 具体条件判断逻辑在OnBeanCondition类中实现, 源码:
@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
/**
* Bean definition attribute name for factory beans to signal their product type (if
* known and it can't be deduced from the factory bean class).
*/
public static final String FACTORY_BEAN_OBJECT_TYPE = BeanTypeRegistry.FACTORY_BEAN_OBJECT_TYPE;
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
...
}
OnBeanCondition类的作用是判断容器中有无指定的Bean实例, 如果存在, 则条件生效。
它实现了抽象类FilteringSpringBootCondition的getOutcomes方法,同时实现了SpringBootCondition的getMatchOutcome方法, 两个核心方法接口,一个是获取定义的匹配条件,一个是返回匹配的结果信息, OnBeanCondition子类去实现具体的判断逻辑, 根据定义的条件输出判断结果。
getOutcomes方法
方法源码:
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// 创建数组, 记录自动化配置的类信息
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
// 遍历处理
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
// 获取具有ConditionalOnBean注解设置的Bean
Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
// 记录outcomes, 条件配置信息
outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
if (outcomes[i] == null) {
// 为空, 则降级获取ConditionalOnSingleCandidate配置信息
Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
"ConditionalOnSingleCandidate");
outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
}
}
}
return outcomes;
}
该方法作用是扫描在META-INF的spring.factories文件中定义的配置类, 检测是否包含对应的条件标注,
也就是是否使用了@OnBeanCondition标注,存在则会记录, 进入后续方法逻辑处理。
可以看到, 通过outcomes数组来记录所有采用了Conditional的Autoconfiguration配置类。
扩展分析:
我们讲解的OnBeanCondition只是其中一个条件注解, 跟踪代码分析, 同组的还有OnClassConditional和OnWebApplicationCondition条件注解,启动处理顺序是:
OnClassConditional->OnWebApplicationCondition->OnBeanCondition,
spring.factories中大部份配置的Autoconfiguration都是采用OnClassConditional来作依赖类的条件判断。
getMatchOutcomes方法
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
// 判断注解类型, ConditionalOnBean处理逻辑
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnBean.class, spec).because(reason));
}
matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec).found("bean", "beans")
.items(Style.QUOTE, matchResult.getNamesOfAllMatches());
}
// ConditionalOnSingleCandidate注解处理逻辑
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,
ConditionalOnSingleCandidate.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, spec)
.didNotFind("any beans").atAll());
}
else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
spec.getStrategy() == SearchStrategy.ALL)) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, spec)
.didNotFind("a primary bean from beans")
.items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
}
matchMessage = matchMessage.andCondition(ConditionalOnSingleCandidate.class, spec)
.found("a primary bean from beans").items(Style.QUOTE, matchResult.getNamesOfAllMatches());
}
// ConditionalOnMissingBean注解处理逻辑
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (matchResult.isAnyMatched()) {
String reason = createOnMissingBeanNoMatchReason(matchResult);
return ConditionOutcome
.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, spec).because(reason));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec).didNotFind("any beans")
.atAll();
}
return ConditionOutcome.match(matchMessage);
}
上面的getOutcomes方法记录了需要匹配处理的条目,该方法是作具体判断实现。 这里支持三种条件注解: ConditionalOnBean、ConditionalOnSingleCandidate和ConditionalOnMissingBean。实际内部逻辑都会调用getMatchingBeans方法。处理完成之后, 返回ConditionMessage对象,最后通过ConditionOutcome包装返回处理结果。
getMatchingBeans方法
该方法是做具体检测是否符合条件注解所配置的信息,主要包含三种类型判断,
一种是Bean Type 也就是class类型, 第二种是annotation标注, 最后一种是Name属性判断。
protected final MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 判断bean的搜寻策略, ANCESTORS为搜索所有父容器的上下文定义
if (beans.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS");
// 父容器转换
beanFactory = (ConfigurableListableBeanFactory) parent;
}
MatchResult matchResult = new MatchResult();
// 判断bean的搜寻策略, 是否为CURRENT当前上下文
boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
TypeExtractor typeExtractor = beans.getTypeExtractor(context.getClassLoader());
List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(beans.getIgnoredTypes(), typeExtractor,
beanFactory, context, considerHierarchy);
// 根据bean的类型遍历判断是否符合规则
for (String type : beans.getTypes()) {
// type类型的具体处理逻辑, 内部为嵌套调用
Collection<String> typeMatches = getBeanNamesForType(beanFactory, type, typeExtractor,
context.getClassLoader(), considerHierarchy);
typeMatches.removeAll(beansIgnoredByType);
if (typeMatches.isEmpty()) {
matchResult.recordUnmatchedType(type);
}
else {
matchResult.recordMatchedType(type, typeMatches);
}
}
// 根据bean的注解遍历判断是否符合规则
for (String annotation : beans.getAnnotations()) {
List<String> annotationMatches = Arrays.asList(
// Annotation类型的具体处理逻辑, 内部为嵌套调用
getBeanNamesForAnnotation(beanFactory, annotation, context.getClassLoader(), considerHierarchy));
annotationMatches.removeAll(beansIgnoredByType);
if (annotationMatches.isEmpty()) {
matchResult.recordUnmatchedAnnotation(annotation);
}
else {
matchResult.recordMatchedAnnotation(annotation, annotationMatches);
}
}
// 根据bean的名称遍历判断是否符合规则
for (String beanName : beans.getNames()) {
if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
matchResult.recordMatchedName(beanName);
}
else {
matchResult.recordUnmatchedName(beanName);
}
}
return matchResult;
}
1) 首先会判断搜寻策略,是否需要搜寻父容器上下文, 支持三种模式,CURRENT: 当前上下文; ANCESTORS: 所有父容器的上下文定义; ALL: 就是支持以上两种搜寻策略。
2) 其次就是根据注解的定义信息, 按三种方式进行判断, 内部按这三种, 类型、注解和名称做处理,如果是父级搜索,会采用递归调用, 检测是否存在, 进行匹配判断。方法调用层级:
getBeanNamesForType(…) -》collectBeanNamesForType(…)
getBeanNamesForAnnotation(…) -》collectBeanNamesForAnnotation(…)
来源:https://blog.csdn.net/hxx688/article/details/125468704


猜你喜欢
- 一、引言那么那么那么今天来说下MP中强大的条件查询功能。本章是基于上个案例来讲的:MyBaits-Plus 快速入门案例二、具体操作首先来说
- 调用微信接口前需要准备的内容。1.微信公众平台的appid2.微信公众平台的secret3..获取tokenid4.获取ticket5.生成
- c++回调之利用函数指针示例#include <iostream>using namespace std;/**********
- Spring AOP对嵌套方法不起作用今天在调研系统操作记录日志时,好多教程都是借助于Spring AOP机制来实现。于是也采用这种方法来实
- 把bitmap图片的某一部分的颜色改成其他颜色private Bitmap ChangeBitmap(Bitmap bitmap){ int
- 在阿里开发手册的建表规约中有说明,数据库表中应该都要有create_time、update_time字段;那么在开发中,对于这些共有字段的处
- 一、简介构造函数,基本用法是在类对象声明的时候完成初始化工作。二、实例构造函数1、构造函数的名字与类名相同。2、使用 new 表达式创建类的
- 一、语音聊天说专业点就是即时语音,是一种基于网络的快速传递语音信息的技术,普遍应用于各类社交软件中,优势主要有以下几点:(1)时效性:视频直
- 什么是事务处理事务是计算机应用中不可或缺的组件模型,它保证了用户操作的原子性 ( Atomicity )、一致性 ( Consistency
- 前言前天工作中遇到了这样一个问题,我在接口的参数封装了一个pojo,这是很常见的,当参数一多,惯性的思维就是封装一个pojo.那么在参数前有
- 这篇文章主要介绍了JPA save()方法将字段更新为null的解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考
- 一、先来看看效果演示二、实现原理:这个其实不难实现,通过一个定时器不断调用TextView的setText就行了,在setText的时候播放
- 枚举是迭代一个集合中的数据项的过程。我们经常使用的大多数集合实际上都已经实现了枚举的接口IEnumerable和IEnumerator接口,
- 对于学习过C语言的朋友应该都知道,使用 malloc/calloc 等分配内存的函数时,一定要检查其返回值是否为“空指针”(亦即检查分配内存
- 两种解决方案前端查询字典数据然后前端转码后端查询字典值,然后再转码返回给前段。本文及时针对方案2 进行的改进目标:在需要返回给前段的字段上添
- 在很多项目中,为了安全安全考虑,需要对数据包进行加密处理,本文实例所述的即为C#加密代码,在应用开发中有很大的实用价值。说起数据包加密,其实
- 用Canvas画贝塞尔曲线,要画贝塞尔曲线首先了解贝塞尔曲线:由于用计算机画图大部分时间是操作鼠标来掌握线条的路径,与手绘的感觉和效果有很大
- 前言日常的Android开发中,我们会用到IntentFilter的匹配规则。IntentFilter的主要规则分为action、categ
- 目录课设要求相关知识点1.服务端能够看到所有在线用户2.服务端能够强制用户下线3.客户端能够看到所有在线用户4.客户端要求能够向某个用户发送
- 本文实例为大家分享了C#强制转换和尝试转换的方法,供大家参考,具体内容如下将String[]类型的Object类型,转换为String[]类