Spring中的@Conditional注解实现分析

 更新时间:2023年12月29日 09:21:14   作者:it_lihongmin  
这篇文章主要介绍了Spring中的@Conditional注解实现分析,  @Conditional是Spring 4出现的注解,但是真正露出价值的是Spring Boot的扩展@ConditionalOnBean等,需要的朋友可以参考下

@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执行时机比较清楚了

再看看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的情况。

到此这篇关于Spring中的@Conditional注解实现分析的文章就介绍到这了,更多相关@Conditional注解实现分析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MyBatis-Generator的配置说明和使用

    MyBatis-Generator的配置说明和使用

    本文主要介绍了MyBatis-Generator的配置说明和使用的相关知识。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • idea中同一SpringBoot项目多端口启动

    idea中同一SpringBoot项目多端口启动

    本文主要介绍了idea中同一SpringBoot项目多端口启动,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • 使用java实现http多线程断点下载文件(二)

    使用java实现http多线程断点下载文件(二)

    下载工具我想没有几个人不会用的吧,前段时间比较无聊,花了点时间用java写了个简单的http多线程下载程序,我实现的这个http下载工具功能很简单,就是一个多线程以及一个断点恢复,当然下载是必不可少的,需要的朋友可以参考下
    2012-12-12
  • Java实现获取Excel中的表单控件

    Java实现获取Excel中的表单控件

    Excel中可通过【开发工具】菜单栏下插入表单控件,如文本框、单选按钮、复选框、组合框等等。本文将利用Java实现获取Excel中的表单控件,需要的可以参考一下
    2022-05-05
  • Springboot集成kafka高级应用实战分享

    Springboot集成kafka高级应用实战分享

    这篇文章主要介绍了Springboot集成kafka高级应用实战分享,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-08-08
  • 详解SpringBoot配置devtools实现热部署

    详解SpringBoot配置devtools实现热部署

    本篇文章主要介绍了详解SpringBoot配置devtools实现热部署 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • 详解Java如何实现一个优秀的散列表

    详解Java如何实现一个优秀的散列表

    这篇文章主要通过简单的示例为大家详细介绍了在Java中如何实现一个优秀的散列表,文中的示例代码讲解详细,具有一定的参考价值,需要的可以了解一下
    2023-07-07
  • 自带IDEA插件的阿里开源诊断神器Arthas线上项目BUG调试

    自带IDEA插件的阿里开源诊断神器Arthas线上项目BUG调试

    这篇文章主要为大家介绍了自带IDEA插件阿里开源诊断神器Arthas线上项目BUG调试,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • MyBatis-Plus中使用EntityWrappe进行列表数据倒序设置方式

    MyBatis-Plus中使用EntityWrappe进行列表数据倒序设置方式

    这篇文章主要介绍了MyBatis-Plus中使用EntityWrappe进行列表数据倒序设置方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 详解Spring Boot集成MyBatis(注解方式)

    详解Spring Boot集成MyBatis(注解方式)

    本篇文章主要介绍了详解Spring Boot集成MyBatis(注解方式),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05

最新评论