文章目录
一、前言
针对条件装配我们讨论了如下内容:
- 《SpringBoot系列十一》:精讲如何使用@Conditional系列注解做条件装配
- 《SpringBoot系列十二》:如何自定义条件装配(由@ConditionalOnClass推导)
- 《SpringBoot启动流程六》:SpringBoot自动装配时做条件装配的原理(万字图文源码分析)(含@ConditionalOnClass原理)
- 《SpringBoot系列十三》:图文精讲@Conditional条件装配实现原理
- 《SpringBoot系列十四》:@ConditionalOnBean、@ConditionalOnMissingBean注解居然失效了!
本文我们接着讨论多个Condition的执行顺序。
二、多个Condition的排序
在博文<《SpringBoot系列十三》:图文精讲@Conditional条件装配实现原理>中,我们讨论了Condition条件装配的原理,其中介绍了条件装配如何执行?入口为:
ConditionEvaluator#shouldSkip(AnnotatedTypeMetadata,ConfigurationPhase)
方法,其返回值为boolean类型,方法返回true表示当前类应该被过滤掉(即不符合条件装配的规则)、否则表示当前类应该被留下(即符合条件装配的规则)。
/**
* Determine if an item should be skipped based on {@code @Conditional} annotations.
* @param metadata the meta data
* @param phase the phase of the call
* @return if the item should be skipped
*/publicbooleanshouldSkip(@NullableAnnotatedTypeMetadata metadata,@NullableConfigurationPhase phase){// 如果类没被@Conditional衍生注解标注,则直接返回FALSE,表示当前类不应该被过滤掉if(metadata ==null||!metadata.isAnnotated(Conditional.class.getName())){returnfalse;}// 如果没设置条件装配的阶段,当类是配置类时,为 PARSE_CONFIGURATION 阶段,否则默认为 REGISTER_BEAN 阶段if(phase ==null){if(metadata instanceofAnnotationMetadata&&ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)){returnshouldSkip(metadata,ConfigurationPhase.PARSE_CONFIGURATION);}returnshouldSkip(metadata,ConfigurationPhase.REGISTER_BEAN);}// 获取类上所有的@Conditional 子注解,返回@Conditional注解中的value值List<Condition> conditions =newArrayList<>();for(String[] conditionClasses :getConditionClasses(metadata)){for(String conditionClass : conditionClasses){Condition condition =getCondition(conditionClass,this.context.getClassLoader());
conditions.add(condition);}}// 对获取到的所有Condition接口的实现类进行排序AnnotationAwareOrderComparator.sort(conditions);// 遍历所有的Condition,进行matchfor(Condition condition : conditions){ConfigurationPhase requiredPhase =null;if(condition instanceofConfigurationCondition){// 获取当前Condition的执行阶段
requiredPhase =((ConfigurationCondition) condition).getConfigurationPhase();}// 如果入参传入的阶段和Condition的阶段不同,直接返回FALSE。// 如果阶段相同 或 Condition的阶段为null,再使用Condition#matches(this.context, metadata)做真正的条件装配逻辑,不符合则返回TRUE。if((requiredPhase ==null|| requiredPhase == phase)&&!condition.matches(this.context, metadata)){returntrue;}}returnfalse;}
shouldSkip()方法执行的逻辑概括如下:
- 首先,如果入参类 为 null 或者 没有被@Conditional衍生注解标注,则直接返回FALSE,表示当前类不应该被过滤掉;
- 如果入参没有传条件装配的阶段;当类是配置类时,为 PARSE_CONFIGURATION 阶段,否则默认为 REGISTER_BEAN 阶段;
- 接着,获取类上所有的@Conditional 子注解,返回@Conditional注解中的value值。并对获取到的所有Condition接口的实现类进行排序。
- 遍历所有的Condition,进行匹配;如果入参传入的阶段和Condition的阶段不同,直接返回FALSE。如果阶段相同 或 Condition的阶段为null,再使用Condition#matches(this.context, metadata)做真正的条件装配逻辑,不符合则返回TRUE。
本文要讨论的多个Condition执行的顺序,就体现在
AnnotationAwareOrderComparator.sort(conditions);
方法。
1、对多个Condition排序
在博文《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息我们有讨论过对所有自动装配类的排序,其实和这里是一样的,本文对其进行更细粒度的讨论;
进入到AnnotationAwareOrderComparator.sort(conditions)方法中;
AnnotationAwareOrderComparator.sort(conditions);
sort()方法中直接使用List集合的sort()方法,但需要自定义
Comparator
为当前类实例
AnnotationAwareOrderComparator
。
再看
AnnotationAwareOrderComparator
的类结构:
AnnotationAwareOrderComparator继承自
OrderComparator
,自定义Comparator需要实现Comparator的抽象方法
compare(T o1, T o2)
。由于AnnotationAwareOrderComparator自身没有compare()方法,所以看其父类OrderComparator中的compare(Object o1, Object o2)方法;
@Overridepublicintcompare(@NullableObject o1,@NullableObject o2){returndoCompare(o1, o2,null);}privateintdoCompare(@NullableObject o1,@NullableObject o2,@NullableOrderSourceProvider sourceProvider){// 判断o1是否实现PriorityOrdered接口boolean p1 =(o1 instanceofPriorityOrdered);// 判断o2是否实现PriorityOrdered接口boolean p2 =(o2 instanceofPriorityOrdered);// 如果o1实现了PriorityOrdered接口 而 o2没实现,则按 o1 在 o2之前的顺序排序(从集合中的排序来看就是交换o1 和 o2的位置)if(p1 &&!p2){return-1;}// 如果o2实现了PriorityOrdered接口 而 o1没实现,则按 o2在o1之前的顺序排序(从集合中的排序来看o2和o1的位置不变)elseif(p2 &&!p1){return1;}// 如果o2和o1都实现了 或 都没实现PriorityOrdered接口,则比对两者的顺序值。int i1 =getOrder(o1, sourceProvider);int i2 =getOrder(o2, sourceProvider);returnInteger.compare(i1, i2);}
方法逻辑如下:
- 本来的集合中的顺序是o2,o1;而入参参数的先后是顺序o1,o2,如果保持(o2 < o1)的顺序就返回1 或 0,交换o2和o1顺序就返回-1,0的含义是在两个元素相同时,不交换顺序(为了排序算法的稳定性,可以使用1来代替0,不要用-1来代替0)。即:想要升序,返回-1;降序返回1。详情见Arrays.sort()方法源码
- 实现PriorityOrdered接口的放前面,如果都实现了PriorityOrdered接口 或 都没实现,则根据获取到的顺序值对比排序。
因为
OrderComparator#doCompare(Object o1, Object o2, OrderSourceProvider sourceProvider)
方法中的入参sourceProvider为null,所以进入到getOrder()方法时,后续直接调用子类的
findOrder(Object obj)
方法去查找相应类的顺序值。
1)AnnotationAwareOrderComparator#findOrder()方法:
@Override@NullableprotectedIntegerfindOrder(Object obj){Integer order =super.findOrder(obj);if(order !=null){return order;}returnfindOrderFromAnnotation(obj);}
先调用父类
OrderComparator#findOrder()
方法,如果找到顺序值直接返回,否者从类的@Order注解中取到顺序值。下面我们分别看OrderComparator#findOrder()方法和AnnotationAwareOrderComparator#findOrderFromAnnotation()方法。
1>
OrderComparator#findOrder()
方法:
@NullableprotectedIntegerfindOrder(Object obj){return(obj instanceofOrdered?((Ordered) obj).getOrder():null);}
该方法判断obj有没有实现Ordered接口,实现Ordered接口之后,有没有重写其getOrder()方法,如果重写了,则直接从getOrder()中获取到序列值;否则返回null;
往上返,回到
OrderComparator#findOrder()
方法中;如果通过
OrderComparator#findOrder()
获取不到顺序值,即返回null,AnnotationAwareOrderComparator会再通过
findOrderFromAnnotation()
方法从@Order注解中获取顺序值。
2>
AnnotationAwareOrderComparator#findOrderFromAnnotation()
方法:
@NullableprivateIntegerfindOrderFromAnnotation(Object obj){AnnotatedElement element =(obj instanceofAnnotatedElement?(AnnotatedElement) obj : obj.getClass());MergedAnnotations annotations =MergedAnnotations.from(element,SearchStrategy.TYPE_HIERARCHY);Integer order =OrderUtils.getOrderFromAnnotations(element, annotations);if(order ==null&& obj instanceofDecoratingProxy){returnfindOrderFromAnnotation(((DecoratingProxy) obj).getDecoratedClass());}return order;}
而
findOrderFromAnnotation()
方法中通过
OrderUtils.getOrderFromAnnotations(element, annotations);
获取@Order注解中的值;
3> 进入
OrderUtils#getOrderFromAnnotations()
方法:
@NullablestaticIntegergetOrderFromAnnotations(AnnotatedElement element,MergedAnnotations annotations){// 这里默认不会走if(!(element instanceofClass)){returnfindOrder(annotations);}// 从缓存中获取,默认为nullObject cached = orderCache.get(element);if(cached !=null){return(cached instanceofInteger?(Integer) cached :null);}// 查找类上是否有@Order注解,如果有,返回@Order注解中的Value值,否者返回null;Integer result =findOrder(annotations);
orderCache.put(element, result !=null? result : NOT_ANNOTATED);return result;}
方法逻辑:
- 首先从缓冲中获取顺序值,默认为null,即获取不到。
- 接着通过findOrder(MergedAnnotations)方法查找类上是否有@Order注解,如果没有返回null,否则返回@Order注解中的Value值;
- findOrder(MergedAnnotations)方法执行完之后,将查询到的@Order中的value值写入到缓存orderCache中。
4> 进入
OrderUtils#findOrder()
方法:
@NullableprivatestaticIntegerfindOrder(MergedAnnotations annotations){// 获取到类上的@Order注解MergedAnnotation<Order> orderAnnotation = annotations.get(Order.class);// 如果@Order注解存在,则返回@Order注解的Value值if(orderAnnotation.isPresent()){return orderAnnotation.getInt(MergedAnnotation.VALUE);}// 如果@Order注解不存在,继续判断类上是否有`javax.annotation.Priority`注解MergedAnnotation<?> priorityAnnotation = annotations.get(JAVAX_PRIORITY_ANNOTATION);if(priorityAnnotation.isPresent()){return priorityAnnotation.getInt(MergedAnnotation.VALUE);}// 如果两个注解都没有,则返回null。returnnull;}
最后返回到
OrderComparator#getOrder()
方法,如果通过子类
AnnotationAwareOrderComparator
的findOrder(Object)方法获取到的order为null,则此处将Order设置为
Integer.MAX_VALUE
;
注意:如果两个对象的Order顺序值一样,则按原本在集合中的顺序先后排列。
2)List集合中的顺序是怎样的?
privateList<String[]>getConditionClasses(AnnotatedTypeMetadata metadata){// 获取类上所有@Conditional注解中的全部ConditionMultiValueMap<String,Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(),true);Object values =(attributes !=null? attributes.get("value"):null);return(List<String[]>)(values !=null? values :Collections.emptyList());}
通过
ConditionEvaluator#getConditionClasses(AnnotatedTypeMetadata)
方法获取类上所有@Conditional注解中的全部Condition,获取次序为:类上的注解从上至下、@Conditional中的Condition集合从左到右。
下面我们用案例来例证这个结论。
3)排序总述
首先通过通过
ConditionEvaluator#getConditionClasses(AnnotatedTypeMetadata)
方法获取类上所有@Conditional注解中的全部Condition,获取次序为:类上的注解从上至下、@Conditional中的Condition集合从左到右。,返回一个
List<Condition>
集合。
然后利用List集合自身的排序,通过传入自定义的Comparator 实现排序规则。具体规则如下:
自定义Comparator
需要实现compare(Object o1, Object o2)方法,
AnnotationAwareOrderComparator
类继承自
OrderComparator
,由于
AnnotationAwareOrderComparator
没有重写
compare(Object o1, Object o2)
方法,所以排序规则体现在
OrderComparator
的compare(Object o1, Object o2)方法中,进而进入到其doCompare(Object o1, Object o2, OrderSourceProvider sourceProvider)方法中;
- 比如:有两个需要被排序的对象o2, o1,它在原List集合中的位置顺序为o2, o1。
- 首先进行PriorityOrdered判断: > > - 如果o1对象实现了PriorityOrdered接口,o2对象没实现,返回-1,即交换o2 和 o1的位置,将o1 放在o2前面;> - 如果o2对象实现了PriorityOrdered接口,o1对象没实现,返回1,即o2 和 o1的位置保持不变,o2 仍在o1前面;> - 否则,如果o1 和 o2对象同时实现了PriorityOrdered接口 或 都没实现,则通过
OrderComparator#getOrder()
方法分别获取到 o2 和 o1对象的顺序值;OrderComparator#getOrder(Object, OrderSourceProvider)
方法中: > 1. 首先通过OrderComparator#findOrder()
方法查找顺序值,而AnnotationAwareOrderComparator
重写了findOrder()方法,所以直接进入到AnnotationAwareOrderComparator#findOrder()方法。> 2. 在AnnotationAwareOrderComparator#findOrder()方法中,会先调用其父类OrderComparator#findOrder()方法判断对象是否实现Ordered
接口 并 重写了getOrder()方法,有则返回getOrder()方法的返回值。> 3. 否则,通过findOrderFromAnnotation()
方法从注解中获取顺序值(优先走orderCache缓存获取,获取不到才会进行如下逻辑,走如下逻辑获取到之后,再放去orderCache缓存中): 1> 首先从对象的@Order注解中获取其value值,有则会返回; 2> 否者从对象的javax.annotation.Priority
注解中获取其Value值,有则返回, 3> 否者,返回null。- 回到
OrderComparator#getOrder()
层面,如果findOrder()返回了具体的Integer值,则返回,否者返回Integer.MAX_VALUE。- 最后,回到
OrderComparator#doCompare()
方法,返回Integer.compare(i1, i2)
的结果,按顺序值的升序排序(即:顺序值小的在前面、大的在后面)。
2、Condition排序案例
从上面的讨论我们可以得知排序规则:如果Condition实现了PriorityOrdered接口,则放在最前面一部分;接着依次通过 <实现Ordered接口的getOrder()方法返回顺序值>、<从@Order注解中的Value值获取到顺序值返回> 、<从javax.annotation.Priority注解中的Value值获取到顺序值返回>,如果通过上面三种方式都没有获取到顺序值,则返回
Integer.MAX_VALUE
。
1)代码目录
自定义Condition参考博文:《SpringBoot系列十二》:如何自定义条件装配。
整体代码目录结构如下:
1> @ConditionalOnSystemProperty
packagecom.saint.autoconfigure.condition;importorg.springframework.context.annotation.Conditional;importjava.lang.annotation.*;/**
* 系统属性名称与属性值匹配条件注解
*
* @author Saint
*/@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional({OnSystemPropertyCondition.class,MyCondition.class})public@interfaceConditionalOnSystemProperty{/**
* 属性名
*
* @return
*/Stringname();/**
* 属性值
*
* @return
*/Stringvalue();}
2> MyCondition
packagecom.saint.autoconfigure.condition;importorg.springframework.context.annotation.Condition;importorg.springframework.context.annotation.ConditionContext;importorg.springframework.core.PriorityOrdered;importorg.springframework.core.type.AnnotatedTypeMetadata;/**
*
* @author Saint
*/publicclassMyConditionimplementsCondition,PriorityOrdered{@Overridepublicbooleanmatches(ConditionContext context,AnnotatedTypeMetadata metadata){returntrue;}@OverridepublicintgetOrder(){return0;}}
3> OnSystemPropertyCondition
packagecom.saint.autoconfigure.condition;importorg.springframework.context.annotation.Condition;importorg.springframework.context.annotation.ConditionContext;importorg.springframework.core.type.AnnotatedTypeMetadata;importorg.springframework.util.MultiValueMap;importjava.util.Objects;/**
* 系统属性值与值匹配条件
*
* @author Saint
*/publicclassOnSystemPropertyConditionimplementsCondition{@Overridepublicbooleanmatches(ConditionContext context,AnnotatedTypeMetadata metadata){MultiValueMap<String,Object> attributes =
metadata.getAllAnnotationAttributes(ConditionalOnSystemProperty.class.getName());String name =(String) attributes.getFirst("name");String value =(String) attributes.getFirst("value");String systemPropertyValue =System.getProperty(name);// 比较系统属性值和方法的值if(Objects.equals(systemPropertyValue, value)){System.out.printf("系统属性【名称: %s】找到匹配值:%s \n", name, value);returntrue;}returnfalse;}}
4> ClassX
packagecom.saint;importcom.saint.autoconfigure.condition.ConditionalOnSystemProperty;importcom.saint.service.TestService;importorg.springframework.boot.autoconfigure.condition.ConditionalOnBean;importorg.springframework.boot.autoconfigure.condition.ConditionalOnClass;importorg.springframework.context.annotation.Configuration;/**
* @author Saint
*/@Configuration@ConditionalOnClass(ClassY.class)@ConditionalOnBean(TestService.class)@ConditionalOnSystemProperty(name ="language", value ="Chinese")publicclassClassX{publicClassX(){System.out.println("初始化了ClassX!");}}
5> ClassY
packagecom.saint;/**
* @author Saint
*/publicclassClassY{publicClassY(){System.out.println("初始化了ClassY!");}}
6> 启动类
packagecom.saint;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.ConfigurableApplicationContext;@SpringBootApplication//@Import(TestImportClassA.class)publicclassSaintSpringbootApplication{publicstaticvoidmain(String[] args){// 设置language属性值System.setProperty("language","Chinese");ConfigurableApplicationContext context =SpringApplication.run(SaintSpringbootApplication.class, args);// 从Spring上下文中获取beanName为message的类String message = context.getBean("message",String.class);System.out.println("当前message类型为:"+ message);}}
2)Condition的顺序
以上面自定义的ClassX为例:
其获取到的Condition的顺序为:OnClassCondition、OnBeanCondition、OnSystemPropertyCondition、MyCondition,这个排序的规则,正如我们上面所说:获取次序为:类上的注解从上至下、@Conditional中的Condition集合从左到右。。
再看排序后的结果:
由于只有MyCondition实现了PriorityOrdered接口,所以它在最前面,另外三个的顺序值都是一次按照顺序值大小排序。
三、总结
从自动装配类的条件装配来看,一般选择将Class Conditions类注解(@ConditionalOnClass、@ConditionalOnMissingClass) 放在最前面、Property Conditions类注解(@ConditionalOnProperty)其次、Bean Conditions类注解(@ConditionalOnBean、@ConditionalOnMissingBean)放最后。当然也还有其他种类的条件注解,一般常用的是这三种。
再结合配置类加载的两个阶段(配置类解析、注册Bean<见博文:《SpringBoot系列十三》:图文精讲@Conditional条件装配实现原理>)来看:
1> 自动装配类:
- 自动装配类在配置类解析阶段会对Bean Conditions之外的条件注解做判断;而Bean Condtions类注解需要到注册bean阶段才会生效。
2> @Component标注的类:
- 同样的@Component衍生注解标注的类 在配置类解析阶段会对Bean Conditions之外的条件注解做判断,与此同时也会注入到Spring的临时容器
beanDefinitionNames
中,所以针对@Component标注的类而言,Bean Conditions可能会在配置类解析阶段生效、也可能不会生效;所以最终还是要在注册Bean阶段做一个全面的条件装配。不过一般而言,正常业务中不会有人在@Component衍生注解里搞条件装配(@Configuration除外),通常都在在自动装配类中做那么个操作。
版权归原作者 秃秃爱健身 所有, 如有侵权,请联系我们删除。