zoukankan      html  css  js  c++  java
  • 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
    
        }
    
    }
  • 相关阅读:
    达梦数据库学习(二、管理数据库实例)
    达梦数据库学习(一、linux操作系统安装及数据库安装)
    SQL Server 数据库还原进度查看
    关于索引的学习(主要是聚集索引与非聚集索引)
    SQL Server批量向表中插入多行数据语句
    最大流-前置push-relabel算法实现
    调度算法(二)
    调度算法(一)
    软件工程:提问回顾
    软件工程:个人阅读作业与总结
  • 原文地址:https://www.cnblogs.com/davidwang456/p/6604112.html
Copyright © 2011-2022 走看看