参考资料:彻底搞定 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 注解