12、常见Conditional注解源码解析(ok)

参考资料:彻底搞定 Java 注解

Spring Boot中所有的@Conditional 注解如下:

Conditional (o.s.context.annotation)
 |-ConditionalOnBean (o.s.boot.autoconfigure.condition)
 |-ConditionalOnClass (o.s.boot.autoconfigure.condition)
 |-ConditionalOnCloudPlatform (o.s.boot.autoconfigure.condition)
 |-ConditionalOnEnabledResourceChain (o.s.boot.autoconfigure.web)
 |-ConditionalOnExpression (o.s.boot.autoconfigure.condition)
 |-ConditionalOnJava (o.s.boot.autoconfigure.condition)
 |-ConditionalOnJndi (o.s.boot.autoconfigure.condition)
 |-ConditionalOnMissingBean (o.s.boot.autoconfigure.condition)
 |-ConditionalOnMissingClass (o.s.boot.autoconfigure.condition)
 |-ConditionalOnNotWebApplication (o.s.boot.autoconfigure.condition)
 |-ConditionalOnProperty (o.s.boot.autoconfigure.condition)
 |-ConditionalOnResource (o.s.boot.autoconfigure.condition)
 |-ConditionalOnSingleCandidate (o.s.boot.autoconfigure.condition)
 |-ConditionalOnWebApplication (o.s.boot.autoconfigure.condition)
 |-Profile (o.s.context.annotation)

我们选最常用的ConditionalOnJava ConditionalOnClass ConditionalOnBean 3个来解析其源码 , 有了这几个基础后 , 对于剩余的大家可以自行查看源码 .

首先要明白的是,上面列出所有的条件注解都使用了 @Conditional 来标注,其value值是个Condition 类型的数组。

在 Spring Framework 的源码中,有个地方的代码会处理标注在Bean上的条件注解,并获取@Conditional 注解中的 Condition 接口的实现类,使用反射创建该实现类的实例并调用其matches方法。

我们画个图看看Spring Framework 处理条件注解的流程
在这里插入图片描述

从图中可以看出 , 这个注解的处理时在解析配置文件时来处理的 , 创建Condition接口实现类的实例使用的是反射 , 而不是使用Spring中的getBean来创建的 .

思考: 为什么不像创建ConfigurationClassPostProcessor对象bean一样使用getBean?

Condition是和配置项相关的 , 每个Condition 可能都不一样 , 不可复用, 也就没必要使用bean的方式创建对象从而放入容器中

1/@ConditionalOnJava

1.1/测试程序

1.2/注解定义

1.3/条件匹配类

2/ @ConditionalOnClass

1.1/测试程序

1.2/注解定义

1.3/条件匹配类

3/ @ConditionalOnBean

1.1/测试程序

1.2/注解定义

1.3/条件匹配类

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
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();

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

}

我翻看了几个注解的源码,发现 OnJavaCondition 比较简单,我们就从它先开始

1、OnJavaCondition

1.1、测试程序:

@Component
@FirstConditionAnnotation(values={"firstCondition1","firstCondition2"})
@ConditionalOnJava(range = ConditionalOnJava.Range.EQUAL_OR_NEWER
        ,value = ConditionalOnJava.JavaVersion.SEVEN)
public class FirstConditionBean {
}

@SpringBootApplication
public class HelloSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloSpringBootApplication.class,args);
    }
}

1.2、OnJavaCondition调用入口

我在OnJavaCondition#getMatchOutcome 方法上打上断点,启动程序调用栈如下:

getMatchOutcome:45, OnJavaCondition (o.s.boot.autoconfigure.condition)
matches:47, SpringBootCondition (o.s.boot.autoconfigure.condition)
shouldSkip:102, ConditionEvaluator (o.s.context.annotation)
shouldSkip:81, ConditionEvaluator (o.s.context.annotation)
shouldSkip:64, ConditionEvaluator (o.s.context.annotation)
isConditionMatch:371, ClassPathScanningCandidateComponentProvider (o.s.context.annotation)
isCandidateComponent:355, ClassPathScanningCandidateComponentProvider (o.s.context.annotation)
// 查找包下的所有的标注了Contitional注解的类,发现了FirstConditionBean标注了ConditionalOnJava
// 此时会实例化 ConditionalOnJava 注解的 value 值 OnJavaCondition,并调用其matches方法
findCandidateComponents:288, ClassPathScanningCandidateComponentProvider (o.s.context.annotation)
// 扫描HelloSpringBootApplication所在的包
doScan:272, ClassPathBeanDefinitionScanner (o.s.context.annotation)
parse:135, ComponentScanAnnotationParser (o.s.context.annotation)
// 处理 HelloSpringBootApplication的 @ComponentScan 注解
doProcessConfigurationClass:287, ConfigurationClassParser (o.s.context.annotation)
processConfigurationClass:245, ConfigurationClassParser (o.s.context.annotation)
parse:198, ConfigurationClassParser (o.s.context.annotation)
// 解析配置类 HelloSpringBootApplication  
parse:167, ConfigurationClassParser (o.s.context.annotation)
processConfigBeanDefinitions:308, ConfigurationClassPostProcessor (o.s.context.annotation)
postProcessBeanDefinitionRegistry:228, ConfigurationClassPostProcessor (o.s.context.annotation)
invokeBeanDefinitionRegistryPostProcessors:272, PostProcessorRegistrationDelegate (o.s.context.support)
invokeBeanFactoryPostProcessors:92, PostProcessorRegistrationDelegate (o.s.context.support)
invokeBeanFactoryPostProcessors:687, AbstractApplicationContext (o.s.context.support)
refresh:525, AbstractApplicationContext (o.s.context.support)
refresh:122, EmbeddedWebApplicationContext (o.s.boot.context.embedded)
refresh:693, SpringApplication (o.s.boot)
refreshContext:360, SpringApplication (o.s.boot)
run:303, SpringApplication (o.s.boot)
run:1118, SpringApplication (o.s.boot)
run:1107, SpringApplication (o.s.boot)
main:14, HelloSpringBootApplication (com.yh.stu.springboot)

调用过程在调用栈中已经注释了,这里总结一下:

// TODO

OnJavaCondition 源码解析

@Conditional(OnJavaCondition.class)
public @interface ConditionalOnJava {
	Range range() default Range.EQUAL_OR_NEWER;
	JavaVersion value();
	enum Range {
		/**
		 * Equal to, or newer than the specified {@link JavaVersion}.
		 */
		EQUAL_OR_NEWER,
		/**
		 * Older than the specified {@link JavaVersion}.
		 */
		OLDER_THAN
	}
	enum JavaVersion {
		/**
		 * Java 1.9.
		 */
		NINE(9, "1.9", "java.security.cert.URICertStoreParameters"),

		/**
		 * Java 1.8.
		 */
		EIGHT(8, "1.8", "java.util.function.Function"),
		......
		JavaVersion(int value, String name, String className) {
			this.value = value;
			this.name = name;
			// className指定了各个版本新增的class类,Class.forName(className)抛异常则说明不支持改版本
			this.available = ClassUtils.isPresent(className, getClass().getClassLoader());
		}
		// 返回当前环境支持的最高版本
		public static JavaVersion getJavaVersion() {
			for (JavaVersion candidate : JavaVersion.values()) {
				// 选取支持的最高版本
				if (candidate.available) {
					return candidate;
				}
			}
			return SIX;
		}
	}
	......
}

OnJavaCondition

OnJavaCondition的类图:
在这里插入图片描述

我们在 OnJavaCondition 中没有发现 matches 方法,所以我们看看其父类

/************** org.springframework.boot.autoconfigure.condition.SpringBootCondition **************/
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
	// 这里的metadata是标注注解的类的元数据
	// 获取被注解类的class名称或者类名+方法名称
	// 本例中的值: com...FirstConditionBean
	String classOrMethodName = getClassOrMethodName(metadata);
	try {
		ConditionOutcome outcome = getMatchOutcome(context, metadata);
		logOutcome(classOrMethodName, outcome);
		recordEvaluation(context, classOrMethodName, outcome);
		return outcome.isMatch();
	}
	catch (NoClassDefFoundError ex) {
		throw new IllegalStateException("......",ex);
	}
	catch (RuntimeException ex) {
		throw new IllegalStateException("......" + getName(metadata), ex);
	}
}
private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
	if (metadata instanceof ClassMetadata) {
		ClassMetadata classMetadata = (ClassMetadata) metadata;
		return classMetadata.getClassName();
	}
	MethodMetadata methodMetadata = (MethodMetadata) metadata;
	return methodMetadata.getDeclaringClassName() + "#"
			+ methodMetadata.getMethodName();
}

@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnJavaCondition extends SpringBootCondition {

	private static final JavaVersion JVM_VERSION = JavaVersion.getJavaVersion();

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		// 本例中attributes 的值如下图,真实我们定义的值
		Map<String, Object> attributes = metadata
				.getAnnotationAttributes(ConditionalOnJava.class.getName());
		Range range = (Range) attributes.get("range");
		JavaVersion version = (JavaVersion) attributes.get("value");
		return getMatchOutcome(range, JVM_VERSION, version);
	}

	protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion,
			JavaVersion version) {
		// 判断是否满足版本要求
		boolean match = runningVersion.isWithin(range, version);
		String expected = String.format(
				range == Range.EQUAL_OR_NEWER ? "(%s or newer)" : "(older than %s)",
				version);
		// 创建ConditionMessage对象,并初始化一些信息
		ConditionMessage message = ConditionMessage
				.forCondition(ConditionalOnJava.class, expected)
				.foundExactly(runningVersion);
		// 将匹配结果和message封装为ConditionOutcome 对象,方便SpringBootCondition统一处理
		return new ConditionOutcome(match, message);
	}

}

在这里插入图片描述

参考资料:彻底搞定 Java 注解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java硕哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值