欢迎来到 通辽市某某化工涂料售后客服中心
全国咨询热线:020-123456789
联系我们

地址:联系地址联系地址联系地址

电话:020-123456789

传真:020-123456789

邮箱:admin@aa.com

新闻中心
「Spring Boot 源码研究 」- 自动化装配条件化配置Conditional剖析
  来源:通辽市某某化工涂料售后客服中心  更新时间:2024-05-07 18:23:11

「Spring Boot 源码研究 」- 自动化装配条件化配置Conditional剖析

1. Spring Boot Condition功能与作用

@Conditional是源码研究基于条件的自动化配置注解, 由Spring 4框架推出的自动新特性。

在一个服务工程,化装 通常会存在多个配置环境,配条配置l剖 比如常见的源码研究DEV(开发环境)、SIT(系统内部集成测试环境)、自动UAT(用户验收测试环境) 、化装PRD(生产环境)等。配条配置l剖在Spring3系列版本中通过@Profile实现,源码研究传入对应的自动环境标识, 系统自动加载不同环境的化装配置 。spring4版本正式推出Condition功能 ,配条配置l剖 在spring5版本,源码研究 @Profile做了改进 ,自动底层是化装通过Condition实现 , 看下Condition接口的UML结构 :

「Spring Boot 源码研究 」- 自动化装配条件化配置Conditional剖析

可以看到两个抽象类应用实现了Condition接口, 一个是Spring Context下的ProfileCondition, 另一个就是SpringBootCondition。

SpringBootCondition下面有很多实现类 ,也是满足Spring Boot的各种Condition需要, 图中只是列出了部分实现 , 每个实现类下面 , 都会有对应的注解来协助处理 。

「Spring Boot 源码研究 」- 自动化装配条件化配置Conditional剖析

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又是如何实现 ? 它的作用范围是什么 ? 是如何生效的 ?

1、Conditional源码

@Target({ ElementType.TYPE, ElementType.METHOD})n@Retention(RetentionPolicy.RUNTIME)n@Documentednpublic @interface Conditional { nn /**n * contion条件的具体实现类  , 必须实现Condition接口n */n Class<? extends Condition>[] value();nn}n

@Target标示它的作用范围是在类或方法上 。它是如何被调用生效的? 我们来写下测试类 , 进行调试 , 分析调用栈。

2 、自定义Conditional

创建com.mirson.spring.boot.research.condition.CustomerMatchCondition

@Log4j2npublic class CustomerMatchCondition implements Condition { nn @Overriden public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { n log.info("Process in CustomerMatchCondition.matches method. ");n return false;n }n}n

创建引用该Condition的配置类,

com.mirson.spring.boot.research.startup.CusomterConditional

@Configurationn@Conditional(CustomerMatchCondition.class)n@Log4j2npublic class CusomterConditional { nn public Object newObj() { n log.info("Process in CusomterConditional.newObj method.");n return new Object();n }n}n

启动调试 ,分析调用栈:

「Spring Boot 源码研究 」- 自动化装配条件化配置Conditional剖析

可以看到, 先从第一步调用refresh调用容器初始化,再到第二步处理Bean配置定义信息  , 最后调用注解的doScan扫描方法 ,这样就能够找到我们自定义的CustomerMatchCondition,调用Condtion定义的matches接口实现 , 决定是否要执行CustomerConditional 的newObject方法 。

4. Conditional核心之matches匹配接口

matchs方法是做规则校验处理 , SpringBootCondition源码 :

public abstract class SpringBootCondition implements Condition { nn private final Log logger = LogFactory.getLog(getClass());nn @Overriden public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { n // 根据注解信息 , 获取类或方法名称n String classOrMethodName = getClassOrMethodName(metadata);n try { n // 获取实现类的处理匹配结果n ConditionOutcome outcome = getMatchOutcome(context, metadata);n // 日志打印匹配结果n logOutcome(classOrMethodName, outcome);n // ConditionEvaluationReport中记录处理结果信息n recordEvaluation(context, classOrMethodName, outcome);n return outcome.isMatch();n }n catch (NoClassDefFoundError ex) { n throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "n + ex.getMessage() + " not " + "found. Make sure your own configuration does not rely on "n + "that class. This can also happen if you are "n + "@ComponentScanning a springframework package (e.g. if you "n + "put a @ComponentScan in the default package by mistake)", ex);n }n catch (RuntimeException ex) { n throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);n }n }n n ...n}

  • 获取使用了Conditional的类或方法名称信息。
  • 根据Conditional条件规则判断, 获取返回处理结果 。
  • 判断是否开启日志记录功能,打印处理结果 。
  • 记录处理结果至ConditionEvaluationReport的outcomes属性中  。最后返回布尔值的处理结果  。它是通过 ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法调用 , 可以看到它是在Bean创建之前就先调用,归属Bean配置定义信息的逻辑处理 ,且在validate方法之前处理。调用机制要理解清楚,便于进行管理配置 。

5. Conditional核心之条件化注解具体实现

以ConditionalOnBean为例 , 进行分析, 源码 :

@Target({ ElementType.TYPE, ElementType.METHOD })n@Retention(RetentionPolicy.RUNTIME)n@Documentedn@Conditional(OnBeanCondition.class)npublic @interface ConditionalOnBean { nn ...nn}n

采用Conditional注解, 具体条件判断逻辑在OnBeanCondition类中实现, 源码:

@Order(Ordered.LOWEST_PRECEDENCE)nclass OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition { nn /**n * Bean definition attribute name for factory beans to signal their product type (ifn * known and it can't be deduced from the factory bean class).n */n public static final String FACTORY_BEAN_OBJECT_TYPE = BeanTypeRegistry.FACTORY_BEAN_OBJECT_TYPE;nn @Overriden public ConfigurationPhase getConfigurationPhase() { n return ConfigurationPhase.REGISTER_BEAN;n }n ...n}

OnBeanCondition类的作用是判断容器中有无指定的Bean实例 , 如果存在  , 则条件生效 。

它实现了抽象类FilteringSpringBootCondition的getOutcomes方法,同时实现了SpringBootCondition的getMatchOutcome方法 , 两个核心方法接口,一个是获取定义的匹配条件,一个是返回匹配的结果信息, OnBeanCondition子类去实现具体的判断逻辑 , 根据定义的条件输出判断结果 。

5.1 getOutcomes方法

方法源码 :

@Overriden protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,n AutoConfigurationMetadata autoConfigurationMetadata) { n // 创建数组, 记录自动化配置的类信息n ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];n // 遍历处理n for (int i = 0; i < outcomes.length; i++) { n String autoConfigurationClass = autoConfigurationClasses[i];n if (autoConfigurationClass != null) { n // 获取具有ConditionalOnBean注解设置的Beann Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");n // 记录outcomes , 条件配置信息n outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);n if (outcomes[i] == null) { n // 为空, 则降级获取ConditionalOnSingleCandidate配置信息n Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,n "ConditionalOnSingleCandidate");n outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);n }n }n }n return outcomes;n }

该方法作用是扫描在META-INF的spring.factories文件中定义的配置类, 检测是否包含对应的条件标注, 也就是是否使用了@OnBeanCondition标注 ,存在则会记录 , 进入后续方法逻辑处理 。

可以看到, 通过outcomes数组来记录所有采用了Conditional的Autoconfiguration配置类 。

扩展分析:

我们讲解的OnBeanCondition只是其中一个条件注解, 跟踪代码分析  , 同组的还有OnClassConditional和OnWebApplicationCondition条件注解,启动处理顺序是:

OnClassConditional->OnWebApplicationCondition->OnBeanCondition,

spring.factories中大部份配置的Autoconfiguration都是采用OnClassConditional来作依赖类的条件判断 。

5.2 getMatchOutcomes方法

@Overriden public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { n ConditionMessage matchMessage = ConditionMessage.empty();n // 判断注解类型, ConditionalOnBean处理逻辑n if (metadata.isAnnotated(ConditionalOnBean.class.getName())) { n BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnBean.class);n MatchResult matchResult = getMatchingBeans(context, spec);n if (!matchResult.isAllMatched()) { n String reason = createOnBeanNoMatchReason(matchResult);n return ConditionOutcome .noMatch(ConditionMessage.forCondition(ConditionalOnBean.class, spec).because(reason));n }n matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec).found("bean", "beans")n .items(Style.QUOTE, matchResult.getNamesOfAllMatches());n }n // ConditionalOnSingleCandidate注解处理逻辑n if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { n BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,n ConditionalOnSingleCandidate.class);n MatchResult matchResult = getMatchingBeans(context, spec);n if (!matchResult.isAllMatched()) { n return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, spec)n .didNotFind("any beans").atAll());n }n else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),n spec.getStrategy() == SearchStrategy.ALL)) { n return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, spec)n .didNotFind("a primary bean from beans")n .items(Style.QUOTE, matchResult.getNamesOfAllMatches()));n }n matchMessage = matchMessage.andCondition(ConditionalOnSingleCandidate.class, spec)n .found("a primary bean from beans").items(Style.QUOTE, matchResult.getNamesOfAllMatches());n }n // ConditionalOnMissingBean注解处理逻辑n if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { n BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);n MatchResult matchResult = getMatchingBeans(context, spec);n if (matchResult.isAnyMatched()) { n String reason = createOnMissingBeanNoMatchReason(matchResult);n return ConditionOutcomen .noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, spec).because(reason));n }n matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec).didNotFind("any beans")n .atAll();n }n return ConditionOutcome.match(matchMessage);n }

上面的getOutcomes方法记录了需要匹配处理的条目  ,该方法是作具体判断实现 。 这里支持三种条件注解 : ConditionalOnBean 、ConditionalOnSingleCandidate和ConditionalOnMissingBean。实际内部逻辑都会调用getMatchingBeans方法。处理完成之后 , 返回ConditionMessage对象 ,最后通过ConditionOutcome包装返回处理结果 。

5.3 getMatchingBeans方法

该方法是做具体检测是否符合条件注解所配置的信息,主要包含三种类型判断 , 一种是Bean Type 也就是class类型 , 第二种是annotation标注, 最后一种是Name属性判断。

protected final MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) { n n ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();n // 判断bean的搜寻策略 , ANCESTORS为搜索所有父容器的上下文定义n if (beans.getStrategy() == SearchStrategy.ANCESTORS) { n BeanFactory parent = beanFactory.getParentBeanFactory();n Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS");n // 父容器转换n beanFactory = (ConfigurableListableBeanFactory) parent;n }n MatchResult matchResult = new MatchResult();n // 判断bean的搜寻策略, 是否为CURRENT当前上下文n boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;n TypeExtractor typeExtractor = beans.getTypeExtractor(context.getClassLoader());n List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(beans.getIgnoredTypes(), typeExtractor,n beanFactory, context, considerHierarchy);n // 根据bean的类型遍历判断是否符合规则n for (String type : beans.getTypes()) { n // type类型的具体处理逻辑 , 内部为嵌套调用n Collection<String> typeMatches = getBeanNamesForType(beanFactory, type, typeExtractor,n context.getClassLoader(), considerHierarchy);n typeMatches.removeAll(beansIgnoredByType);n if (typeMatches.isEmpty()) { n matchResult.recordUnmatchedType(type);n }n else { n matchResult.recordMatchedType(type, typeMatches);n }n }n // 根据bean的注解遍历判断是否符合规则n for (String annotation : beans.getAnnotations()) { n List<String> annotationMatches = Arrays.asList(n // Annotation类型的具体处理逻辑, 内部为嵌套调用n getBeanNamesForAnnotation(beanFactory, annotation, context.getClassLoader(), considerHierarchy));n annotationMatches.removeAll(beansIgnoredByType);n if (annotationMatches.isEmpty()) { n matchResult.recordUnmatchedAnnotation(annotation);n }n else { n matchResult.recordMatchedAnnotation(annotation, annotationMatches);n }n }n // 根据bean的名称遍历判断是否符合规则n for (String beanName : beans.getNames()) { n if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) { n matchResult.recordMatchedName(beanName);n }n else { n matchResult.recordUnmatchedName(beanName);n }n }n return matchResult;n }

1) 首先会判断搜寻策略,是否需要搜寻父容器上下文, 支持三种模式,CURRENT: 当前上下文; ANCESTORS: 所有父容器的上下文定义; ALL: 就是支持以上两种搜寻策略 。

2) 其次就是根据注解的定义信息 , 按三种方式进行判断 , 内部按这三种 , 类型  、注解和名称做处理 ,如果是父级搜索 ,会采用递归调用, 检测是否存在 , 进行匹配判断。方法调用层级 :

getBeanNamesForType(..) -》collectBeanNamesForType(..)

getBeanNamesForAnnotation(..) -》collectBeanNamesForAnnotation(...)

以上就是以ConditionalOnBean为例, 对ConditionOnXXX的实现原理做了剖析, SpringBootCondition的其他实现类还有很多 , 这里只抽取代表性常见的条件注解作分析,大家有兴趣可再研究其他条件注解的实现机制 , 这里就不一一例举。

6. 总结

基于Conditional条件的自动化配置 , 从SpringBootCondition实现原理到OnBeanCondition 、AutoConfigurationImportFilter的剖析, 综合可以看出Spring Boot对于条件化注解的实现  , 无论从层次结构  , 还是内部逻辑处理的关联性, 都比较清晰明了 ,值得借鉴的是它的良好的扩展性设计 ,比如策略模式, 模板模式等,抽象类的合理运用设计  , 没有出现接口泛滥, 强耦合性等问题, 也便于Spring Boot后续版本的功能扩展 。




友情链接《阴阳师》现世召唤怎么玩 和普通召唤有什么区别CFPL穿越火线最强联盟 这样的阵容蔑视一切《英雄联盟》为什么 AP 英雄先穿透能尽量提高输出,而 AD 英雄却不可以呢?梦幻西游:这样合宠,可以让你轻松获得3轮攻谛听吗?英雄联盟手游周年庆典派送豪华福利,燃斗之夜重磅嘉宾助阵乐翻天荆棘谷野人海岸在哪(wow在湿地怎么去黑海岸)《阴阳师》现世召唤怎么玩 和普通召唤有什么区别【陕西】DNF全国超级锦标赛 月赛持续升温英雄联盟咖啡甜心2023皮肤效果,LOL13.21咖啡甜心系列皮肤价格魔兽世界9.2:通灵奶骑大秘境攻略DNF:10大最受欢迎的幻化宠物外观,“大黄狗”最受欢迎2024手游私sf平台排行榜 十大私服手游平台推荐《伊苏6:纳比斯汀的方舟》简体中文汉化版PSP版dnf装备净化怎么弄(DNF史诗装备获取全指南)生存球竞技对抗试炼(Survival Ball Arena)正在阅读:哈利波特魔法觉醒绝音鸟羽毛怎么获得 哈利波特魔法觉醒绝音鸟羽毛在哪里刷几率高哈利波特魔法觉醒绝音鸟羽毛怎么获得 哈利波特魔法觉醒绝音鸟羽毛在哪里刷几率高「聚光灯:房间逃生Spotlight:Room Escape」·全攻略(第一章:威胁/命运)魔兽世界掉落怎么查询 掉落查询方法介绍世界战争英雄内置修改器版LOL全英雄7级成就世界第一人,竟是国服第一皮肤帝dnf冰结师刷图装备怎么搭配 dnf冰洁师异界套装怎样选择【ZYL_10671】 【诺克萨斯】331皮肤/1海克斯/1终极/3神话/117英雄/段位:未定级 海克斯安妮 灵魂守卫乌迪尔 龙年琴女 蛇年库奇 马年锐雯 羊年金克丝 猴年美猴王孙悟空 猴年莫甘娜 鸡年司马懿仲达阿兹尔 鸡年盖伦 狗年内瑟斯 陆地王者4WD布里茨 女皇艾希 冰川巨兽墨菲特 上古战魂魔腾 黑夜使者亚索 地狱之门卫士加里奥 黯黑魔龙希瓦娜 战地机甲厄加特 绅士科加斯 神拳李青 西5.2新增精良宠物25级属性一览及精英宠物属性有什么好玩的放置挂机游戏推荐 2023放置挂机游戏推荐DNF各职业BUFF技能名字和学习等级LOL转区系统上线!你最想去哪个区?《DNF》2021金秋国庆套礼包内容介绍插上科学的翅膀飞作文回到恐龙时代[交流]针对远古异界 阿修罗刷图及装备选择《魔兽争霸3冰封王座》爽就完了v1.0.5正式版《魔兽世界》10.0冰法AOE天赋加点分享皮甲幻化:恶魔猎手职业幻化合集 果然蛋刀才是本体魔兽世界熊猫人声望,魔兽世界熊猫人声望id《守望先锋:归来》新英雄“拉玛刹”无法免费体验 需直接购买或将免费战令升到55级DNF:神器装扮升级券到手了!部位不要乱升,没天空玩家等27号英雄联盟手游S11赛季什么时候开始 S11赛季开始时间【原】【探索诗歌】于耀江:涂抹颜料的巨匠(诗十首) ||摘星照梦059期100句春花诗词,一朝春雨落,十里春花开【全球播资讯】白芷是什么中药?类似效果的中药材有哪些?'+ nodedata.title +'
联系我们

地址:联系地址联系地址联系地址

电话:020-123456789

传真:020-123456789

邮箱:admin@aa.com

3.6602

Copyright © 2024 Powered by 通辽市某某化工涂料售后客服中心   sitemap