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

时间:2022-09-11 12:46:09

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