Spring源码解析-@Conditional注解

Spring死磕系列-@Conditional注解

一、Conditional注解介绍

Conditional注解表示仅当所有指定条件都匹配时,该组件才会被注册。

二、Conditional注解三种使用方式

  • 作为类级别的注解使用:作用于任何直接或间接被@Component注解的类上,除此之外还包括@Configuration注解的类
  • 作为方法级别的注解使用:作用于任何被@Bean注解的方法上
  • 作为元注解使用:目的是组成自定义注释

如果一个@Configuration类也被@Conditional标记,则该配置类中@Bean方法、@Import以及@ComponentScan都会受到该条件的限制

三、Conditional注解的定义

3.1、Conditioanl注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
	/**该组件必须满足所有的匹配条件(Condition)才能被注册*/
	Class<? extends Condition>[] value();
}

该注解从spring4.0开始提供,一般作用于类或@bean方法上,其中value属性是Condition[]类型,表示所有Condition同时满足时该类或者方法才会生效。下面我们看看Condition的定义。

3.2、Condition接口

@FunctionalInterface
public interface Condition {
	/**判断是否满足条件*/
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

Condition是一个函数式接口,里面提供了一个抽象方法,返回值为boolean类型且有两个入参,分别是ConditionContextAnnotatedTypeMetadata。该接口作为判断条件是否满足的顶级抽象,那判断所需的信息就应该由这两个入参提供,那从这两个类能获取到哪些信息呢?

3.2.1、ConditionContext接口
/**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加载并检查类是否存在
3.2.2、AnnotatedTypeMetadata接口
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,我们可以获取到如下信息:

  • getAnnotations() 方法可以根据基础元素的直接注释返回注释详细信息
  • isAnnotated(String annotationName) 方法确定基础元素是否具有已定义的给定类型的注释或元注释
  • getAnnotationAttributes(String annotationName) 方法检索给定类型的注释的属性(如果有)(即,如果在基础元素上定义为直接注释或元注释),则还应考虑组合注释上的属性覆盖。
  • getAnnotationAttributes(String annotationName,boolean classValuesAsString)方法检索给定类型的注释的属性(如果有)(即,如果在基础元素上定义为直接注释或元注释),则还应考虑组合注释上的属性覆盖。
  • getAllAnnotationAttributes(String annotationName)方法检索给定类型的所有注释的所有属性(如果有)(即,如果在基础元素上定义为直接注释或元注释)。请注意,这个变体并没有采取属性覆盖进去。
  • getAllAnnotationAttributes(String annotationName, boolean classValuesAsString)方法检索给定类型的所有注释的所有属性(如果有)(即,如果在基础元素上定义为直接注释或元注释)。请注意,这个变体并没有采取属性覆盖进去。

四、Conditioanl在Spring中的机制

4.1、跟踪代码

通过上面的分析,我们知道Conditional注解其实是通过对应的Condition来处理的,下面我们来探究一下该注解在Spring框架中是如何使用的。

image-20200920144337696

通过对上面代码的反响跟踪,你会发现最后会跟踪到ConditionEvaluator类,它里面调用了Conditionmatches(ConditionContext context, AnnotatedTypeMetadata metadata)方法。

4.2、ConditionEvaluator类

该类是Spring的内部类,用于判断是否需要跳过

ConditionEvaluator类成员方法

该类对外暴露了3个方法,一个构造,两个重载的shouldSkip方法,由此可知该类的核心就是这两个重载方法的实现,那我们就来看看这两个方法具体实现。

4.2.1、核心方法分析
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步:

  1. 是否存在Conditional注解的标记,没有,返回false,否则接着往下走。
  2. 如果ConfigurationPhase为空,先根据一定条件设置``ConfigurationPhase`,然后递归调用。
  3. 走到这,说明存在Conditioanl注解标记且ConfigurationPhase不为空,然后收集所有Conditional的value值(Condition实现类的全限定类名),最后通过反射创建这些Condition
  4. Condition列表进行排序(每个Condition类上基本都有@Order指定的一个排序序号)
  5. 遍历集合,判断是否满足所有Condition中的条件,存在不满足的条件,则返回true,表示该bean将被忽略。
4.2.2、ConditionEvaluator功能示意图

五、Condition整体结构图

image-20200920231933061

上面的整体结构图可以按功能大致划分:

  • Condition:所有条件类的根接口,由spring-context包提供,主要功能是满足匹配的条件该bean才能被注册。
  • ConfigurationCondition:继承Condition接口,由spring-context包提供,扩展了更细颗粒度的控制@Configuration,提供了配置阶段(ConfigurationPhase)的支持。
  • SpringBootCondition:实现了Condition接口,是SpringBoot的Condition的基类,扩展了日志记录功能,可以帮助用户诊断加载了哪些类。(Spring Boot Actuator项目中有使用到该功能
  • FilteringSpringBootCondition:继承SpringBootCondition并实现了AutoConfigurationImportFilter接口,使其同时具有对普通bean(@Bean、@Configuration、@Component及@Component派生的注解标记的bean)和自动配置类(定义在META-INF/spring.factories中的bean)的过滤能力。

六、Springboot对Conditional注解的扩展

从上一节Condition整体的类层次结构图可以看出,Springboot对Conditional进行了扩展,一般这些注解作用于配置类(@Configuration标记的类)和单个@Bean的方法中。

6.1、SpringBoot中Conditional的分类

下面将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实例

6.2、SpringBoot对Conditional扩展源码分析

6.2.1、ConditionalOnProperty注解
@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));
	}
}

image-20200930214921863

6.2.2、ConditionalOnClass注解
ConditionalOnClass注解定义
@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
	 * <b>not</b> 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两个维度去判断类是否存在

OnClassCondition类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oEbG9mLH-1601474572175)(/home/keminapera/.config/Typora/typora-user-images/image-20200920171752493.png)]

上图是OnClassCondition类的层次结构图,本文我们主要关注左侧类的层次结构。Aware在这就不再展开讨论。

  • Condition接口:
  • AutoConfigurationImportFilter接口:过滤定义在spring.factories自动配置类,这个接口用于在读取它们字节码之前移除这些类。
  • SpringBootCondition抽象类:SpringBoot项目提供的对Condition的基本实现,是所有SpringBoot相关的Condition基类。它提供了比较智能的日志记录功能,能帮助用户诊断加载了哪些类。
  • FilteringSpringBootCondition抽象类:
  • OnClassCondition具体实现类:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v3RrFHmN-1601474572175)(/home/keminapera/.config/Typora/typora-user-images/image-20200920172021678.png)]

6.3、SpringBoot中Conditional系列注解 的使用

在SpringBoot中,它的“starter”机制大量使用了Conditional注解,下面我们就列举几个SpringBoot源码中是如何使用这些注解的。在spring-boot-autoconfigure的META-INF/spring.factories中定义了大量的自动配置类,SpringBoot使开发人员从繁琐的配置中解放出来的关键就在于此,这里就不再展开讨论。现在思考一个问题,springboot项目是会创建所有定义在META-INF/spring.factories的自动配置类吗???

6.3.1、AopAutoConfiguration类
@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);
			}
		}
	}
}
6.3.2、RedisAutoConfiguration类
@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;
	}

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot中,@Conditional注解和@AutoConfigureAfter注解是非常常用的注解,下面我来给你详细解析一下这两个注解。 ## @Conditional注解 @Conditional注解Spring Boot中非常重要的一个注解,在Spring Boot中,很多自动配置都是通过@Conditional注解来实现的。 @Conditional注解可以根据满足某些条件来决定是否创建一个bean。比如,我们可以根据某个类是否存在来决定是否创建一个bean,具体示例如下: ```java @Configuration @Conditional(ExistClassCondition.class) public class MyConfiguration { @Bean public MyBean myBean() { return new MyBean(); } } public class ExistClassCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { try { Class.forName("com.example.MyClass"); return true; } catch (ClassNotFoundException e) { return false; } } } ``` 上面的代码中,我们定义了一个MyConfiguration类,并且在该类上加了@Conditional注解,该注解的参数是一个Condition的实现类ExistClassCondition。ExistClassCondition类中的matches方法返回true的条件是com.example.MyClass类存在。 这样,当com.example.MyClass类存在的时候,MyBean这个bean才会被创建。否则,MyBean这个bean不会被创建。 ## @AutoConfigureAfter注解 @AutoConfigureAfter注解也是Spring Boot中比较常用的注解之一,它可以用来控制自动配置的顺序。 比如,我们可以通过@AutoConfigureAfter注解来控制某个自动配置类在另一个自动配置类之后加载,具体示例如下: ```java @Configuration @AutoConfigureAfter(MyAutoConfiguration.class) public class MyAnotherAutoConfiguration { // ... } ``` 上面的代码中,我们定义了一个MyAnotherAutoConfiguration类,并且在该类上加了@AutoConfigureAfter注解,该注解的参数是MyAutoConfiguration.class。这样,在Spring Boot启动时,MyAutoConfiguration这个自动配置类会先于MyAnotherAutoConfiguration这个自动配置类被加载。 总结:@Conditional注解和@AutoConfigureAfter注解都是Spring Boot中非常实用的注解。通过@Conditional注解可以实现根据满足某些条件来决定是否创建一个bean,通过@AutoConfigureAfter注解可以控制自动配置类的加载顺序,这些都是我们在实际开发中非常常用的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值