Spring源码 - @Conditional实现分析

 

    @Conditional是Spring 4出现的注解,但是真正露出价值的是Spring Boot的扩展@ConditionalOnBean等。但是任然使用的是Spring框架进行处理,并没有做太多定制的东西,所以还是先看看@Conditional注解的实现。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition Conditions} that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

    先看看@Conditional注解的结构比较简单,只需要定义一个Condition子类即可,并且说明只有满足了当前Condition的matches方法时才会将当前@Component注册成Bean。那么再看看Condition接口和体系。

/**
 * @since 4.0
 * @see ConfigurationCondition
 * @see Conditional
 * @see ConditionContext
 */
@FunctionalInterface
public interface Condition {

    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

    当前会传入ConditionContext和AnnotatedTypeMetadata进行回调,返回是否匹配,如果不匹配则不会注册成Bean。但是这是在哪里进行回调的呢?

ConfigurationClassParser # processConfigurationClass (ConfigurationClassParser # doProcessConfigurationClass等)

ConditionEvaluator # shouldSkip

比较清楚了,又是在处理@Import、@ComponentScan、ImportSelector等的处理类ConfigurationClassParser执行时机比较清楚了(详细可以查看:SpringIoc源码(十一)- ApplicationContext(七)- refresh(ConfigurationClassPostProcessor下))。

 

再看看Condition的结构体系:

大致有四类

1)、ProfileCondition,项目启动后Profile信息存放在ApplicationContext的Environment中,能拿到两者之一就可以判断。

2)、ConfigurationCondition

public interface ConfigurationCondition extends Condition {

    // 定义了子类必须实现,返回下面枚举的一种
    ConfigurationPhase getConfigurationPhase();

    // 判断阶段
	enum ConfigurationPhase {
            
        // Conponent阶段,即将@Component加入到BeanFactory
        PARSE_CONFIGURATION,

        // 只有通过getBean才能正在将Bean注册到Ioc容器中。前提是要将BeanDefinition添加到 
        // BeanFactory中
        REGISTER_BEAN
    }
}

3)、ConditionEvalutionReport,Spring Boot报表相关

4)、SpringBootCondition,直接是Spring Boot中扩展的。下一篇博客,具体分析 @ConditionalOnBean等再具体分析。 

 

    几个的角色比较清楚了,只要一个@Conditional注解,注解的属性为@Condition或其子类。根据回调@Condition的matches方法,即可判断是否将注册成Bean。

    先看看回调时机,都是在处理@Component、@ComponentSacn、ImportSelector等情况注册Bean时。由于情况比较复杂,可能@Component上有@ComponentScan,则会递归进行处理,总之都会先调用ConfigurationClassParser的ConditionEvaluator conditionEvaluator的shouldSkip方法判断是否跳过。比如:

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(),  
           ConfigurationPhase.PARSE_CONFIGURATION)) {

        return;
    }

    // 省略其余代码
}

    而conditionEvaluator在ConfigurationClassParser的构造器中被初始化,如下:

this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);

  

    先看看ConditionEvaluator的类结构

class ConditionEvaluator {

    private final ConditionContextImpl context;

    public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
			@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {

		this.context = new ConditionContextImpl(registry, environment, resourceLoader);
	}
    
    // 省略其他方法

    private static class ConditionContextImpl implements ConditionContext {

        private final BeanDefinitionRegistry registry;

        private final ConfigurableListableBeanFactory beanFactory;

        private final Environment environment;

        private final ResourceLoader resourceLoader;

        private final ClassLoader classLoader;

        public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry,
                                    @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {

            this.registry = registry;
            this.beanFactory = deduceBeanFactory(registry);
            this.environment = (environment != null ? environment : deduceEnvironment(registry));
            this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry));
            this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);
        }
    }
}

    也就是说,在ConfigurationClassParser构造器中初始化ConditionEvaluator时候,就初始化了内部类ConditionContextImpl,并且传入了BeanFactory(Bean是否存在可以通过工厂进行判断)、Environment(环境配置、Profile等存放在其中)、ResourceLoader(Spring的Resource加载器)、ClassLoader(类加载器)等。

    到这里就比较清楚了,回调Condition的matches接口时传入这些组件的类ConditionContextImpl,要实现@ConditionalOnBean、@OnPropertyCondition、@Profile、@ConditionalOnClass等都比较简单了。

 

    在调用Condition的matches时,不仅传入了ConditionContextImpl,还出入了AnnotatedTypeMetadata,这是当前注解结合被Spring封装的注解元信息。理解比较抽象,比如自动装配EmbeddedTomcat需要同时存在Servlet、Tomcat、upgradeProtocol类;并且没有将ServletWebServerFactory注册成Bean,此时Component才能真正生效,如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
    // 省略其他代码
}

    具体再看看ConditionEvaluator的shouldSkip方法的实现:

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    // 注解信息不能为空, 并且必须有Conditional或者其子类
    if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
        return false;
    }
    // 如果判断阶段为空,进行类型判断再递归调用该方法
    if (phase == null) {
        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<>();
    // 获取多有的Condition类型,如上面的EmbeddedTomcat同时需要多个条件成立
    for (String[] conditionClasses : getConditionClasses(metadata)) {
        for (String conditionClass : conditionClasses) {
            Condition condition = getCondition(conditionClass, this.context.getClassLoader());
            conditions.add(condition);
        }
    }
    // 条件排序(根据Spring的那三个排序方式定义)
    AnnotationAwareOrderComparator.sort(conditions);

    for (Condition condition : conditions) {
        ConfigurationPhase requiredPhase = null;
        if (condition instanceof ConfigurationCondition) {
            requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
        }
        // 判断阶段为空(非ConfigurationCondition的子类)、不需要判断阶段,则直接返回true
        // 否则才调用matches接口判断
        if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
            return true;
        }
    }
    return false;
}

1)、注解信息不能为空, 并且必须有Conditional或者其子类

2)、阶段为null,则根据情况设置阶段后再递归调用该方法

3)、获取所有的Condition列表

4)、进行排序

5)、遍历Condition,是否在该阶段进行判断。需要则再调用该Condition的matches方法

 

总结:添加了@Conditional或者@ConditionXXX注解,其value值会对应一个Condition或者子类的Class。在处理@ComponentScan、ImportSelector等时会根据判断阶段,调用Condition的matches方法判断是否进行注册成Bean。从而实现各种复杂的动态判断注册成Bean的情况。

 

 

©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值