MySQL, Oracle, Linux, 软件架构及大数据技术知识分享平台

网站首页 > 精选文章 / 正文

@Conditional注解的原理及衍生注解的原理

2025-02-24 16:38 huorong 精选文章 1 ℃ 0 评论

在一些框架集成中,我们可以看到各种各样的@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的原理。

Tags:conditionalonproperty

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言