Conditional注解表示仅当所有指定条件都匹配时,该组件才会被注册。
如果一个@Configuration类也被@Conditional标记,则该配置类中@Bean方法、@Import以及@ComponentScan都会受到该条件的限制
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**该组件必须满足所有的匹配条件(Condition)才能被注册*/
Class<? extends Condition>[] value();
}
该注解从spring4.0开始提供,一般作用于类或@bean
方法上,其中value属性是Condition[]
类型,表示所有Condition
同时满足时该类或者方法才会生效。下面我们看看Condition的定义。
@FunctionalInterface
public interface Condition {
/**判断是否满足条件*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
Condition
是一个函数式接口,里面提供了一个抽象方法,返回值为boolean类型且有两个入参,分别是ConditionContext
和AnnotatedTypeMetadata
。该接口作为判断条件是否满足的顶级抽象,那判断所需的信息就应该由这两个入参提供,那从这两个类能获取到哪些信息呢?
/**Condition类使用的一些上下文信息*/
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getClassLoader();
}
通过ConditionContext
,我们可以获取到如下信息:
getRegistry()
返回的BeanDefinitionRegistry
可以检查bean定义getBeanFactory()
返回的ConfigurableListableBeanFactory
检查bean是否存在,甚至探查bean的属性getEnvironment()
返回的Environment
检查环境变量是否存在以及它的值是什么getResourceLoader()
返回的ResourceLoader
所加载的资源getClassLoader()
返回的ClassLoader
加载并检查类是否存在public interface AnnotatedTypeMetadata {
/**@since 5.2*/
MergedAnnotations getAnnotations();
default boolean isAnnotated(String annotationName) {
return getAnnotations().isPresent(annotationName);
}
@Nullable
default Map<String, Object> getAnnotationAttributes(String annotationName) {
return getAnnotationAttributes(annotationName, false);
}
@Nullable
default Map<String, Object> getAnnotationAttributes(String annotationName,
boolean classValuesAsString) {
MergedAnnotation<Annotation> annotation = getAnnotations().get(annotationName,
null, MergedAnnotationSelectors.firstDirectlyDeclared());
if (!annotation.isPresent()) {
return null;
}
return annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, true));
}
@Nullable
default MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName) {
return getAllAnnotationAttributes(annotationName, false);
}
@Nullable
default MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationName, boolean classValuesAsString) {
Adapt[] adaptations = Adapt.values(classValuesAsString, true);
return getAnnotations().stream(annotationName)
.filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes))
.map(MergedAnnotation::withNonMergedAttributes)
.collect(MergedAnnotationCollectors.toMultiValueMap(map ->
map.isEmpty() ? null : map, adaptations));
}
}
通过AnnotatedTypeMetadata
,我们可以获取到如下信息:
通过上面的分析,我们知道Conditional
注解其实是通过对应的Condition
来处理的,下面我们来探究一下该注解在Spring框架中是如何使用的。
通过对上面代码的反响跟踪,你会发现最后会跟踪到ConditionEvaluator
类,它里面调用了Condition
的matches(ConditionContext context, AnnotatedTypeMetadata metadata)
方法。
该类是Spring的内部类,用于判断是否需要跳过
该类对外暴露了3个方法,一个构造,两个重载的shouldSkip
方法,由此可知该类的核心就是这两个重载方法的实现,那我们就来看看这两个方法具体实现。
public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
return shouldSkip(metadata, null);
}
该方法调用了它的重载方法shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase)
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
//注解信息中是否存在Conditional注解
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 推断ConfigurationPhase
// 第一次调用该方法时,如果ConfigurationPhase为null,则会走这部分代码推导ConfigurationPhase
// 递归调用本方法,当再次进来时ConfigurationPhase已经不为null,会跳过这部分代码,走下面的处理逻辑
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
// 递归调用本方法
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
// 递归调用本方法
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
//获取所有待判断的条件
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
//通过反射创建Condition对象并添加到条件集合中
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 对所有待判断的条件进行排序
AnnotationAwareOrderComparator.sort(conditions);
// 开始对所有条件进行评估
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
//ConfigurationCondition是对Condition的扩展,多了一个getConfigurationPhase()方法
//且ConfigurationCondition的每个实现类都有一个ConfigurationPhase属性,并且不为空
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
//有两种情况
//第一种:requiredPhase为空,判断条件是否满足
//第二种:requiredPhase不为空,必须requiredPhase == phase 且 判断是否满足条件
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
//不创建该bean
return true;
}
}
//创建该bean
return false;
}
该方法大致可以划分为5步:
Conditional
注解的标记,没有,返回false,否则接着往下走。ConfigurationPhase
为空,先根据一定条件设置``ConfigurationPhase`,然后递归调用。ConfigurationPhase
不为空,然后收集所有Conditional的value值(Condition实现类的全限定类名),最后通过反射创建这些Condition
Condition
列表进行排序(每个Condition类上基本都有@Order指定的一个排序序号)Condition
中的条件,存在不满足的条件,则返回true,表示该bean将被忽略。上面的整体结构图可以按功能大致划分:
spring-context
包提供,主要功能是满足匹配的条件该bean才能被注册。spring-context
包提供,扩展了更细颗粒度的控制@Configuration,提供了配置阶段(ConfigurationPhase)的支持。从上一节Condition整体的类层次结构图可以看出,Springboot对Conditional进行了扩展,一般这些注解作用于配置类(@Configuration标记的类)和单个@Bean的方法中。
下面将Springboot对Condition类的扩展进行大致梳理,具体每个注解的使用和注意事项请查看SpringBoot官网
分类 | 注解 | 功能介绍 |
---|---|---|
Class Conditions | @ConditionalOnClass | 当classpath中指定的Class存在时,才创建注册该注解所修饰的bean实例 |
@ConditionalOnMissingClass | 当classpath中指定的Class不存在时,才创建并注册该注解所修饰的bean实例 | |
Bean Conditions | @ConditionalOnBean | 当容器中指定name或Class的bean存在时,创建并注册该注解所修饰的bean实例(按名称或类型进行匹配) |
@ConditionalOnMissingBean | 当容器中指定name或Class的bean不存在时,创建并注册该注解所修饰的bean实例 | |
Property Conditions | @ConditionalOnProperty | 当配置文件中指定的配置项为指定的值时,创建并注册该注解所修饰的bean实例 |
Resource Conditions | @ConditionalOnResource | 当classpath中指定的资源文件存在时,创建并注册该注解所修饰的bean实例 |
Web Application Conditions | @ConditionalOnWebApplication | 当应用上下文是web环境时,创建并注册该注解所修饰的bean实例 |
@ConditionalOnNotWebApplication | 当应用上下文不是web环境时,创建并注册该注解所修饰的bean实例 | |
SpEL Expression Conditions | @ConditionalOnExpression | 当指定的SpEL表达式为true时,创建并注册该注解所修饰的bean实例 |
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class) //该注解对应的处理类为OnPropertyCondition
public @interface ConditionalOnProperty {
/**name的别名,指定配置的key*/
String[] value() default {};
/**配置的前缀*/
String prefix() default "";
/**对应配置的key*/
String[] name() default {};
/**对应配置(name)预期的值*/
String havingValue() default "";
/**如果没有设置该属性值,是否算作匹配*/
boolean matchIfMissing() default false;
}
@Order(Ordered.HIGHEST_PRECEDENCE + 40) //用于排序使用
class OnPropertyCondition extends SpringBootCondition {
//这里只粘贴了核心方法,其他方法请查看源代码
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.match(ConditionMessage.of(match));
}
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/**
* The classes that must be present. Since this annotation is parsed by loading class
* bytecode, it is safe to specify classes here that may ultimately not be on the
* classpath, only if this annotation is directly on the affected component and
* not if this annotation is used as a composed, meta-annotation. In order to
* use this annotation as a meta-annotation, only use the {@link #name} attribute.
* @return the classes that must be present
*/
Class<?>[] value() default {};
/**指定这些类名必须存在*/
String[] name() default {};
}
该注解可以通过类名称和Class两个维度去判断类是否存在
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oEbG9mLH-1601474572175)(/home/keminapera/.config/Typora/typora-user-images/image-20200920171752493.png)]
上图是OnClassCondition
类的层次结构图,本文我们主要关注左侧类的层次结构。Aware
在这就不再展开讨论。
spring.factories
自动配置类,这个接口用于在读取它们字节码之前移除这些类。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v3RrFHmN-1601474572175)(/home/keminapera/.config/Typora/typora-user-images/image-20200920172021678.png)]
在SpringBoot中,它的“starter”机制大量使用了Conditional
注解,下面我们就列举几个SpringBoot源码中是如何使用这些注解的。在spring-boot-autoconfigure的META-INF/spring.factories
中定义了大量的自动配置类,SpringBoot使开发人员从繁琐的配置中解放出来的关键就在于此,这里就不再展开讨论。现在思考一个问题,springboot项目是会创建所有定义在META-INF/spring.factories
的自动配置类吗???
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {
ClassProxyingConfiguration(BeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}