SpringBoot的条件注解@ConditionalOnXxx
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 |
注意:一些如@ConditionalOnClass注解,提供了条件类型是Class或字符串两种方式,这里的字符串一般是类的全路径名(包名+类名)
实现原理
单纯的注释肯定实现不了这个功能,处理逻辑一般都在条件注解解析类中,我们来看看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-INF路径看到这么一个文件:spring.factories
SpringBoot应用在启动的时候都会加@SpringBootApplication注解,而@SpringBootApplication注解内部又包含EnableAutoConfiguration注解。
EnableAutoConfiguration注解里面有导入了AutoConfigurationImportSelector.class类
在 AutoConfigurationImportSelector类中又通过Spring的SpringFactoriesLoader取加载类,即Spring的Spi机制
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南