首页 > 代码库 > Spring Boot AutoConfiguration注解@ConditionalXXXX之前生今世

Spring Boot AutoConfiguration注解@ConditionalXXXX之前生今世

1.注解@Conditional的定义

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})public @interface Conditional {    /**     * All {@link Condition}s that must {@linkplain Condition#matches match}     * in order for the component to be registered.     */    Class<? extends Condition>[] value();}

注解@Conditional标识一个组件时,该组件只有全面满足value()指定的所有条件时才可以注册到容器中。

注解@Conditional的使用场景如下:

  . 作为类型级别的注解,作用在一个直接或者间接@Component注解(包括@Configuration作为元注解的类)的类上,目标是组成自定义的steretype注解。

  . 作为方法级别的注解,作用在任意的@Bean 方法上

如果一个标注了@Configuration的类,也标注了@Conditional,所有的@Bean方法,@Import和@ComponentScan注解关联的类将也满足这些Conditions。

注意,@Conditional注解不能继承,从父类或者重写方法的condition是不起作用的。

其中,一个Condition是要注册的Bean定义之前可以编程决定的状态。详细信息如下:

2 前生 Condition定义

public interface Condition {    /**     * Determine if the condition matches.     * @param context the condition context     * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}     * or {@link org.springframework.core.type.MethodMetadata method} being checked.     * @return {@code true} if the condition matches and the component can be registered     * or {@code false} to veto registration.     */    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);}

一个单独的condition是一个组件为注册为bean时必须满足matches()方法。

Conditions在要注册的组件变成bean definition之前必须检查立即检查所有的matches方法。

Condition也必须和BeanFactoryPostProcessor一样满足同样的限制条件。更细粒度的控制可以考虑使用ConfigurationCondition。

3.后世

spring-boot-autoconfigure condition相关的类如下:

3.1 ConditionalOnBean定义

@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(OnBeanCondition.class)public @interface ConditionalOnBean {    /**     * The class type of bean that should be checked. The condition matches when all of     * the classes specified are contained in the {@link ApplicationContext}.     * @return the class types of beans to check     */    Class<?>[] value() default {};    /**     * The class type names of bean that should be checked. The condition matches when all     * of the classes specified are contained in the {@link ApplicationContext}.     * @return the class type names of beans to check     */    String[] type() default {};    /**     * The annotation type decorating a bean that should be checked. The condition matches     * when all of the annotations specified are defined on beans in the     * {@link ApplicationContext}.     * @return the class-level annotation types to check     */    Class<? extends Annotation>[] annotation() default {};    /**     * The names of beans to check. The condition matches when all of the bean names     * specified are contained in the {@link ApplicationContext}.     * @return the name 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;}

ConditionalOnBean作用:当指定bean的类名或者名称已经在BeanFactory中存在时才算满足条件。

其实现类为OnBeanCondition,检查指定的bean是存在还是不存在。

@Order(Ordered.LOWEST_PRECEDENCE)class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {    /**     * Bean definition attribute name for factory beans to signal their product type (if     * known and it can‘t be deduced from the factory bean class).     */    public static final String FACTORY_BEAN_OBJECT_TYPE = BeanTypeRegistry.FACTORY_BEAN_OBJECT_TYPE;    @Override    public ConfigurationPhase getConfigurationPhase() {        return ConfigurationPhase.REGISTER_BEAN;    }    @Override    public ConditionOutcome getMatchOutcome(ConditionContext context,            AnnotatedTypeMetadata metadata) {        ConditionMessage matchMessage = ConditionMessage.empty();        if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {            BeanSearchSpec spec = new BeanSearchSpec(context, metadata,                    ConditionalOnBean.class);            MatchResult matchResult = getMatchingBeans(context, spec);            if (!matchResult.isAllMatched()) {                String reason = createOnBeanNoMatchReason(matchResult);                return ConditionOutcome.noMatch(ConditionMessage                        .forCondition(ConditionalOnBean.class, spec).because(reason));            }            matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)                    .found("bean", "beans")                    .items(Style.QUOTE, matchResult.getNamesOfAllMatches());        }        if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {            BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,                    ConditionalOnSingleCandidate.class);            MatchResult matchResult = getMatchingBeans(context, spec);            if (!matchResult.isAllMatched()) {                return ConditionOutcome.noMatch(ConditionMessage                        .forCondition(ConditionalOnSingleCandidate.class, spec)                        .didNotFind("any beans").atAll());            }            else if (!hasSingleAutowireCandidate(context.getBeanFactory(),                    matchResult.getNamesOfAllMatches(),                    spec.getStrategy() == SearchStrategy.ALL)) {                return ConditionOutcome.noMatch(ConditionMessage                        .forCondition(ConditionalOnSingleCandidate.class, spec)                        .didNotFind("a primary bean from beans")                        .items(Style.QUOTE, matchResult.getNamesOfAllMatches()));            }            matchMessage = matchMessage                    .andCondition(ConditionalOnSingleCandidate.class, spec)                    .found("a primary bean from beans")                    .items(Style.QUOTE, matchResult.namesOfAllMatches);        }        if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {            BeanSearchSpec spec = new BeanSearchSpec(context, metadata,                    ConditionalOnMissingBean.class);            MatchResult matchResult = getMatchingBeans(context, spec);            if (matchResult.isAnyMatched()) {                String reason = createOnMissingBeanNoMatchReason(matchResult);                return ConditionOutcome.noMatch(ConditionMessage                        .forCondition(ConditionalOnMissingBean.class, spec)                        .because(reason));            }            matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)                    .didNotFind("any beans").atAll();        }        return ConditionOutcome.match(matchMessage);    }}

3.2 ConditionalOnClass

当指定的类在classpath下认定满足条件,实现类为:OnClassCondition。

定义如下:

@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(OnClassCondition.class)public @interface ConditionalOnClass {    /**     * The classes that must be present. Since this annotation parsed by loading class     * bytecode it is safe to specify classes here that may ultimately not be on the     * classpath.     * @return the classes that must be present     */    Class<?>[] value() default {};    /**     * The classes names that must be present.     * @return the class names that must be present.     */    String[] name() default {};}

3.3 ConditionalOnCloudPlatform

指定的云平台激活时满足条件。实现类为:OnCloudPlatformCondition

@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();}

3.4 ConditionalOnExpression

/** * Configuration annotation for a conditional element that depends on the value of a SpEL * expression. * * @author Dave Syer */@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";}

3.5 ConditionalOnJava

/** * {@link Conditional} that matches based on the JVM version the application is running * on. * * @author Oliver Gierke * @author Phillip Webb * @author Andy Wilkinson * @since 1.1.0 */@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    }    /**     * Java versions.     */    enum JavaVersion {        /**         * Java 1.9.         */        NINE(9, "1.9", "java.security.cert.URICertStoreParameters"),        /**         * Java 1.8.         */        EIGHT(8, "1.8", "java.util.function.Function");        private final int value;        private final String name;        private final boolean available;        JavaVersion(int value, String name, String className) {            this.value =http://www.mamicode.com/ value;            this.name = name;            this.available = ClassUtils.isPresent(className, getClass().getClassLoader());        }        /**         * Determines if this version is within the specified range of versions.         * @param range the range         * @param version the bounds of the range         * @return if this version is within the specified range         */        public boolean isWithin(Range range, JavaVersion version) {            Assert.notNull(range, "Range must not be null");            Assert.notNull(version, "Version must not be null");            switch (range) {            case EQUAL_OR_NEWER:                return this.value >= version.value;            case OLDER_THAN:                return this.value < version.value;            }            throw new IllegalStateException("Unknown range " + range);        }        @Override        public String toString() {            return this.name;        }        /**         * Returns the {@link JavaVersion} of the current runtime.         * @return the {@link JavaVersion}         */        public static JavaVersion getJavaVersion() {            for (JavaVersion candidate : JavaVersion.values()) {                if (candidate.available) {                    return candidate;                }            }            return EIGHT;        }    }}

3.6 ConditionalOnJndi

/** * {@link Conditional} that matches based on the availability of a JNDI * {@link InitialContext} and the ability to lookup specific locations. * * @author Phillip Webb * @since 1.2.0 */@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 {};}

3.7 ConditionalOnMissingBean

/** * {@link Conditional} that only matches when the specified bean classes and/or names are * not already contained in the {@link BeanFactory}. * <p> * The condition can only match the bean definitions that have been processed by the * application context so far and, as such, it is strongly recommended to use this * condition on auto-configuration classes only. If a candidate bean may be created by * another auto-configuration, make sure that the one using this condition runs after. * * @author Phillip Webb * @author Andy Wilkinson */@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(OnBeanCondition.class)public @interface ConditionalOnMissingBean {    /**     * The class type of bean that should be checked. The condition matches when each     * class specified is missing in the {@link ApplicationContext}.     * @return the class types of beans to check     */    Class<?>[] value() default {};    /**     * The class type names of bean that should be checked. The condition matches when     * each class specified is missing in the {@link ApplicationContext}.     * @return the class type names of beans to check     */    String[] type() default {};    /**     * The class type 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 ApplicationContext}.     * @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 ApplicationContext}.     * @return the name 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;}

3.8 ConditionalOnMissingClass

/** * {@link Conditional} that only matches when the specified classes are not on the * classpath. * * @author Dave Syer */@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 {};}

3.9 ConditionalOnNotWebApplication

/** * {@link Conditional} that only matches when the application context is a not a web * application context. * * @author Dave Syer */@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(OnWebApplicationCondition.class)public @interface ConditionalOnNotWebApplication {}

3.10 ConditionalOnProperty

/** * {@link Conditional} that checks if the specified properties have a specific value. By * default the properties must be present in the {@link Environment} and * <strong>not</strong> equal to {@code false}. The {@link #havingValue()} and * {@link #matchIfMissing()} attributes allow further customizations. * * <p> * The {@link #havingValue} attribute can be used to specify the value that the property * should have. The table below shows when a condition matches according to the property * value and the {@link #havingValue()} attribute: * * <table summary="having values" border="1"> * <tr> * <th>Property Value</th> * <th>{@code havingValue=""}</th> * <th>{@code havingValue="http://www.mamicode.com/true"}</th> * <th>{@code havingValue="http://www.mamicode.com/false"}</th> * <th>{@code havingValue="http://www.mamicode.com/foo"}</th> * </tr> * <tr> * <td>{@code "true"}</td> * <td>yes</td> * <td>yes</td> * <td>no</td> * <td>no</td> * </tr> * <tr> * <td>{@code "false"}</td> * <td>no</td> * <td>no</td> * <td>yes</td> * <td>no</td> * </tr> * <tr> * <td>{@code "foo"}</td> * <td>yes</td> * <td>no</td> * <td>no</td> * <td>yes</td> * </tr> * </table> * * <p> * If the property is not contained in the {@link Environment} at all, the * {@link #matchIfMissing()} attribute is consulted. By default missing attributes do not * match. * * @author Maciej Walkowiak * @author Stephane Nicoll * @author Phillip Webb * @since 1.1.0 */@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.TYPE, ElementType.METHOD })@Documented@Conditional(OnPropertyCondition.class)public @interface ConditionalOnProperty {    /**     * Alias for {@link #name()}.     * @return the names     */    String[] value() default {};    /**     * A prefix that should be applied to each property. The prefix automatically ends     * with a dot if not specified.     * @return the prefix     */    String prefix() default "";    /**     * The name of the properties to test. If a prefix has been defined, it is applied to     * compute the full key of each property. For instance if the prefix is     * {@code app.config} and one value is {@code my-value}, the fully key would be     * {@code app.config.my-value}     * <p>     * Use the dashed notation to specify each property, that is all lower case with a "-"     * to separate words (e.g. {@code my-long-property}).     * @return the names     */    String[] name() default {};    /**     * The string representation of the expected value for the properties. If not     * specified, the property must <strong>not</strong> be equals to {@code false}.     * @return the expected value     */    String havingValue() default "";    /**     * Specify if the condition should match if the property is not set. Defaults to     * {@code false}.     * @return if should match if the property is missing     */    boolean matchIfMissing() default false;    /**     * If relaxed names should be checked. Defaults to {@code true}.     * @return if relaxed names are used     */    boolean relaxedNames() default true;}

3.11 ConditionalOnResource

/** * {@link Conditional} that only matches when the specified resources are on the * classpath. * * @author Dave Syer */@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 {};}

3.12 ConditionalOnSingleCandidate

/** * {@link Conditional} that only matches when the specified bean class is already * contained in the {@link BeanFactory} and a single candidate can be determined. * <p> * The condition will also match if multiple matching bean instances are already contained * in the {@link BeanFactory} but a primary candidate has been defined; essentially, the * condition match if auto-wiring a bean with the defined type will succeed. * <p> * The condition can only match the bean definitions that have been processed by the * application context so far and, as such, it is strongly recommended to use this * condition on auto-configuration classes only. If a candidate bean may be created by * another auto-configuration, make sure that the one using this condition runs after. * * @author Stephane Nicoll * @since 1.3.0 */@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 match if the class     * specified is contained in the {@link ApplicationContext} and a primary candidate     * exists in case of multiple instances.     * <p>     * This attribute may <strong>not</strong> 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 the     * class specified is contained in the {@link ApplicationContext} and a primary     * candidate exists in case of multiple instances.     * <p>     * This attribute may <strong>not</strong> 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;}

3.13 ConditionalOnWebApplication

/** * {@link Conditional} that matches when the application is a web application. By default, * any web application will match but it can be narrowed using the {@link #type()} * attribute. * * @author Dave Syer * @author Stephane Nicoll */@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 Boot AutoConfiguration注解@ConditionalXXXX之前生今世