在一些框架集成中,我们可以看到各种各样的@Conditional的衍生注解,像@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等等。我们知道@Conditional是一个条件注解,只有被标记的类或者方法满足了指定条件后才会被加载,今天我们一起来看看其中的原理。
@Conditional注解被调用是在ConditionEvaluator类中,通过shouldSkip方法来判断当前类是否需要被跳过而不需要加载。
而shouldSkip被调用的地方有ConfigurationClassParser#processConfigurationClass方法(加载配置类)、
ClassPathScanningCandidateComponentProvider#isCandidateComponent方法(扫描执行路径)等,使用shouldSkip方法判断当前类是否需要被跳过而不需要加载。
知道了它大概的位置以及作用之后,我们再来分析shouldSkip方法
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
//判断被扫描的类中是否有@Conditional注解,没有的话直接返回false不应该被跳过
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
//扫描bean的时候会走这里,processConfigurationClass加载配置类phase不是null
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
//开始获取被扫描类中@Conditional注解中value的值
//需要注意的是value的值需要是Condition接口的实现类才可以,否则会报错
List conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
//排序@Conditional注解中value的值
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
//重点在condition.matches(this.context, metadata)方法,实现了Condition接口后,需要重写matches方法
//这里可以看到,如果matches方法返回false,则会被跳过,因此我们知道是否会被跳过的逻辑在matches方法中
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
这里我们知道了使用@Conditional注解,然后将Condition接口的实现类加入到value中,Condition接口的实现类中的matches方法来写具体的匹配逻辑。
懂了这个注解的用法之后我们来玩一下:
public class OnBeanCondition implements Condition {
@Override
//如果容器中有Demo1的话,则不再加载此标记类
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
try {
conditionContext.getBeanFactory().getBean("demo1");
} catch (BeansException e) {
return false;
}
return true; }
}
@Component
@Conditional(OnBeanCondition.class)
public class Demo2 {
public Demo2() {
System.out.println("Demo2 创建了");
}
}
当遍历到Demo2这个类的时候,我们可以看到这里拿到了我们自定义的OnBeanCondition类,然后通过反射创建了OnBeanCondition对象。
然后到开始判断是否跳过,由于我们在OnBeanCondition中写入的是flase,因此这里会跳过Demo2 bean的加载。
ok,到这里,我们对@Conditional有了一个简单的理解,接下来我们来看看Spring是如何用的。
@ConditionalOnClass:这个类的作用是当项目中存在指定类时,标记的对应类才会被加入到IOC容器中
可以看到@ConditionalOnClass内部就是使用了@Conditional,我们来看看这个OnClassCondition类
OnClassCondition继承SpringBootCondition,而SpringBootCondition实现了Condition接口,在SpringBootCondition中,重写了matches方法,然后提供getMatchOutcome方法供子类重写,返回的ConditionOutcome&isMatch就是匹配结果,因此我们看一下OnClassCondition的getMatchOutcome就可以了。
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
//获取ConditionalOnClass中value和name的值
List onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
//开始进行匹配
List missing = getMatches(onClasses, MatchType.MISSING, classLoader);
//因为使用的是MatchType.MISSING,返回的数据是没有匹配到的,如果有,则 OnClassCondition不生效
if (!missing.isEmpty()) {
return ConditionOutcome
//这里返回的match=false
.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes")
.items(Style.QUOTE, missing));
}
//这里暂时对我们没啥用
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class", "required classes").items(Style.QUOTE,
getMatches(onClasses, MatchType.PRESENT, classLoader));
}
//这是解析@ConditionalOnMissingClass的
List onMissingClasses = getCandidates(metadata,
ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
//这里使用的是MatchType.PRESENT,表示匹配到的数量
List present = getMatches(onMissingClasses, MatchType.PRESENT,
classLoader);
//不为空说明有指定类,则match=false
if (!present.isEmpty()) {
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(ConditionalOnMissingClass.class)
.found("unwanted class", "unwanted classes")
.items(Style.QUOTE, present));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE,
getMatches(onMissingClasses, MatchType.MISSING, classLoader));
}
//如果上面都没有问题,返回的match=true
return ConditionOutcome.match(matchMessage);
}
MatchType.MISSING和MatchType.PRESENT是怎么进行匹配的呢?
看懂了吗,就是使用类加载器去加载指定的类,加载到了说明存在,则返回true,抛出异常则说明没加载到,返回false。
到这里我们就说明白了@ConditionalOnClass的作用和原理,顺便看到了@ConditionalOnMissingClass的原理。