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

Posted 一天不进步,就是退步!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 = 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="true"}</th>
 * <th>{@code havingValue="false"}</th>
 * <th>{@code havingValue="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之前生今世的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 学习例子

Spring Boot 2Spring Boot CLI

为啥 Spring Boot 应用程序 pom 同时需要 spring-boot-starter-parent 和 spring-boot-starter-web?

《02.Spring Boot连载:Spring Boot实战.Spring Boot核心原理剖析》

spring-boot-quartz, 依赖spring-boot-parent

spring-boot系列:初试spring-boot