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 } }