1.@Conditional注解

@Conditional注解是从spring4.0才有的,可以用在任何类型或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理。

可以通过@Conditional来控制bean是否需要注册,控制被@Configuration标注的配置类是需要需要被解析等。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

注解只有一个value参数,Condition类型的数组,Condition是一个接口,表示一个条件判断,内部有个方法返回true或false,当所有Condition都成立的时候,@Conditional的结果才成立。

Condition接口

用来表示条件判断的接口

@FunctionalInterface
public interface Condition {
 
    /**
     * 判断条件是否匹配
     * context:条件判断上下文
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
 
}

是一个函数式接口,内部只有一个matches方法,用来判断条件是否成立的,2个参数:

  • context:条件上下文,ConditionContext接口类型的,可以用来获取容器中的个人信息
  • metadata:用来获取被@Conditional标注的对象上的所有注解信息
ConditionContext接口

接口中提供了一些常用的方法,可以用来获取spring容器中的各种信息

public interface ConditionContext {
 
    /**
     * 返回bean定义注册器,可以通过注册器获取bean定义的各种配置信息
     */
    BeanDefinitionRegistry getRegistry();
 
    /**
     * 返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象
     */
    @Nullable
    ConfigurableListableBeanFactory getBeanFactory();
 
    /**
     * 返回当前spring容器的环境配置信息对象
     */
    Environment getEnvironment();
 
    /**
     * 返回资源加载器
     */
    ResourceLoader getResourceLoader();
 
    /**
     * 返回类加载器
     */
    @Nullable
    ClassLoader getClassLoader();
 
}

如果将Condition接口的实现类作为配置类上@Conditional中,那么这个条件会对两个阶段都有效,此时通过Condition是无法精细的控制某个阶段的,如果想控制某个阶段,比如可以让他解析,但是不能让他注册,此时就就需要用到另外一个接口了:ConfigurationCondition

ConfigurationCondition接口
public interface ConfigurationCondition extends Condition {
 
    /**
     * 条件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤
     */
    ConfigurationPhase getConfigurationPhase();
 
 
    /**
     * 表示阶段的枚举:2个值
     */
    enum ConfigurationPhase {
 
        /**
         * 配置类解析阶段,如果条件为false,配置类将不会被解析
         */
        PARSE_CONFIGURATION,
 
        /**
         * bean注册阶段,如果为false,bean将不会被注册
         */
        REGISTER_BEAN
    }
 
}

ConfigurationCondition接口相对于Condition接口多了一个getConfigurationPhase方法,用来指定条件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤

@Conditional使用步骤
  1. 自定义一个类,实现Condition或ConfigurationCondition接口,实现matches方法
  2. 在目标对象上使用@Conditional注解,并指定value的指为自定义的Condition类型
  3. 启动spring容器加载资源,此时@Conditional就会起作用了

2.条件判断执行时机

Spring对配置类的处理阶段
  1. 配置类解析阶段
    会得到一批配置类的信息,和一些需要注册的bean
  2. bean注册阶段
    将配置类解析阶段得到的配置类和需要注册的bean注册到spring容器中

spring中处理这2个阶段会循环进行,直到完成所有配置类的解析及所有bean的注册。

配置类种类

配置类的种类:

  1. 类上有@Component注解
  2. 类上有@Configuration注解
  3. 类上有@CompontentScan注解
  4. 类上有@Import注解
  5. 类上有@ImportResource注解
  6. 类中有@Bean标注的方法

判断一个类是不是一个配置类,是否的是下面这个方法:
org.springframework.context.annotation.ConfigurationClassUtils#isConfigurationCandidate

Spring对配置类的处理过程

源码位置:org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

大致过程如下:

  1. 通常我们会通过new AnnotationConfigApplicationContext()传入多个配置类来启动spring容器
  2. spring对传入的多个配置类进行解析
  3. 配置类解析阶段:这个过程就是处理配置类上面6中注解的过程,此过程中又会发现很多新的配置类,比如@Import导入的一批新的类刚好也符合配置类,而被@CompontentScan扫描到的一些类刚好也是配置类;此时会对这些新产生的配置类进行同样的过程解析
  4. bean注册阶段:配置类解析后,会得到一批配置类和一批需要注册的bean,此时spring容器会将这批配置类作为bean注册到spring容器,同样也会将这批需要注册的bean注册到spring容器
  5. 经过上面第3个阶段之后,spring容器中会注册很多新的bean,这些新的bean中可能又有很多新的配置类
  6. Spring从容器中将所有bean拿出来,遍历一下,会过滤得到一批未处理的新的配置类,继续交给第3步进行处理
  7. 3到6,这个过程会经历很多次,直到完成所有配置类的解析和bean的注册

过程分析:

  1. 可以在配置类上面加上@Conditional注解,来控制是否需要解析这个配置类,配置类如果不被解析,那么这个配置上面6种注解的解析都会被跳过
  2. 可以在被注册的bean上面加上@Conditional注解,来控制这个bean是否需要注册到spring容器中
  3. 如果配置类不会被注册到容器,那么这个配置类解析所产生的所有新的配置类及所产生的所有新的bean都不会被注册到容器

一个配置类被spring处理有2个阶段:配置类解析阶段、bean注册阶段(将配置类作为bean被注册到spring容器)。

3.@Conditional扩展注解

注解描述处理类使用方法
@ConditionalOnBean当容器中至少存在一个指定name或class的Bean时,进行实例化OnBeanCondition@ConditionalOnBean(CacheManager.class)
@ConditionalOnMissingBean当容器中指定name或class的Bean都不存在时,进行实例化OnBeanCondition@ConditionalOnMissingBean(CacheManager.class)
@ConditionalOnClass当类路径下至少存在一个指定的class时,进行实例化OnClassCondition@ConditionalOnClass({Aspect.class, Advice.class })
@ConditionalOnMissingClass当容器中指定class都不存在时,进行实例化OnClassCondition@ConditionalOnMissingClass(“org.thymeleaf.templatemode.TemplateMode”)
@ConditionalOnSingleCandidate当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化OnBeanCondition@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnProperty当指定的属性有指定的值时进行实例化OnPropertyCondition@ConditionalOnProperty(prefix = “spring.aop”, name = “auto”)
@ConditionalOnResource当类路径下有指定的资源时触发实例化OnResourceCondition@ConditionalOnResource(resources = “classpath:META-INF/build.properties”)
@ConditionalOnExpression基于SpEL表达式的条件判断,当为true的时候进行实例化OnExpressionCondition@ConditionalOnExpression(“true”)
@ConditionalOnWebApplication当项目是一个Web项目时进行实例化OnWebApplicationCondition@ConditionalOnWebApplication
@ConditionalOnNotWebApplication当项目不是一个Web项目时进行实例化OnWebApplicationCondition@ConditionalOnNotWebApplication
@ConditionalOnJava当JVM版本为指定的版本范围时触发实例化OnJavaCondition@ConditionalOnJava(ConditionalOnJava.JavaVersion.EIGHT)
@ConditionalOnJndi在JNDI存在的条件下触发实例化OnJndiCondition@ConditionalOnJndi({ “java:comp/TransactionManager”})

4.@ConditionalOnBean

@ConditionalOnBean源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {

// bean的类型,当ApplicationContext包含给定类的bean时返回true
Class<?>[] value() default {};


// bean的类型名,当ApplicationContext包含给定的id时返回true   
String[] type() default {};

// bean所声明的注解,当ApplicationContext中存在声明该注解的bean时返回true
Class<? extends Annotation>[] annotation() default {};

// bean的id,,当ApplicationContext中存在给定id的bean时返回true
String[] name() default {};

// 默认是所有上下文搜索
SearchStrategy search() default SearchStrategy.ALL;
}
SearchStrategy类源码
public enum SearchStrategy {

// 查询当前的context
CURRENT,

// 查询所有的祖先和父辈容器,但是不包含当前容器,从1.5开始废弃,推荐使用ANCESTORS
@Deprecated PARENTS,

// 搜索所有的祖先,不搜索当前的context
ANCESTORS,

// 搜索整个上下文
ALL
}
处理类OnBeanCondition类源码
public ConfigurationPhase getConfigurationPhase() {
    return ConfigurationPhase.REGISTER_BEAN;
}

OnBeanCondition是@ConditionalOnBean,@ConditionalOnSingleCandidate,@ConditionalOnMissingBean三个注解的处理类

使用案例

在DataSourceAutoConfiguration中声明了如下方法:

@Bean
@ConditionalOnMissingBean
public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties,
        ApplicationContext applicationContext) {
    return new DataSourceInitializer(properties, applicationContext);
}

表明当beanFactory中不存在DataSourceInitializer类型的bean时,才进行注册

5.@ConditionalOnMissingBean

@ConditionalOnMissingBean源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {

// bean的类型,当ApplicationContext不包含给定类的bean时返回true
Class<?>[] value() default {};

// bean的类型名,当ApplicationContext不包含给定的id时返回true
String[] type() default {};

// 给定的类型当进行匹配时进行忽略
Class<?>[] ignored() default {};


// 给定的类型名当进行匹配时进行忽略
String[] ignoredType() default {};

// bean所声明的注解,当ApplicationContext中不存在声明该注解的bean时返回true
Class<? extends Annotation>[] annotation() default {};

// bean的id,,当ApplicationContext中不存在给定id的bean时返回true
String[] name() default {};

// 默认是所有上下文搜索
SearchStrategy search() default SearchStrategy.ALL;
}

处理类OnBeanCondition源码
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
        // 3.1 实例化BeanSearchSpec
        BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
                ConditionalOnMissingBean.class);
        // 3.2 获得给定条件的beanNames
        List<String> matching = getMatchingBeans(context, spec);
        if (!matching.isEmpty()) {
            // 3.3 如果不为空,返回不匹配,否则返回匹配
            return ConditionOutcome.noMatch(ConditionMessage
                    .forCondition(ConditionalOnMissingBean.class, spec)
                    .found("bean", "beans").items(Style.QUOTE, matching));
        }
        matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
                .didNotFind("any beans").atAll();
    }
    return ConditionOutcome.match(matchMessage);
  1. 实例化BeanSearchSpec
  2. 获得给定条件的beanNames
  3. 如果不为空,返回不匹配,否则返回匹配
使用案例

在DataSourceAutoConfiguration中声明了如下方法:

@Bean
@ConditionalOnMissingBean
public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties,
        ApplicationContext applicationContext) {
    return new DataSourceInitializer(properties, applicationContext);
}

表明当beanFactory中不存在DataSourceInitializer类型的bean时,才进行注册

6.@ConditionalOnClass

@ConditionalOnClass源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

/**
 * 
 * 给定的类必须存在
 * @return the classes that must be present
 */
Class<?>[] value() default {};

/**
 * 
 * 给定的类名,该类名必须存在
 * @return the class names that must be present.
 */
String[] name() default {};
}
使用案例

AopAutoConfiguration声明了如下注解:

@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })

表明当在当前类路径存在EnableAspectJAutoProxy.class, Aspect.class, Advice.class时才对AopAutoConfiguration进行解析

7.@ConditionalOnMissingClass

@ConditionalOnMissingClass 源码

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

// 给定的类名在当前类路径下不存在时返回true
String[] value() default {};
}
使用案例

Thymeleaf2Configuration 声明了如下注解:

@ConditionalOnMissingClass("org.thymeleaf.templatemode.TemplateMode")

表明当在当前类路径不存在org.thymeleaf.templatemode.TemplateMode时才对Thymeleaf2Configuration进行解析

8.@ConditionalOnSingleCandidate

@ConditionalOnSingleCandidate源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnSingleCandidate {

/**
 * 
 * bean的类型,当ApplicationContext包含给定类的bean时并且如果有多个该类型的bean并且指定为primary的
 * 存在则返回true.
 * 
 * @return the class type of the bean to check
 */
Class<?> value() default Object.class;

/**
 * 
 * bean的类型名,当ApplicationContext包含给定的id并且如果有多个该类型的bean并且指定为primary的
 * 存在则返回true.
 * @return the class type name of the bean to check
 */
String type() default "";

/**
 * 
 * 默认是所有上下文搜索
 * @return the search strategy
 */
SearchStrategy search() default SearchStrategy.ALL;
}
使用案例

在DataSourceTransactionManagerConfiguration 声明了如下注解:

@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
static class DataSourceTransactionManagerConfiguration

标识:当DataSource类型的bean存在并且指定为Primary的DataSource存在时,加载DataSourceTransactionManagerConfiguration的配置

9.@ConditionalOnProperty

ConditionalOnProperty源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

    // name属性的别名
    String[] value() default {};

    // 属性前缀,如果该前缀不是.结尾的,则会自动加上
    String prefix() default "";

    // 属性名,如果前缀被声明了,则会拼接为prefix+name 去查找.通过-进行分割单词,name需要为小写
    String[] name() default {};

    // 表明所期望的结果,如果没有指定该属性,则该属性所对应的值不为false时才匹配
    String havingValue() default "";

    // 表明配置的属性如果没有指定的话,是否匹配,默认不匹配
    boolean matchIfMissing() default false;

    // 是否支持relaxed(松散匹配). 默认支持
    boolean relaxedNames() default true;
}

使用案例

在AopAutoConfiguration声明了如下注解:

@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)

表明: 如果配置了spring.aop.auto并且值为true时匹配,或者spring.aop.auto没配置时匹配

10.@ConditionalOnResource

@ConditionalOnResource源码:

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

    // 指定的资源必须存在,否则返回不匹配
    String[] resources() default {};

}

OnResourceCondition
@ConditionalOnResource 所对应的处理类为OnResourceCondition

使用案例

在ProjectInfoAutoConfiguration中声明了如下方法:

@ConditionalOnResource(resources = "${spring.info.build.location:classpath:META-INF/build-info.properties}")
@ConditionalOnMissingBean
@Bean
public BuildProperties buildProperties() throws Exception {
    return new BuildProperties(
            loadFrom(this.properties.getBuild().getLocation(), "build"));
}

当spring.info.build.location配置的资源如果存在的话 或者 spring.info.build.location没配置的话并且classpath:META-INF/build-info.properties 存在的话,则 进行进一步的处理–> @ConditionalOnMissingBean 注解的处理

11.@ConditionalOnWebApplication

@ConditionalOnWebApplication源码

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

}
使用案例

FreeMarkerWebConfiguration 声明了如下注解:

@ConditionalOnWebApplication

表明在web环境时加载该配置

12.@ConditionalOnNotWebApplication

@ConditionalOnNotWebApplication源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnNotWebApplication {

}
使用案例

FreeMarkerNonWebConfiguration声明了如下注解:

@ConditionalOnNotWebApplication

表明不在web环境时加载该配置

13.@ConditionalOnJava

@ConditionalOnJava源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnJavaCondition.class)
public @interface ConditionalOnJava {

/**
 * 
 * 表明是大于等于配置的JavaVersion还是小于配置的JavaVersion
 */
Range range() default Range.EQUAL_OR_NEWER;

/**
 * 
 * 配置要检查的java版本.使用range属性来表明大小关系
 * @return the java version
 */
JavaVersion value();
使用案例

ThymeleafJava8TimeDialect声明了如下注解:

@ConditionalOnJava(ConditionalOnJava.JavaVersion.EIGHT)

表明只有在1.8及以上的java环境下才加载ThymeleafJava8TimeDialect的配置

14.@ConditionalOnJndi

@ConditionalOnJndi源码:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnJndiCondition.class)
public @interface ConditionalOnJndi {

// 给定的jndi的Location 必须存在一个.否则,返回不匹配
String[] value() default {};
}

OnJndiCondition
@ConditionalOnJndi 对应的处理类为 OnJndiCondition

使用案例

JndiJtaConfiguration声明了如下注解

@ConditionalOnJndi({ JtaTransactionManager.DEFAULT_USER_TRANSACTION_NAME,
    "java:comp/TransactionManager", "java:appserver/TransactionManager",
    "java:pm/TransactionManager", "java:/TransactionManager" })

表明当jndi 在java:comp/UserTransaction,java:comp/TransactionManager,java:appserver/TransactionManager,java:pm/TransactionManager,java:/TransactionManager 路径上只要存在一个资源,则加载JndiJtaConfiguration的配置

参考文章:
@Conditional注解详解
@Conditional通过条件来控制bean的注册

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐