深入浅出Spring和Spring Boot条件注解

前言

参考链接1

org.springframework.context.annotation.Conditional

源码

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

	Class<? extends Condition>[] value();

}

需要一个Condition的子类作为value参数

@FunctionalInterface
public interface Condition {

	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

实现接口自定义判断逻辑,返回true代表条件成立,返回false代表条件不成立。

如何使用

第一步:实现接口中的matches方法。

public class MyCondition1 implements org.springframework.context.annotation.Condition {

    private final static Logger logger = LoggerFactory.getLogger(MyCondition1.class);

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        logger.info("reject...");
        return false;
    }
}

第二步:将 @Conditional(value = MyCondition1.class) 添加到配置类或者bean上。

多个条件指定优先级

class Condition1 implements org.springframework.context.annotation.Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }
}

class Condition2 implements org.springframework.context.annotation.Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }
}

class Condition3 implements org.springframework.context.annotation.Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }
}

@Configuration
@Conditional({Condition1.class, Condition2.class, Condition3.class})
public class Config7 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config7.class);
    }
}

输出

com.example.lurenjia.spring.c19.config.Condition1
com.example.lurenjia.spring.c19.config.Condition2
com.example.lurenjia.spring.c19.config.Condition3

默认情况下按照数组的顺序依次执行。

@Order(1)

给第三个添加注解

@Order(1)
class Condition3 implements org.springframework.context.annotation.Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }
}

输出

com.example.lurenjia.spring.c19.config.Condition3
com.example.lurenjia.spring.c19.config.Condition1
com.example.lurenjia.spring.c19.config.Condition2

implements Ordered

class Condition3 implements org.springframework.context.annotation.Condition, Ordered {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

输出

com.example.lurenjia.spring.c19.config.Condition3
com.example.lurenjia.spring.c19.config.Condition1
com.example.lurenjia.spring.c19.config.Condition2

implements PriorityOrdered

class Condition3 implements org.springframework.context.annotation.Condition, PriorityOrdered {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

输出

com.example.lurenjia.spring.c19.config.Condition3
com.example.lurenjia.spring.c19.config.Condition1
com.example.lurenjia.spring.c19.config.Condition2

Condition 失效的情况

配置类

@Configuration
@Import({Config1.class, Config2.class})
// @Import({Config1.class, Config3.class})
// @Import({Config1.class, Config4.class})
class ConfigAll {

}

@Configuration
class Config1 {

    private static final Logger logger = LoggerFactory.getLogger(Config2.class);

    @Bean
    public String str1() {
        logger.info("str1 created ");
        return "str1";
    }
}

@Conditional(value = My4Condition.class)
@Configuration
class Config2 {

    private static final Logger logger = LoggerFactory.getLogger(Config2.class);

    @Bean
    public Integer int1() {
        logger.info("int1 created ");
        return Integer.valueOf(129);
    }

}

@Configuration
class Config3 {

    private static final Logger logger = LoggerFactory.getLogger(Config3.class);

    @Conditional(value = My4Condition.class)
    @Bean
    public Integer int1() {
        logger.info("int1 created ");
        return Integer.valueOf(129);
    }

}

@Conditional(value = My4ConfigurationCondition.class)
@Configuration
class Config4 {

    private static final Logger logger = LoggerFactory.getLogger(Config4.class);

    @Bean
    public Integer int1() {
        logger.info("int1 created ");
        return Integer.valueOf(129);
    }
}

启动类

public class Client {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigAll.class);
    }
}

My4Condition :判断容器中是否存在String类型bean

public class My4Condition implements org.springframework.context.annotation.Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        Map<String, String> map = context.getBeanFactory().getBeansOfType(String.class);
        System.out.println("map.size() = " + map.size());
        return !map.isEmpty();
    }
}

My4ConfigurationCondition :REGISTER_BEAN这个阶段判断

public class My4ConfigurationCondition implements ConfigurationCondition {
    @Override
    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.REGISTER_BEAN;
        // return ConfigurationPhase.PARSE_CONFIGURATION;
    }

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        Map<String, String> map = context.getBeanFactory().getBeansOfType(String.class);
        System.out.println("map.size() = " + map.size());
        return !map.isEmpty();
    }
}

正确的输出

str1 created 
map.size() = 1
int1 created 

错误的输出

map.size() = 0
str1 created 

说明:Config2 中,注解解析的过程中无法获取到bean,Config3和Config4是正确的修改方式。

ConfigurationCondition

源码

public interface ConfigurationCondition extends Condition {

	ConfigurationPhase getConfigurationPhase();
	
	enum ConfigurationPhase {
	
		PARSE_CONFIGURATION,
		REGISTER_BEAN
	}
}

ConfigurationCondition 可以指定PARSE_CONFIGURATION或者REGISTER_BEAN这两个阶段进行条件判断。

PARSE_CONFIGURATION:解析配置。
REGISTER_BEAN:注册bean。

比起Condition 更加细粒度的控制条件语句生效条件。

ConfigurationClassPostProcessor

org.springframework.boot.autoconfigure.condition包下的注解

深入浅出Spring和Spring Boot条件注解_第1张图片

ConditionalOnBean

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

	Class<?>[] value() default {};
	
	String[] type() default {};
	
	Class<? extends Annotation>[] annotation() default {};
	
	String[] name() default {};

	SearchStrategy search() default SearchStrategy.ALL;
	
	Class<?>[] parameterizedContainer() default {};
}

修饰范围:类型、方法。

案例

ConditionalOnClass

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

	Class<?>[] value() default {};

	String[] name() default {};

}

ConditionalOnCloudPlatform

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

	/**
	 * The {@link CloudPlatform cloud platform} that must be active.
	 * @return the expected cloud platform
	 */
	CloudPlatform value();

}

ConditionalOnExpression

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

	/**
	 * The SpEL expression to evaluate. Expression should return {@code true} if the
	 * condition passes or {@code false} if it fails.
	 * @return the SpEL expression
	 */
	String value() default "true";

}

ConditionalOnJava

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

	/**
	 * Configures whether the value configured in {@link #value()} shall be considered the
	 * upper exclusive or lower inclusive boundary. Defaults to
	 * {@link Range#EQUAL_OR_NEWER}.
	 * @return the range
	 */
	Range range() default Range.EQUAL_OR_NEWER;

	/**
	 * The {@link JavaVersion} to check for. Use {@link #range()} to specify whether the
	 * configured value is an upper-exclusive or lower-inclusive boundary.
	 * @return the java version
	 */
	JavaVersion value();

	/**
	 * Range options.
	 */
	enum Range {

		/**
		 * Equal to, or newer than the specified {@link JavaVersion}.
		 */
		EQUAL_OR_NEWER,

		/**
		 * Older than the specified {@link JavaVersion}.
		 */
		OLDER_THAN

	}

}

ConditionalOnJndi

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

	/**
	 * JNDI Locations, one of which must exist. If no locations are specific the condition
	 * matches solely based on the presence of an {@link InitialContext}.
	 * @return the JNDI locations
	 */
	String[] value() default {};

}

ConditionalOnMissingBean

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

	/**
	 * The class types of beans that should be checked. The condition matches when no bean
	 * of each class specified is contained in the {@link BeanFactory}.
	 * @return the class types of beans to check
	 */
	Class<?>[] value() default {};

	/**
	 * The class type names of beans that should be checked. The condition matches when no
	 * bean of each class specified is contained in the {@link BeanFactory}.
	 * @return the class type names of beans to check
	 */
	String[] type() default {};

	/**
	 * The class types of beans that should be ignored when identifying matching beans.
	 * @return the class types of beans to ignore
	 * @since 1.2.5
	 */
	Class<?>[] ignored() default {};

	/**
	 * The class type names of beans that should be ignored when identifying matching
	 * beans.
	 * @return the class type names of beans to ignore
	 * @since 1.2.5
	 */
	String[] ignoredType() default {};

	/**
	 * The annotation type decorating a bean that should be checked. The condition matches
	 * when each annotation specified is missing from all beans in the
	 * {@link BeanFactory}.
	 * @return the class-level annotation types to check
	 */
	Class<? extends Annotation>[] annotation() default {};

	/**
	 * The names of beans to check. The condition matches when each bean name specified is
	 * missing in the {@link BeanFactory}.
	 * @return the names of beans to check
	 */
	String[] name() default {};

	/**
	 * Strategy to decide if the application context hierarchy (parent contexts) should be
	 * considered.
	 * @return the search strategy
	 */
	SearchStrategy search() default SearchStrategy.ALL;

	/**
	 * Additional classes that may contain the specified bean types within their generic
	 * parameters. For example, an annotation declaring {@code value=Name.class} and
	 * {@code parameterizedContainer=NameRegistration.class} would detect both
	 * {@code Name} and {@code NameRegistration}.
	 * @return the container types
	 * @since 2.1.0
	 */
	Class<?>[] parameterizedContainer() default {};

}

ConditionalOnMissingClass

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

	/**
	 * The names of the classes that must not be present.
	 * @return the names of the classes that must not be present
	 */
	String[] value() default {};

}

ConditionalOnNotWebApplication

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

}

ConditionalOnProperty

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

	String[] value() default {};
	
	String prefix() default "";
	
	String[] name() default {};

	String havingValue() default "";

	boolean matchIfMissing() default false;

}

value和name互为别名,prefix为前缀。

案例:havingValue和matchIfMissing

@Configuration(proxyBeanMethods = false)
public class ConditionalOnPropertyConfig {

    private static final Logger logger = LoggerFactory.getLogger(ConditionalOnPropertyConfig.class);

    @ConditionalOnProperty(prefix = "str", value = "enable1",
            havingValue = "true", matchIfMissing = true)
    @Bean
    public String str1() {
        logger.info("str1 create success");
        return "str1";
    }

    @ConditionalOnProperty(prefix = "str", value = "enable2",
            havingValue = "true", matchIfMissing = false)
    @Bean
    public String str2() {
        logger.info("str2 create success");
        return "str2";
    }

    @ConditionalOnProperty(prefix = "str", value = "enable3",
            matchIfMissing = true)
    @Bean
    public String str3() {
        logger.info("str3 create success");
        return "str3";
    }

    @ConditionalOnProperty(prefix = "str", value = "enable4",
            matchIfMissing = false)
    @Bean
    public String str4() {
        logger.info("str4 create success");
        return "str4";
    }
}

yml不做配置,输出如下

ConditionalOnPropertyConfig      : str1 create success
ConditionalOnPropertyConfig      : str3 create success

也就是 matchIfMissing = true 的时候,没有配置文件一样会注册bean。

yml新增如下代码

str:
  enable1: truee
  enable2: truee
  enable3: truee
  enable4: truee

输出

str3 create success
str4 create success

当prefix+value能够在yml中找到的啥时候,会注册havingValue没有指定值的情况。

将truee改为true,四个bean都可以注册成功。改为false则一个都不会注册。

在注解 If not specified, the property must not be equal to false.有这么一段话,如果没有指定,这个属性不能等于false

ConditionalOnProperty 总结

ConditionalOnProperty 代表从配置文件中读取条件,value跟name互为别名选一个即可,prefix是前缀。
matchIfMissing如果为true代表配置文件中即使未匹配到key,该注解返回true。
havingValue不指定,当value不为false的其他情况条件注解返回true,指定则必须严格匹配value值才会返回true。

ConditionalOnResource

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

	/**
	 * The resources that must be present.
	 * @return the resource paths that must be present.
	 */
	String[] resources() default {};

}

ConditionalOnSingleCandidate

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

	/**
	 * The class type of bean that should be checked. The condition matches if a bean of
	 * the class specified is contained in the {@link BeanFactory} and a primary candidate
	 * exists in case of multiple instances.
	 * 

* This attribute may not be used in conjunction with * {@link #type()}, but it may be used instead of {@link #type()}. * @return the class type of the bean to check */ Class<?> value() default Object.class; /** * The class type name of bean that should be checked. The condition matches if a bean * of the class specified is contained in the {@link BeanFactory} and a primary * candidate exists in case of multiple instances. *

* This attribute may not be used in conjunction with * {@link #value()}, but it may be used instead of {@link #value()}. * @return the class type name of the bean to check */ String type() default ""; /** * Strategy to decide if the application context hierarchy (parent contexts) should be * considered. * @return the search strategy */ SearchStrategy search() default SearchStrategy.ALL; }

ConditionalOnWarDeployment

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

}

ConditionalOnWebApplication

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

	/**
	 * The required type of the web application.
	 * @return the required web application type
	 */
	Type type() default Type.ANY;

	/**
	 * Available application types.
	 */
	enum Type {

		/**
		 * Any web application will match.
		 */
		ANY,

		/**
		 * Only servlet-based web application will match.
		 */
		SERVLET,

		/**
		 * Only reactive-based web application will match.
		 */
		REACTIVE

	}

}

你可能感兴趣的:(Spring和分布式全家桶,spring,java,spring,boot)