-
SpringBoot的条件注解
-
实现原理
-
@Conditional 原理解析
-
条件注解解析类(类似OnJavaCondition)到底怎么自动加载的
springboot版本: 2.6.0-SNAPSHOT 紧随官网最新稳定版本
SpringBoot中有一个很重要的模块,那就是spring-boot-autoconfigure
。可以说我们所有的三方依赖的自动配置AutoConfiguration
(自动化配置类),比如KafkaAutoConfiguration
、GsonAutoConfiguration
、WebMvcAutoConfiguration
、JsonbAutoConfiguration
等。

这些AutoConfiguration
都只会在特定情况下才会生效,具体是如何实现的呢?就是依赖于SpringBoot的条件注解
SpringBoot的条件注解

条件注解 | 作用 | 条件注解解析类 |
---|---|---|
ConditionalOnBean | ApplicationContext存在某些bean时条件才成立 | OnBeanCondition |
ConditionalOnClass | classPath中存在某些Class时条件才成立 | OnClassCondition |
ConditionalOnCloudPlatform | 在某些云平台(Kubemetes、Heroku、Cloud Foundry)下条件才成立 | OnCloudPlatformCondition |
ConditionalOnExpression | SPEL表达式成立时条件才成立 | OnExpressionCondition |
ConditionalOnJava | JDK某些版本条件才成立 | OnJavaCondition |
ConditionalOnJndi | JNDI路径下存在时条件才成立 | OnJndiCondition |
ConditionalOnMissingBean | ApplicationContext不存在某些bean时条件才成立 | OnBeanCondition |
ConditionalOnMissingClass | classpath中不存在某些class是条件才成立 | OnClassCondition |
ConditionalOnNotWebApplication | 在非Web环境下条件才成立 | OnWebApplicationCondition |
ConditionalOnProperty | Environment中存在某些配置项时条件才成立 | OnPropertyCondition |
ConditionalOnResource | 存在某些资源时条件才成立 | OnResourceCondition |
ConditionalOnSingleCandidate | ApplicationContext中存在且只有一个bean时条件成立 | OnBeanCondition |
ConditionalOnWebApplication | 在Web环境下条件才成立 | OnWebApplicationCondition |
实现原理
单纯的注释肯定实现不了这个功能,一般都处理逻辑都在条件注解解析类中,我们来看看OnJndiCondition
是如何实现的
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnJavaCondition extends SpringBootCondition {
private static final JavaVersion JVM_VERSION = JavaVersion.getJavaVersion();
/**
* 根据 JavaVersion 获取当前jdk版本, JavaVersion 是SpringBoot 自己封装的
* JavaVersion中的实现主要是根据 jdk特有版本的一些方法来判断 jdk的版本的
* 比如: 判断Optional empty方法是否存在,来看这是不是 jdk8 判断 Optional stream方法是否存在,来看是不是 jdk9
* ConditionOutcome 包含两个属性 match:条件是否匹配的结果 ConditionMessage:条件的匹配结果的信息对象
* @param context the condition context
* @param metadata the annotation metadata
* @return
*/
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取 ConditionalOnJava 注解的属性
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
Range range = (Range) attributes.get("range");
JavaVersion version = (JavaVersion) attributes.get("value");
// 判断当前jdk版本是否满足条件
return getMatchOutcome(range, JVM_VERSION, version);
}
protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion, JavaVersion version) {
boolean match = isWithin(runningVersion, range, version);
String expected = String.format((range != Range.EQUAL_OR_NEWER) ? "(older than %s)" : "(%s or newer)", version);
ConditionMessage message = ConditionMessage.forCondition(ConditionalOnJava.class, expected)
.foundExactly(runningVersion);
return new ConditionOutcome(match, message);
}
/**
* Determines if the {@code runningVersion} is within the specified range of versions.
* @param runningVersion the current version.
* @param range the range
* @param version the bounds of the range
* @return if this version is within the specified range
*/
private boolean isWithin(JavaVersion runningVersion, Range range, JavaVersion version) {
if (range == Range.EQUAL_OR_NEWER) {
return runningVersion.isEqualOrNewerThan(version);
}
if (range == Range.OLDER_THAN) {
return runningVersion.isOlderThan(version);
}
throw new IllegalStateException("Unknown range " + range);
}
}
其实我们看到上面的OnJavaCondition
是继承了抽象类SpringBootCondition
而抽象类SpringBootCondition
实现了Condition
接口
public abstract class SpringBootCondition implements Condition {
private final Log logger = LogFactory.getLog(getClass());
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
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("Could not evaluate condition on " + classOrMethodName + " due to "
+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "
+ "that class. This can also happen if you are "
+ "@ComponentScanning a springframework package (e.g. if you "
+ "put a @ComponentScan in the default package by mistake)", ex);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
}
}
private String getName(AnnotatedTypeMetadata metadata) {
if (metadata instanceof AnnotationMetadata) {
return ((AnnotationMetadata) metadata).getClassName();
}
if (metadata instanceof MethodMetadata) {
MethodMetadata methodMetadata = (MethodMetadata) metadata;
return methodMetadata.getDeclaringClassName() + "." + methodMetadata.getMethodName();
}
return metadata.toString();
}
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();
}
protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(getLogMessage(classOrMethodName, outcome));
}
}
private StringBuilder getLogMessage(String classOrMethodName, ConditionOutcome outcome) {
StringBuilder message = new StringBuilder();
message.append("Condition ");
message.append(ClassUtils.getShortName(getClass()));
message.append(" on ");
message.append(classOrMethodName);
message.append(outcome.isMatch() ? " matched" : " did not match");
if (StringUtils.hasLength(outcome.getMessage())) {
message.append(" due to ");
message.append(outcome.getMessage());
}
return message;
}
private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) {
if (context.getBeanFactory() != null) {
ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this,
outcome);
}
}
/**
* Determine the outcome of the match along with suitable log output.
* @param context the condition context
* @param metadata the annotation metadata
* @return the condition outcome
*/
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
/**
* Return true if any of the specified conditions match.
* @param context the context
* @param metadata the annotation meta-data
* @param conditions conditions to test
* @return {@code true} if any condition matches.
*/
protected final boolean anyMatches(ConditionContext context, AnnotatedTypeMetadata metadata,
Condition... conditions) {
for (Condition condition : conditions) {
if (matches(context, metadata, condition)) {
return true;
}
}
return false;
}
/**
* Return true if any of the specified condition matches.
* @param context the context
* @param metadata the annotation meta-data
* @param condition condition to test
* @return {@code true} if the condition matches.
*/
protected final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition condition) {
if (condition instanceof SpringBootCondition) {
return ((SpringBootCondition) condition).getMatchOutcome(context, metadata).isMatch();
}
return condition.matches(context, metadata);
}
}
@Conditional 原理解析
需要注意的是@Conditional
不是springboot提供的注解,是spring提供的,所以Springboot上面的接口都是基于Spring的@Conditional
接口扩展的
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
可以看到 Conditional
源码中的属性需要传递一个实现了接口Condition
的类
我们简单来使用下,我们随便定义一个自己的Condition
实现类
public class MyConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
这里我们设置 matches 方法返回的是 false
然后我们再定义一个配置类
@Component
@Conditional(MyConditional.class)
public class TestConfig {
public TestConfig() {
System.out.println("测试我加载了");
}
}
我们在启动的时候可以看到TestConfig
类是不会被加载,如果将matches
方法改为true
就会加载了
条件注解解析类(类似OnJavaCondition)到底怎么自动加载的
我们知道了条件解析类的实现类如OnJavaCondition
,那他是如何被SpringBoot加载的呢?
这里我们就需要了解SpringBoot的工作加载机制了
一般我们可以看到一些SpringBoot整合的三方jar通常会在META-INFO
路径看到这么一个文件:spring.factories
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h2jXdzCA-1635320720608)(https://gitee.com/weihu666/figure-bed/raw/master/img/image-20211027153145971.png)]
SpringBoot应用在启动的时候都会加@SpringBootApplication
注解,而@SpringBootApplication
注解内部又包含EnableAutoConfiguration
注解

EnableAutoConfiguration
注解里面有导入了AutoConfigurationImportSelector.class
类

在 AutoConfigurationImportSelector
类中又通过Spring的SpringFactoriesLoader
取加载类,即Spring的Spi机制

有点人可能会想为什么不直接使用@Component注解注入bean,是因为我们很多三方jar整合的包不在Application下面的子包,SpringBoot默认是只会加载Application及Application子包下面的Bean
原文始发于微信公众号(小奏技术):深入理解SpringBoot 条件注解
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/30243.html