美文网首页
spring源码------@Conditional注解的解析C

spring源码------@Conditional注解的解析C

作者: szhlcy | 来源:发表于2019-10-22 11:24 被阅读0次

    1.SpringBoot中的共同点

     在springBoot中有很多这种标签@ConditionalOnXXX标签让springBoot的代码更加标签化配置更加灵活。这些标签都有共同点,这里例举两个标签的源码

    1.1``@ConditionalOnXXX`
    ......
    @Conditional({OnClassCondition.class})
    public @interface ConditionalOnClass {
        ......
    }
    
    ......
    @Conditional({OnBeanCondition.class})
    public @interface ConditionalOnBean {
        ......
    }
    

     发现这些标签的共同点是上面都贴有@Conditional标签,然后在进入到这个标签里面的值的类看看

    class OnClassCondition extends FilteringSpringBootCondition {
        ......
    }
    
    class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
        ......
    }
    

     这里发现又有一个共同点这两个类都是FilteringSpringBootCondition的子类,但是这个类是springBoot扩展实现的我们要找的是源头在spring中类,网上找可以发现这个类最后间接实现了spring的Condition。因此我们后面要找的就是Condition类。

    2. Conditional标签的解析

    2.1Condition介绍

     在Condition类中只有一个方法matches方法。这类的作用是,在bean的定义即将被注册之前,会检查条件是否匹配,然后根据匹配的结果决定是否注册bean。

    /**
     * A single {@code condition} that must be {@linkplain #matches matched} in order
     * for a component to be registered.
     *
     * <p>Conditions are checked immediately before the bean-definition is due to be
     * registered and are free to veto registration based on any criteria that can
     * be determined at that point.
     *
     * <p>Conditions must follow the same restrictions as {@link BeanFactoryPostProcessor}
     * and take care to never interact with bean instances. For more fine-grained control
     * of conditions that interact with {@code @Configuration} beans consider the
     * {@link ConfigurationCondition} interface.
     *
     * @author Phillip Webb
     * @since 4.0
     * @see ConfigurationCondition
     * @see Conditional
     * @see ConditionContext
     */
    @FunctionalInterface
    public interface Condition {
    
        boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
    
    }
    

     检查条件是否匹配的方法就是matches,在源码的类描述中提到了一点。如果实现的是这个类,那么必须注意,在实现方法也就是matches中不能与bean的实例交互。之所以要注意这一点是因为这个方法的调用时间在bean的实例化之前的,此时如果跟实例交互就会提前实例化bean,可能会引起错误。如果想要对贴有@Configuration标签的bean更细粒度的控制可以通过实现ConfigurationCondition来完成。

    2.2ConditionEvaluator类处理match方法

     通过查看Conditionmatches在哪里被调用。发现整个spring中只有在ConditionEvaluator中调用了这个方法。这个类的作用是评估一个贴了Conditional注解的类是否需要跳过。通过类上面的注解来判断。进入到类方法

        //metadata是AnnotationMetadataReadingVisitor类型的,在5.2版本被SimpleAnnotationMetadataReadingVisitor代替
        public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
            //检查注解中是否包含@Conditional类型的注解
            if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
                return false;
            }
            //如果没有指定了当前bean是解析还是注册
            if (phase == null) {
                //bean的注解信息封装对象是AnnotationMetadata类型并且,类上有@Component,@ComponentScan,@Import,@ImportResource,则表示为解析类型
                if (metadata instanceof AnnotationMetadata &&
                        ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
                    return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
                }
                return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
            }
    
            List<Condition> conditions = new ArrayList<>();
            //从bean的注解信息封装对象中获取所有的Conditional类型或者Conditional的派生注解
            for (String[] conditionClasses : getConditionClasses(metadata)) {
                for (String conditionClass : conditionClasses) {
                    //实例化Conditional中的条件判断类(Condition的子类)
                    Condition condition = getCondition(conditionClass, this.context.getClassLoader());
                    //添加到条件集合中
                    conditions.add(condition);
                }
            }
            //根据Condition的优先级进行排序
            AnnotationAwareOrderComparator.sort(conditions);
    
            for (Condition condition : conditions) {
                ConfigurationPhase requiredPhase = null;
                //如果是ConfigurationCondition类型的Condition
                if (condition instanceof ConfigurationCondition) {
                    //获取需要对bean进行的操作,是解析还是注册
                    requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
                }
                //(如果requiredPhase==null或者指定的操作类型是目前阶段的操作类型)并且不符合设置的条件则跳过
                if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
                    return true;
                }
            }
    
            return false;
        }
    

     上面这个方法作用就是判断当前bean处于解析还是注册,如果处于解析阶段则跳过,如果处于注册阶段则不跳过。其中Conditionmatches方法就起到了判断的是否符合的作用,进而觉得是否跳过当前bean。

    2.3 ConfigurationClassPostProcessorprocessConfigBeanDefinitions

     还是通过查找ConditionEvaluator类的match方法调用链的方式,发现最后都是在ConfigurationClassPostProcessorprocessConfigBeanDefinitions中进行调用的。一共有两个调用的位置,这里用调用的位置的代码进行展示

        public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
            List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
            //获取registry中定义的所有的bean的name
            String[] candidateNames = registry.getBeanDefinitionNames();
            ......
            do {
                //第一个会调用shouldSkip的位置,这里是解析能够直接获取的候选配置bean。可能是Component,ComponentScan,Import,ImportResource或者有Bean注解的bean
                parser.parse(candidates);
                parser.validate();
                //获取上面封装已经解析过的配置bean的ConfigurationClass集合
                Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
                //移除前面已经处理过的
                configClasses.removeAll(alreadyParsed);
                //第二个会调用shouldSkip的位置,这里是加载configurationClasse中内部可能存在配置bean,比如方法上加了@Bean或者@Configuration标签的bean
                this.reader.loadBeanDefinitions(configClasses);
                alreadyParsed.addAll(configClasses);
                }
            ......
        }
    

     这里的parse方法解析BeanDefinitionRegistry中能直接获取到的候选bean,并解析保存到ConfigurationClassParser类的保存解析过的配置类的集合configurationClasses中。
    loadBeanDefinitions则是对上面解析的集合configurationClasses中的bean内部的进一步的处理,处理类内部定义的bean。

    2.4ConfigurationClassParserparse方法
    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        ......
            try {
                    if (bd instanceof AnnotatedBeanDefinition) {
                        parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                    }
                 }
         ......
    }
    
    protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
            processConfigurationClass(new ConfigurationClass(metadata, beanName));
        }
    

     在ConfigurationClassParserparse方法中有三个分支,分别是对不同类型的BeanDefinition进行解析,这里进入AnnotatedBeanDefinition类型的。
     进入到parse方法后在进入里面调用的processConfigurationClass方法,这里只需要分析开头就知道了

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
            //检查当前解析的配置bean是否包含Conditional注解,如果不包含则不需要跳过
            // 如果包含了则进行match方法得到匹配结果,如果是符合的并且设置的配置解析策略是解析阶段不需要调过
            if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
                return;
            }
        ......
        //后面的步骤就是解析配置bean,然后进行注册的操作
    }
    

     可以看到这里就是对是否跳过bean解析的位置。在这里Conditional标签的作用就完了。主要就是觉得当前的配置bean是否符合我们规定的规则,不符合就不会注册。

    2.5ConfigurationClassBeanDefinitionReaderloadBeanDefinitions方法

     这里对上面已经解析过的bean类集合的内部进行处理的步骤。是一个循环迭代处理的过程

        public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
            //对import标签处理的类
            TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
            //循环处理
            for (ConfigurationClass configClass : configurationModel) {
                
                loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
            }
        }
    

    TrackedConditionEvaluator主要是处理@import标签同时也you的。举个例子:如果A类通过@import类引入了另外的一个B类,如果A类需要跳过解析,那么B类也肯定需要调过解析。如果A类需要进行解析,那么B类也需要进行解析。后面会分析内部的方法,现在进入到loadBeanDefinitionsForConfigurationClass方法。

        private void loadBeanDefinitionsForConfigurationClass(
                ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
            //检查当前的bean是否是通过@import注解引入的,如果是的则循环解析到原始的贴有@import标签的bean,检查是否有@conditionl标签并检查是否需要跳过
            if (trackedConditionEvaluator.shouldSkip(configClass)) {
                //获取当前配置bean的beanName
                String beanName = configClass.getBeanName();
                //如果当前beanName在BeanDefinitionRegistry中需要注册的bean的列表中则移除,因为这个bean需要被跳过
                if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
                    this.registry.removeBeanDefinition(beanName);
                }
                //移除在ImportRegistry中imports列表中的该beanName,因为这个bean需要被跳过
                this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
                return;
            }
            //如果当前配置bean是通过@import注解进行注入的则进行注册
            if (configClass.isImported()) {
                registerBeanDefinitionForImportedConfigurationClass(configClass);
            }
            //获取当前配置类的BeanMethod,就是在方法上面贴了@Bean注解的方法
            for (BeanMethod beanMethod : configClass.getBeanMethods()) {
                //进行加载注入
                loadBeanDefinitionsForBeanMethod(beanMethod);
            }
            //加载configClass中的配置的resource
            loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
            //加载configClass中的配置的ImportBeanDefinitionRegistrar
            loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
        }
    

     这里理解还是比较简单的,就是判断当前的配置类是否需要跳过,因为当前的配置类也可能是通过@import标签进行引入的,所以有必要进行对最原始的进行引入的类进行分析,决定当前的类是否需要跳过。如果需要调过,则一处对应的需要进行注册bean列表中的改bean,如果不需要则进行注册处理。这里关键在于当前的bean是否需要调过,就在TrackedConditionEvaluator类的shouldSkip方法中,而这个类也只有这一个方法。这里比较难以理解,我也是看来半天才理解的。可以多看几遍,debug更好。

        private class TrackedConditionEvaluator {
    
            private final Map<ConfigurationClass, Boolean> skipped = new HashMap<>();
    
            public boolean shouldSkip(ConfigurationClass configClass) {
                //检查当前的配置类是否需要调过,因为这里是前面的直接获取到的配置类,如果当前类需要跳过那么,内部的也必定需要跳过
                //如果是null则说明这个类不是需要跳过的,但是也不代表是不需要跳过的,因为如果是被引入的则决定于最外面的一层bean是否需要跳过
                Boolean skip = this.skipped.get(configClass);
                if (skip == null) {
                    //当前的配置bean是不是通过@import注解引入的,不是的则不需要跳过,因为只有是在bean内部定义的bean才需要判断外面的一层bean是否需要跳过
                    if (configClass.isImported()) {
                        boolean allSkipped = true;
                        //获取通过@import标签引入这个配置类的bean
                        for (ConfigurationClass importedBy : configClass.getImportedBy()) {
                            //检查引入配置类的bean的是否需要跳过(这里一直会检查到最终的引入类,来决定是否需要全部跳过)
                            if (!shouldSkip(importedBy)) {
                                allSkipped = false;
                                break;
                            }
                        }
                        //如果所有的bean(1.当前的配置bean,2.引入当前配置bean) 的bean 都是需要跳过的,则这个配置bean需要跳过
                        if (allSkipped) {
                            // The config classes that imported this one were all skipped, therefore we are skipped...
                            skip = true;
                        }
                    }
                    //能够到这一步的是最层的bean,例如A引入了B,B引入了C,那么A就是最外层的bean,检查A对应的@condition决定是否需要跳过,
                    if (skip == null) {
                        //这里就是对ConditionEvaluator方法的调用
                        skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
                    }
                    //将对应的配置bean记录起来是否需要跳过
                    this.skipped.put(configClass, skip);
                }
                return skip;
            }
        }
    

     其实最后目的还是很简单的就是最终到最上级的bean,来决定当前bean是否需要进行注册。前面举得例子就是这个意思。

    3. spring中的总结

     对于Conditional标签的解析上面就是全部的了。作用就是来决定贴了这个注解的bean,通过指定的Condition实现类实现matches方法来决定是否需要进行解析,需要进行注册。代码在调用链
    AbstractApplicationContext

    public void refresh() throws BeansException, IllegalStateException {
        .......
        invokeBeanFactoryPostProcessors(beanFactory);
        ......
    }
    
        protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
            PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
            ......
    }
    

     进入到PostProcessorRegistrationDelegate类,在这个类的invokeBeanFactoryPostProcessors方法中会多次调用到ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistrypostProcessBeanFactory方法。在这两个方法中都会调用上面讲解的processConfigBeanDefinitions方法进而处理Condition标签。

    4.springboot中的扩展

     在spring中实现Condition接口的类很少,在springboot中才广泛的运用到了。而springboot也在spring的基础上做了一个基础的扩展实现,然后再在这个基础的扩展实现上进一步扩展的。这个扩展的实现类就是SpringBootCondition类。现在进入到这个类

    public abstract class SpringBootCondition implements Condition {
        @Override
        public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            //从封装condition注解信息的类中获取指定的Condition类的实现类
            String classOrMethodName = getClassOrMethodName(metadata);
            try {
                //确定匹配结果以及日志输出对象
                ConditionOutcome outcome = getMatchOutcome(context, metadata);
                //打印匹配的情况,如果是不匹配会打印不匹配的原因
                logOutcome(classOrMethodName, outcome);
                //将匹配结果进行存储
                recordEvaluation(context, classOrMethodName, outcome);
                //返回匹配的结果
                return outcome.isMatch();
            }
            catch (NoClassDefFoundError ex) {
                throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
                        + ex.getMessage() + " not found. Make sure your own configuration does not rely on "
                        + "that class. This can also happen if you are "
                        + "@ComponentScanning a springframework package (e.g. if you "
                        + "put a @ComponentScan in the default package by mistake)", ex);
            }
            catch (RuntimeException ex) {
                throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
            }
        }
    
        public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
    
    }
    

     可以看到这里的扩展的就是对匹配的结果进行封装,然后打印以及存储。其中getMatchOutcome是由各个SpringBootCondition的实现类去实现的。作用就是判断各自按照各自的使用条件来判断是否符合来返回匹配的结果。

    相关文章

      网友评论

          本文标题:spring源码------@Conditional注解的解析C

          本文链接:https://www.haomeiwen.com/subject/bxqqvctx.html