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

    }

}
时间: 2024-08-26 23:44:57

Spring Boot AutoConfiguration注解@ConditionalXXXX之前生今世的相关文章

How Spring Boot Autoconfiguration Magic Works--转

原文地址:https://dzone.com/articles/how-springboot-autoconfiguration-magic-works In my previous post "Why Spring Boot?", we looked at how to create a Spring Boot application, but you may or may not understand what is going on behind the scenes. You

spring boot 之注解

@EnableAutoConfiguration有这个注解,spring boot 就会根据classpat中的jar依赖,来猜测你将要开发一个什么样的spring应用.比如:spring-boot-starter-web这个依赖,则springboot就会自动嵌入tomcat和springMVC的相关依赖,并在启动的时候,启动为一个web应用.并会查找相应的配置参数,如果没有,则使用默认值.如:server.port=8080: @Component它是一个类级注解.当应用是通过注解来配置或x

How Spring Boot Autoconfiguration Magic Works

In my previous post "Why Spring Boot?", we looked at how to create a Spring Boot application, but you may or may not understand what is going on behind the scenes. You may want to understand the magic behind Spring Boot’s AutoConfiguration. Befo

spring boot 的注解

(1)@SpringBootApplication 申明让spring boot自动给程序进行必要的配置,这个配置等同于: @Configuration ,@EnableAutoConfiguration 和 @ComponentScan 三个配置. 示例代码: package com.kfit; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.Spr

spring boot常用注解使用小结

1.@RestController和@RequestMapping注解 4.0重要的一个新的改进是@RestController注解,它继承自@Controller注解. 4.0之前的版本,Spring MVC的组件都使用@Controller来标识当前类是一个控制器servlet.使用这个特性,我们可以开发REST服务的时候不需要使用@Controller,而专门的@RestController. 当你实现一个RESTful web services的时候,response将一直通过respo

Spring Boot @Condition 注解,组合条件你知道吗

上一篇文章 你应该知道的 @ConfigurationProperties 注解的使用姿势,这一篇就够了 介绍了如何通过 @ConfigurationProperties 注解灵活读取配置属性,这篇文章将介绍如何灵活配置 Spring Bean 写在前面 当我们构建一个 Spring 应用的时候,有时我们想在满足指定条件的时候才将某个 bean 加载到应用上下文中, 在Spring 4.0 时代,我们可以通过 @Conditional 注解来实现这类操作 我们看到 @Conditional 注解

【转】Spring Boot @Condition 注解,组合条件你知道吗

原文:https://www.cnblogs.com/FraserYu/p/11280420.html 上一篇文章 你应该知道的 @ConfigurationProperties 注解的使用姿势,这一篇就够了 介绍了如何通过 @ConfigurationProperties 注解灵活读取配置属性,这篇文章将介绍如何灵活配置 Spring Bean 写在前面 当我们构建一个 Spring 应用的时候,有时我们想在满足指定条件的时候才将某个 bean 加载到应用上下文中, 在Spring 4.0 时

Spring Boot MyBatis注解:@MapperScan和@Mapper

最近参与公司的新项目架构搭建,在使用mybatis的注解时,和同时有了不同意见,同事认为使用@Mapper注解简单明了,而我建议使用@MapperScan,直接将mapper所在的目录扫描进去就行,而且@Mapper需要在每一个mapper上都添加,繁琐.同事又说--我们可以用逆向工程自动生产entity,mapper,service时,将注解加上,很方便的,于是各执一词. 下面是我整理的这两种方法的比较: 使用@Mapper注解 为了让DemoMapper能够让别的类进行引用,我们可以在Dem

Spring Boot Annotations 注解

1.概述 Spring Boot通过其自动配置功能使Spring更容易配置. 在本教程中,我们将探讨org.springframework.boot.autoconfigure和org.springframework.boot.autoconfigure.condition包中的注释. 2. @SpringBootApplication 我们使用此批注来标记Spring Boot应用程序的主类: @SpringBootApplication封装@Configuration,@EnableAuto