注:本系列源码分析基于spring 5.2.2.RELEASE,本文的分析基于 annotation 注解方式,gitee仓库链接:gitee.com/funcy/sprin….
本文是ConfigurationClassPostProcessor分析的第四篇,主要是分析spring对@Conditional 注解的处理流程。
5. spring 是如何处理 @Conditional 注解的?
5.1 @Conditional 的处理流程
在前面分析ConfigurationClassParser#processConfigurationClass方法时,有这么一行:
class ConfigurationClassParser {
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// 判断是否需要跳过处理,针对于 @Conditional 注解,判断是否满足条件
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(),
ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
...
}
...
}
conditionEvaluator.shouldSkip(...) 方法就是用来处理@Conditional注解的,关于这个方法的处理流程,我们晚点再分析,我们先来看看什么是 @Conditional 注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 条件类
*/
Class<? extends Condition>[] value();
}
@Conditional 注解非常简单,仅有一个属性,返回值是 Class[],且必须是 Condition 的子类。我们再来看看Condition:
public interface Condition {
/**
* 在这里指定匹配逻辑
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
Condition接口仅有一个matches方法,我们可以在其中指定匹配逻辑。
接着我们来看看conditionEvaluator.shouldSkip(...) 的处理流程:
class ConditionEvaluator {
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata,
@Nullable ConfigurationPhase phase) {
// 是否标记 @Conditional
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 判断传入的 phase
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
// 实例化 condition,其放入 conditions 中
List<Condition> conditions = new ArrayList<>();
// 1. getConditionClasses(metadata):获取 @Conditional 中指定的判断类
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
// 2. 实例化操作(用到的还是反射),统一放到 conditions 中
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 3. 排序上面得到的 condition 实例
AnnotationAwareOrderComparator.sort(conditions);
// 遍历上面得到的 conditions
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
// 4. 调用 Condition#matches 方法进行判断
if ((requiredPhase == null || requiredPhase == phase) &&
!condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
...
}
该方法的处理流程如下:
- 获取
@Conditional中指定的判断类,就是@Conditional的value属性值; - 使用 反射实例化第1步中 得到的判断类,并 保存到
conditions(这是个List) 中; - 对第2步得到的
conditions进行排序; - 遍历第3步得到的
conditions,调用Condition#matches方法进行匹配。
@Conditional 的处理还是非常简单的,接下来我们来看看它的使用示例。
5.2 @Conditional 使用示例
示例1:当指定的类存在时,才创建 spring bean
这里我们实现个功能:当指定的类存在时,才进行spring bean的创建、初始化,代码如下:
-
准备一个简单的bean:
public class BeanObj { } -
实现
Condition接口,在这里处理判断逻辑public class MyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String className = "java.lang.Object"; try { // 判断类是否存在 Class.forName(className); return true; } catch (ClassNotFoundException e) { return false; } } }在
matches(...)方法中 ,先是指定className为java.lang.Object,然后判断其是否存在,判断方式也 十分简单 ,就是使用java的反射机制:Class.forName(...),当类不存在时,会抛出ClassNotFoundException,我们可以捕获该异常,从而就知道类存在不存在了。 -
准备配置类
@ComponentScan public class BeanConfigs { @Bean @Conditional(MyCondition.class) public BeanObj beanObj() { return new BeanObj(); } }配置类比较简单,需要注意的是
beanObj()上的@Conditional注解,指定的是条件匹配类是MyCondition,匹配操作就是在这个类中进行的。 -
主类
public class Demo06Main { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfigs.class); try { Object obj = context.getBean("beanObj"); System.out.println("beanObj 存在!"); } catch (Exception e) { System.out.println("beanObj 不存在!"); } } }
运行,结果如下:
beanObj 存在!
在MyCondition#matches中,我们判断的是当前项目中是否存在java.lang.Object,显然这是存在的,因此beanObj会在spring容器中,接着我们换下className:
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 更换类名
String className = "java.lang.Object111";
...
}
}
显然,java.lang.Object111是不存在于当前项目中的,运行,结果如下:
beanObj 不存在!
示例2:改进示例1功能
在示例1中,我们通过在MyCondition#matches修改className来改变beanObj在容器中的存在情况,那如果项目中有非常多的类需要按类的存在与否进行加载,我们是实现多个MyCondition吗?
比如,类A需要根据类A1的存在与否来判断是否进行初始化,类B需要根据类B1的存在与否来判断是否进行初始化,类C需要根据类C1的存在与否来判断是否进行初始化...我们是否需要分别为类A、类B、类C实现Condition,在各自的match(...) 方法中进行判断吗?
实际上,我们并不需要这么做,这里通过spring组合注解的方法来完成以上功能。
-
准备一个bean,这与示例1并无区别
public class BeanObj { } -
准备注解
@ConditionalForClass,该注解组合了@Conditional的功能,处理条件匹配的类为MyCondition,className属性就是必须存在的类:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 组合了 @Conditional 的功能,处理条件匹配的类为 MyCondition
@Conditional(MyCondition.class)
public @interface ConditionalForClass {
/**
* 这里指定必须存在的类
*/
String className();
}
-
准备
MyCondition,注意与示例的差别在于,className不是在该方法中定义,而是由@ConditionalForClass传入:public class MyCondition implements Condition { /** * 这里处理匹配条件,注意与示例1中的区别 */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 获取 @ConditionalForClass 注解的所有属性值 Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes( ConditionalForClass.class.getName()); // 获取className的属性值,就是 @ConditionalForClass 的 className 属性 String className = (String)annotationAttributes.get("className"); if(null == className || className.length() <= 0) { return true; } try { // 判断类是否存在 Class.forName(className); return true; } catch (ClassNotFoundException e) { return false; } } } -
准备配置类,此时的条件注解为
@ConditionalForClass:@ComponentScan public class BeanConfigs { @Bean /** * 在 @ConditionalForClass 中指定了依赖的类 */ @ConditionalForClass(className = "java.lang.Object") public BeanObj beanObj() { return new BeanObj(); } } -
最后是主类,与示例1并不区别:
public class Demo07Main { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfigs.class); try { Object obj = context.getBean("beanObj"); System.out.println("beanObj 存在!"); } catch (Exception e) { System.out.println("beanObj 不存在!"); } } }
以上类通过自定义注解@ConditionalForClass来指定,当类java.lang.Object存在时,beanObj才会被添加到spring容器中,这个条件显然成立,运行,结果如下:
beanObj 存在!
我们再来调整下@ConditionalForClass的className值:
@ComponentScan
public class BeanConfigs {
@Bean
// 仅修改了@ConditionalForClass的className值,其他条件不变
@ConditionalForClass(className = "java.lang.Object1111")
public BeanObj beanObj() {
return new BeanObj();
}
}
这里将 @ConditionalForClass的className值调整为 java.lang.Object1111,显然这个类并不在当前项目中,运行结果如下:
beanObj 不存在!
结果也与我们的期望一致。
让我们回到本节开头的问题:比如,类A需要根据类A1的存在与否来判断是否进行初始化,类B需要根据类B1的存在与否来判断是否进行初始化,类C需要根据类C1的存在与否来判断是否进行初始化...我们是否需要分别为类A、类B、类C实现Condition,在各自的match(...) 方法中进行判断吗?
有了@ConditionalForClass注解后,我们并不需要这么麻烦,只需要在各自的@Bean方法上添加@ConditionalForClass就行了,像这样:
@Bean
@ConditionalForClass(className = "A1")
public A a() {
return new A();
}
@Bean
@ConditionalForClass(className = "B1")
public B b() {
return new B();
}
@Bean
@ConditionalForClass(className = "B1")
public C c() {
return new C();
}
...
注意体会@ConditionalForClass的实现,springboot中的@ConditionalOnClass就是按这种思路实现的。
5.3 总结
本文主要分析了spring处理@Conditional的流程,逻辑比较简单,最终调用的是Condition#matches方法进行匹配操作的,而匹配操作由Condition的实现类自行指定。
为了更好地说明@Conditional的使用,本文准备了两个使用示例,特别是示例2,需要特别体会,springboot中的@ConditionalOnClass正是基于示例2的思路进行实现的,另外,springboot中的多个条件注解也是对@Conditional的扩展。
本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。
本系列的其他文章