Spring中@Condition底层实现原理

Love The Way You Lie 2024-04-05 07:22 132阅读 0赞

@Condition是Spring中很关键的一个注解,它能够帮助我们只注册符合条件的Bean,我们可以通过配置、容器中的Bean、Java版本等筛选符合bean注册。

1.注解逻辑

以ConditionOnBean为例

  1. @Target({
  2. ElementType.TYPE, ElementType.METHOD })
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. @Conditional(OnBeanCondition.class)
  6. public @interface ConditionalOnBean {
  7. }

本质上引入了OnBeanCondition类来进行筛选,而这个类是继承于Condition类来实现的。

  1. @FunctionalInterface
  2. public interface Condition {
  3. /**
  4. * 该方法用于判断该注解条件是否符合,符合返回true
  5. **/
  6. boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
  7. }

2.SpringBoot扩展

SpringBoot为了使这个注解能够更加全面,定义了抽象类SpringBootCondition,所有的条件注解都是继承于此类,但SB提供的所有注解都没有实现matches方法,只有在SpringBootCondition中有,那么我们可以得出其实是使用了模版方法

  1. @Override
  2. public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  3. String classOrMethodName = getClassOrMethodName(metadata);
  4. try {
  5. // 本质通过子类getMatchOutcome来实现具体方法逻辑
  6. ConditionOutcome outcome = getMatchOutcome(context, metadata);
  7. logOutcome(classOrMethodName, outcome);
  8. recordEvaluation(context, classOrMethodName, outcome);
  9. return outcome.isMatch();
  10. }
  11. catch (NoClassDefFoundError ex) {
  12. xxx
  13. }
  14. catch (RuntimeException ex) {
  15. xxx
  16. }
  17. }

3.具体的getMatchOutcome方法

  1. @Override
  2. public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
  3. ConditionMessage matchMessage = ConditionMessage.empty();
  4. MergedAnnotations annotations = metadata.getAnnotations();
  5. // 如果是ConditionOnBean则会执行以下判断逻辑
  6. if (annotations.isPresent(ConditionalOnBean.class)) {
  7. Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
  8. // 关键方法用于获取匹配结果
  9. MatchResult matchResult = getMatchingBeans(context, spec);
  10. if (!matchResult.isAllMatched()) {
  11. String reason = createOnBeanNoMatchReason(matchResult);
  12. return ConditionOutcome.noMatch(spec.message().because(reason));
  13. }
  14. matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
  15. matchResult.getNamesOfAllMatches());
  16. }
  17. // 省略其他条件注解判断逻辑
  18. }

定位到getMatchingBeans方法,知道本质就是通过去容器获取该类型的beandefinition名称,后续会对获取到的名称做筛选,筛选出满足的条件

  1. // 核心逻辑
  2. Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type, parameterizedContainers);

同理我们可以看其他条件注解
@ConditionalOnClass
@ConditionalOnProperty等具体实现也是获取环境配置或者class.forName去判断是否有这个类。

2.调用入口

1.BeanFactoryPostProcessor

condition和BeanFactoryPostProcessor有什么关系呢?
我们都知道@condition注解的作用就是避免注册某些我们不需要的bean。那我们入手就在bean的扫描过程。那么我们定义到自动配置类扫描代码处,即ConfigurationClassPostProcessor类

我们定位到配置类注册逻辑处,postProcessBeanDefinitionRegistry() -> processConfigBeanDefinitions()

可以发现内部有parse类就是用于解析配置。最后跟踪到processConfigBeanDefinitions处会发现关键调用入口

  1. if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
  2. return;
  3. }

最后我们跟踪到ConditionEvaluator就能发现本质就是调用了matches方法进行匹配

  1. public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
  2. if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
  3. return false;
  4. }
  5. if (phase == null) {
  6. if (metadata instanceof AnnotationMetadata &&
  7. ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
  8. return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
  9. }
  10. return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
  11. }
  12. // 解析注解condition
  13. List<Condition> conditions = new ArrayList<>();
  14. for (String[] conditionClasses : getConditionClasses(metadata)) {
  15. for (String conditionClass : conditionClasses) {
  16. Condition condition = getCondition(conditionClass, this.context.getClassLoader());
  17. conditions.add(condition);
  18. }
  19. }
  20. AnnotationAwareOrderComparator.sort(conditions);
  21. for (Condition condition : conditions) {
  22. ConfigurationPhase requiredPhase = null;
  23. if (condition instanceof ConfigurationCondition) {
  24. requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
  25. }
  26. // matches方法进行判断
  27. if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
  28. return true;
  29. }
  30. }
  31. return false;
  32. }

看到这里会不会有个疑问,明明是按顺序解析Bean信息,如果使用了@ConditionOnBean,但解析该配置类时并未引入相同类型的Bean,解析完再解析其他同类型的Bean,该条件注解不就不起作用了吗?

是的,解析顺序在条件注解判断时候也很重要,像ConditionOnBean判断容器中是否有该bean,但该bean还未注册到容器中,相当于判断就失效了,所有bean的解析顺序也很重要,所以我们会发现一般我们会先注册自己代码中的bean,然后扫描自动配置的bean。而同一配置类内我们可以用@AutoConfigureBefore等注解指定扫描顺序

3.注解扫描顺序

我们查看AutoConfigurationSorter中的getInPriorityOrder方法

  1. 按照字母顺序排序
  2. 按照@AutoConfigureOrder排序
  3. 按照@AutoConfigureBefore @AutoConfigureAfter排序

    List getInPriorityOrder(Collection classNames) {

    1. AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
    2. this.autoConfigurationMetadata, classNames);
    3. List<String> orderedClassNames = new ArrayList<>(classNames);
    4. // 按字母顺序排序
    5. Collections.sort(orderedClassNames);
    6. // 然后按照order顺序排序
    7. orderedClassNames.sort((o1, o2) -> {
    8. int i1 = classes.get(o1).getOrder();
    9. int i2 = classes.get(o2).getOrder();
    10. return Integer.compare(i1, i2);
    11. });
    12. // 最后按照 @AutoConfigureBefore @AutoConfigureAfter排序
    13. orderedClassNames = sortByAnnotation(classes, orderedClassNames);
    14. return orderedClassNames;

    }

经过上面的排序后就能保证配置类的加载顺序,保证Bean创建流程。

总结

@Condition注解帮助我们选择适当的bean进行注册,而不同的bean注册时候又有错综复杂的依赖关系,在SpringBoot中都帮我们很好解决了。

发表评论

表情:
评论列表 (有 0 条评论,132人围观)

还没有评论,来说两句吧...

相关阅读