全部条件注解的实现
概述
Spring Boot 自动配置的核心能力之一是通过 @Conditional 系列注解实现条件化 Bean 注册。自动配置类上的 @ConditionalOnXxx 注解决定了哪些配置类在什么条件下生效。
本文逐个深入 10 种条件注解的源码实现,包括它们的核心方法、批量检测、缓存策略、宽松匹配和内部类设计。
本文基于 Spring Boot 3.x 源码分析。
1. 条件注解总览
| # | 注解 | 关键类 | 评估条件 |
|---|---|---|---|
| ① | @ConditionalOnClass | OnClassCondition | classpath 中存在指定类 |
| ② | @ConditionalOnMissingClass | OnClassCondition | classpath 中不存在指定类 |
| ③ | @ConditionalOnBean | OnBeanCondition | 容器中已存在指定 Bean |
| ④ | @ConditionalOnMissingBean | OnBeanCondition | 容器中不存在指定 Bean |
| ⑤ | @ConditionalOnSingleCandidate | OnBeanCondition | 容器中只有一个候选 Bean |
| ⑥ | @ConditionalOnProperty | OnPropertyCondition | Environment 中存在指定属性 |
| ⑦ | @ConditionalOnResource | OnResourceCondition | classpath 中存在指定资源 |
| ⑧ | @ConditionalOnExpression | OnExpressionCondition | SpEL 表达式求值为 true |
| ⑨ | @ConditionalOnJndi | OnJndiCondition | JNDI 中存在指定资源 |
| ⑩ | @ConditionalOnWebApplication | OnWebApplicationCondition | 当前 Web 应用类型匹配 |
| ⑪ | @ConditionalOnNotWebApplication | OnWebApplicationCondition | 当前不是 Web 应用 |
| ⑫ | @ConditionalOnCloudPlatform | OnCloudPlatformCondition | 指定云平台已激活 |
2. OnClassCondition 批量类名存在性检测
2.1 核心方法 getOutcomes()
// OnClassCondition.java
public class OnClassCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 获取 @ConditionalOnClass 和 @ConditionalOnMissingClass 的属性
ClassLoader classLoader = context.getClassLoader();
MatchSpecs matchSpecs = createMatchSpecs(metadata);
// 批量检测
List<ConditionOutcome> outcomes = getOutcomes(classLoader, matchSpecs);
// 合并结果
return ConditionOutcome.merge(outcomes);
}
private List<ConditionOutcome> getOutcomes(ClassLoader classLoader,
MatchSpecs matchSpecs) {
List<ConditionOutcome> outcomes = new ArrayList<>();
// 处理 @ConditionalOnClass 的所有值
for (String className : matchSpecs.getRequiredClasses()) {
outcomes.add(getOutcome(classLoader, className, true));
}
// 处理 @ConditionalOnMissingClass 的所有值
for (String className : matchSpecs.getMissingClasses()) {
outcomes.add(getOutcome(classLoader, className, false));
}
return outcomes;
}
private ConditionOutcome getOutcome(ClassLoader classLoader,
String className, boolean required) {
try {
// 使用 ClassUtils.isPresent() 检测类是否存在
if (ClassUtils.isPresent(className, classLoader)) {
return required
? ConditionOutcome.match(
ConditionMessage.forCondition("OnClassCondition")
.found("class").item(className))
: ConditionOutcome.noMatch(
ConditionMessage.forCondition("OnClassCondition")
.didNotFind("class").item(className));
} else {
return required
? ConditionOutcome.noMatch(
ConditionMessage.forCondition("OnClassCondition")
.didNotFind("class").item(className))
: ConditionOutcome.match(
ConditionMessage.forCondition("OnClassCondition")
.found("class").item(className));
}
} catch (Exception ex) {
return ConditionOutcome.noMatch(
ConditionMessage.forCondition("OnClassCondition")
.didNotFind("class").item(className));
}
}
}2.2 ClassNameFilter.MISSING 批量过滤
在 ConfigurationClassFilter 中,OnClassConditionFilter 使用 ClassNameFilter 进行批量过滤:
// OnClassConditionFilter.java(ConfigurationClassFilter 的内部类)
static class OnClassConditionFilter implements ConditionFilter {
private final ClassNameFilter classNameFilter;
OnClassConditionFilter() {
// 创建 MISSING 过滤器——查找不存在的类
this.classNameFilter = ClassNameFilter.MISSING;
}
@Override
public ConditionOutcome match(AutoConfigurationClass autoConfigClass) {
// 从 @ConditionalOnClass 注解中提取需要的类名列表
List<String> requiredClasses = autoConfigClass.getRequiredClasses();
if (requiredClasses.isEmpty()) {
return ConditionOutcome.match(); // 无条件要求 → 默认通过
}
// 批量检测
List<String> missing = this.classNameFilter
.matches(requiredClasses, autoConfigClass.getClassLoader());
if (missing.isEmpty()) {
return ConditionOutcome.match(); // 所有类都存在
}
return ConditionOutcome.noMatch(
ConditionMessage.forCondition("OnClassCondition")
.didNotFind("class", "classes")
.items(Style.QUOTE, missing));
}
}2.3 结果缓存机制
ClassNameFilter 内部维护了一个 matchResults 缓存:
// ClassNameFilter.java
abstract class ClassNameFilter {
private final ConcurrentHashMap<String, Boolean> matchResults =
new ConcurrentHashMap<>();
List<String> matches(List<String> classNames, ClassLoader classLoader) {
List<String> matched = new ArrayList<>();
for (String className : classNames) {
// 先从缓存中查找
Boolean result = matchResults.get(className);
if (result == null) {
// 缓存未命中 → 实际检测
result = matches(className, classLoader);
matchResults.put(className, result); // 写入缓存
}
if (result) {
matched.add(className);
}
}
return matched;
}
}缓存的 key = 全限定类名。同一个类名在整个启动周期中只会被 Class.forName() 检测一次,后续查询直接从 ConcurrentHashMap 读取。
2.4 ClassUtils.isPresent() 源码
// ClassUtils.java
public static boolean isPresent(String className, ClassLoader classLoader) {
try {
// 尝试加载类
Class.forName(className, false, classLoader);
return true;
} catch (ClassNotFoundException ex) {
return false; // 类不存在
} catch (Throwable ex) {
// 类存在但无法加载(如链接错误)→ 也视为不存在
return false;
}
}注意第二个参数 initialize = false——只加载不初始化,避免触发静态代码块。
3. OnBeanCondition 的 4 种 searchStrategy
3.1 searchStrategy 枚举
// SearchStrategy.java
public enum SearchStrategy {
/** 只在当前 BeanFactory 中搜索 */
CURRENT,
/** 只在父 BeanFactory 中搜索 */
PARENT,
/** 搜索当前及所有祖先 BeanFactory */
ANCESTORS,
/** 搜索整个层次结构(当前 + 父 + 祖父...) */
ALL
}3.2 在 OnBeanCondition 中的使用
// OnBeanCondition.java
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 获取 @ConditionalOnBean 或 @ConditionalOnMissingBean 的属性
ConditionalOnBean annotation = ...;
SearchStrategy search = annotation.search();
// 根据 searchStrategy 获取相应的 BeanFactory
ListBeanFactoryProvider provider = getListBeanFactoryProvider(context, search);
// 执行查询
boolean matched = matches(provider, ...);
return matched
? ConditionOutcome.match(...)
: ConditionOutcome.noMatch(...);
}3.3 四种搜索策略的实现
// OnBeanCondition.java
private ListableBeanFactory getListBeanFactoryProvider(
ConditionContext context, SearchStrategy search) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
switch (search) {
case CURRENT:
// 只返回当前 BeanFactory(最常见)
return wrap(beanFactory);
case PARENT:
// 只返回父 BeanFactory(如果有的话)
BeanFactory parent = beanFactory.getParentBeanFactory();
return parent instanceof ListableBeanFactory
? wrap((ListableBeanFactory) parent)
: wrap(null);
case ANCESTORS:
// 返回当前 + 所有祖先 BeanFactory 的合并视图
return mergeAncestors(beanFactory);
case ALL:
// 返回当前 BeanFactory(与 CURRENT 语义相同,但包括子容器)
return wrap(beanFactory);
default:
return wrap(beanFactory);
}
}3.4 搜索范围对比
| SearchStrategy | 搜索范围 | 典型场景 | 性能 |
|---|---|---|---|
CURRENT | 仅当前 ApplicationContext | 绝大多数自动配置类 | 最快 |
PARENT | 仅父 ApplicationContext | 父子容器分离的场景(如 Spring Security) | 较快 |
ANCESTORS | 当前 + 所有祖先 | 多层父子容器嵌套 | 较慢 |
ALL | 整个层次结构(含子容器) | 需要全局唯一 Bean 检测 | 较慢 |
4. OnBeanCondition.Outcomes 内部类的 4 种查询
4.1 内部类结构
// OnBeanCondition.java
public class OnBeanCondition extends SpringBootCondition {
// Outcomes 内部类封装了四种查询方式
abstract static class Outcomes {
// 创建 Outcomes 实例(根据注解属性自动选择查询模式)
static Outcomes from(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 解析 @ConditionalOnBean / @ConditionalOnMissingBean 的属性
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
ConditionalOnBean.class.getName(), true);
// 根据属性决定使用哪种查询模式
if (attributes.containsKey("value")) {
// 按 Class 类型查询
return new ClassQuery(attributes);
} else if (attributes.containsKey("annotation")) {
// 按注解查询
return new AnnotationQuery(attributes);
} else if (attributes.containsKey("name")) {
// 按 Bean 名称查询
return new BeanNameQuery(attributes);
} else {
// 按 Bean 类型查询(不支持具体类型,仅查类型)
return new BeanTypeQuery(attributes);
}
}
// 子类实现具体的匹配逻辑
abstract boolean matches(ListableBeanFactory beanFactory);
}
}4.2 四种查询的源码
// 查询方式 1:按 Class 类型
private static class ClassQuery extends Outcomes {
private final Class<?>[] beanClasses;
@Override
boolean matches(ListableBeanFactory beanFactory) {
for (Class<?> beanClass : beanClasses) {
// 按类型查找 Bean
Map<String, ?> beans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(beanFactory, beanClass);
if (beans.isEmpty()) {
return false; // 找不到指定类型的 Bean
}
}
return true;
}
}
// 查询方式 2:按注解
private static class AnnotationQuery extends Outcomes {
private final Class<? extends Annotation>[] annotations;
@Override
boolean matches(ListableBeanFactory beanFactory) {
for (Class<? extends Annotation> annotation : annotations) {
// 查找所有带指定注解的 Bean
Map<String, Object> beans = BeanFactoryUtils
.beansWithAnnotation(beanFactory, annotation);
if (beans.isEmpty()) {
return false;
}
}
return true;
}
}
// 查询方式 3:按 Bean 名称
private static class BeanNameQuery extends Outcomes {
private final String[] beanNames;
@Override
boolean matches(ListableBeanFactory beanFactory) {
for (String beanName : beanNames) {
// BeanFactory.containsBean() + BeanFactoryUtils.isGeneratedBeanName()
if (!beanFactory.containsBean(beanName)
|| BeanFactoryUtils.isGeneratedBeanName(beanName)) {
return false;
}
}
return true;
}
}
// 查询方式 4:按 Bean 类型(通用)
private static class BeanTypeQuery extends Outcomes {
// 本质与 ClassQuery 相同,但用于没有显式指定 Class 的情况
// 通常与 @ConditionalOnMissingBean 配合,查找是否有任何同类型 Bean
}4.3 查询方式选择决策树
@ConditionalOnBean / @ConditionalOnMissingBean 的属性
│
├─ value 属性有值(指定 Class) → ClassQuery
│ @ConditionalOnBean(DataSource.class)
│ @ConditionalOnMissingBean(JdbcTemplate.class)
│
├─ annotation 属性有值(指定注解) → AnnotationQuery
│ @ConditionalOnBean(annotation = EnableAutoConfiguration.class)
│
├─ name 属性有值(指定 Bean 名称) → BeanNameQuery
│ @ConditionalOnBean(name = "dataSource")
│
└─ 都没有指定 → BeanTypeQuery(通用类型匹配)
@ConditionalOnMissingBean // 不传任何参数5. OnPropertyCondition 的 RelaxedNames 匹配
5.1 核心逻辑
// OnPropertyCondition.java
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 解析 @ConditionalOnProperty 注解属性
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
ConditionalOnProperty.class.getName(), true);
String prefix = (String) attributes.getFirst("prefix");
String havingValue = (String) attributes.getFirst("havingValue");
boolean matchIfMissing = (Boolean) attributes.getFirst("matchIfMissing");
// 获取 name 属性(支持多个)
List<String> names = (List<String>) attributes.get("name");
// 是否支持宽松匹配
boolean relaxedNames = (Boolean) attributes.getFirst("relaxedNames");
for (String name : names) {
String fullName = (prefix != null) ? prefix + "." + name : name;
// 使用 Binder 获取属性值
Binder binder = Binder.get(context.getEnvironment());
BindResult<String> result = binder.bind(fullName, String.class);
if (result.isBound()) {
// 属性存在 → 检查值是否匹配
String value = result.get();
if (havingValue != null && !havingValue.equals(value)) {
return ConditionOutcome.noMatch(...);
}
} else {
// 属性不存在 → 看 matchIfMissing
if (!matchIfMissing) {
return ConditionOutcome.noMatch(...);
}
}
}
return ConditionOutcome.match(...);
}5.2 RelaxedNames 宽松匹配机制
// RelaxedNames.java
public final class RelaxedNames implements Iterable<String> {
private final Set<String> values = new LinkedHashSet<>();
public RelaxedNames(String name) {
// 为同一个属性名生成所有可能的变体
// 例如: "my-property" → 生成以下变体
initialize(name);
}
private void initialize(String name) {
// 1. 原始形式
add(name);
// 2. 驼峰形式
add(toCamelCase(name));
// 3. 下划线形式
add(toUnderscore(name));
// 4. 大写形式
add(toUpperCase(name));
// 5. 点号分隔
add(toDotted(name));
// ...
}
}5.3 变体生成示例
# application.yml 中定义的属性
my-app:
data-source:
url: jdbc:mysql://localhost:3306/db通过 RelaxedNames,@ConditionalOnProperty 可以用以下任意一种形式匹配:
| 变体 | 值 |
|---|---|
my-app.data-source.url | jdbc:mysql://localhost:3306/db |
my-app.dataSource.url | jdbc:mysql://localhost:3306/db |
my-app.data_source.url | jdbc:mysql://localhost:3306/db |
MY_APP.DATA_SOURCE.URL | jdbc:mysql://localhost:3306/db |
5.4 name / havingValue / matchIfMissing 三条件
@ConditionalOnProperty(
name = "spring.datasource.url", // 条件 1:属性名
havingValue = "jdbc:mysql://...", // 条件 2:期望的属性值
matchIfMissing = false // 条件 3:属性缺失时的行为
)matchIfMissing | 属性存在且匹配 | 属性存在但不匹配 | 属性不存在 |
|---|---|---|---|
false(默认) | ✅ 匹配 | ❌ 不匹配 | ❌ 不匹配 |
true | ✅ 匹配 | ❌ 不匹配 | ✅ 匹配 |
6. OnResourceCondition 资源探测
6.1 源码
// OnResourceCondition.java
public class OnResourceCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 获取 @ConditionalOnResource 的资源路径
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
ConditionalOnResource.class.getName(), true);
List<String> resources = (List<String>) attributes.get("resources");
ResourceLoader resourceLoader = context.getResourceLoader();
// 逐个检查资源是否存在
for (String resource : resources) {
Resource loaded = resourceLoader.getResource(resource);
// 通过 File.exists() 判断资源是否存在
if (!loaded.exists()) {
return ConditionOutcome.noMatch(
ConditionMessage.forCondition("OnResourceCondition")
.didNotFind("resource").item(resource));
}
}
return ConditionOutcome.match(
ConditionMessage.forCondition("OnResourceCondition")
.found("resources").items(resources));
}
}6.2 支持的资源路径格式
@ConditionalOnResource(resources = {
"classpath:logback-spring.xml", // classpath 下的资源
"file:/etc/config/myapp.properties", // 文件系统中的资源
"classpath:META-INF/spring.factories" // classpath 下的 META-INF
})6.3 ResourceLoader.getResource() + File.exists()
resourceLoader.getResource() 返回的 Resource 对象使用模板方法模式:
// AbstractResource.exists()
@Override
public boolean exists() {
// File.exists():对于 classpath 资源,尝试获取 File 句柄
// 如果 File 不存在,再尝试通过 InputStream 判断
try {
return getFile().exists();
} catch (IOException ex) {
// 获取 File 失败 → 尝试通过 InputStream
try {
getInputStream().close();
return true;
} catch (Throwable isEx) {
return false;
}
}
}7. OnExpressionCondition SpEL 求值
7.1 源码
// OnExpressionCondition.java
public class OnExpressionCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 获取 @ConditionalOnExpression 的 SpEL 表达式
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
ConditionalOnExpression.class.getName(), true);
String expression = (String) attributes.getFirst("value");
// 解析表达式前先替换属性占位符
String resolved = context.getEnvironment()
.resolvePlaceholders(expression);
// 创建 SpEL 解析器
ExpressionParser parser = new SpelExpressionParser();
Expression parsedExpression = parser.parseExpression(resolved);
// 创建评估上下文(注入 Environment 作为变量)
StandardEvaluationContext evalContext =
new StandardEvaluationContext();
evalContext.setRootObject(context.getEnvironment());
evalContext.setVariable("environment", context.getEnvironment());
// 求值
Boolean result = parsedExpression.getValue(
evalContext, Boolean.class);
return result
? ConditionOutcome.match(...)
: ConditionOutcome.noMatch(...);
}
}7.2 属性占位符替换
context.getEnvironment().resolvePlaceholders() 将 ${...} 替换为实际属性值:
// 示例
@ConditionalOnExpression("${spring.datasource.enabled:true}")
// 在评估前,${spring.datasource.enabled:true} 被替换为实际值
// 如果 application.yml 中有 spring.datasource.enabled=true
// 则表达式变为: "true"
// SpEL 解析为 Boolean: true7.3 典型用法
// 用法 1:简单布尔属性
@ConditionalOnExpression("${my.feature.enabled:false}")
// 用法 2:多条件组合
@ConditionalOnExpression("${my.feature.enabled:false} " +
"and ${my.data.source:false}")
// 用法 3:判断字符串值
@ConditionalOnExpression(
"'${spring.profiles.active}'.contains('dev') " +
"or '${spring.profiles.active}'.contains('test')")8. OnJndiCondition JNDI 探测
8.1 源码
// OnJndiCondition.java
public class OnJndiCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 获取 @ConditionalOnJndi 的 JNDI 资源列表
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
ConditionalOnJndi.class.getName(), true);
List<String> locations = (List<String>) attributes.get("value");
for (String location : locations) {
if (!jndiLocationExists(location)) {
return ConditionOutcome.noMatch(
ConditionMessage.forCondition("OnJndiCondition")
.didNotFind("JNDI resource").item(location));
}
}
return ConditionOutcome.match(...);
}
private boolean jndiLocationExists(String location) {
try {
// 创建 InitialContext
InitialContext initialContext = new InitialContext();
// 尝试查找 JNDI 资源
initialContext.lookup(location);
return true;
} catch (NamingException ex) {
// JNDI 资源不存在 → 返回 false
return false;
}
}
}8.2 异常处理策略
| 异常类型 | 含义 | 返回结果 |
|---|---|---|
NameNotFoundException | JNDI 资源不存在 | false |
NoInitialContextException | JNDI 环境未配置 | false |
CommunicationException | 无法连接到 JNDI 服务器 | false |
其他 NamingException | 其他命名异常 | false |
所有异常统一处理——只要 lookup() 抛出 NamingException,就认为资源不存在。
9. OnCloudPlatformCondition 云平台检测
9.1 源码
// OnCloudPlatformCondition.java
public class OnCloudPlatformCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 获取 @ConditionalOnCloudPlatform 指定的云平台
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
ConditionalOnCloudPlatform.class.getName(), true);
CloudPlatform platform = (CloudPlatform) attributes
.getFirst("value");
// 使用 CloudPlatform.getActive() 检测当前平台
CloudPlatform active = CloudPlatform.getActive(
context.getEnvironment());
if (active == platform) {
return ConditionOutcome.match(
ConditionMessage.forCondition("OnCloudPlatformCondition")
.found("cloud platform").item(platform.name()));
}
return ConditionOutcome.noMatch(
ConditionMessage.forCondition("OnCloudPlatformCondition")
.didNotFind("cloud platform").item(platform.name()));
}
}9.2 CloudPlatform.getActive() 的实现
// CloudPlatform.java
public enum CloudPlatform {
// 枚举值
CLOUD_FOUNDRY {
@Override
public boolean isActive(Environment environment) {
// 检测 VCAP_APPLICATION 环境变量
return environment.containsProperty("VCAP_APPLICATION");
}
},
HEROKU {
@Override
public boolean isActive(Environment environment) {
// 检测 DYNO 环境变量
return environment.containsProperty("DYNO");
}
},
KUBERNETES {
@Override
public boolean isActive(Environment environment) {
// 检测 KUBERNETES_SERVICE_HOST 环境变量
return environment.containsProperty(
"KUBERNETES_SERVICE_HOST");
}
};
// 核心方法:从 Environment 中探测活跃的云平台
public static CloudPlatform getActive(Environment environment) {
for (CloudPlatform platform : values()) {
if (platform.isActive(environment)) {
return platform;
}
}
return null;
}
public abstract boolean isActive(Environment environment);
}9.3 平台检测机制
| 云平台 | 检测方式 | 环境变量 / 属性 |
|---|---|---|
CLOUD_FOUNDRY | Environment 中是否存在 VCAP_APPLICATION | VCAP_APPLICATION |
HEROKU | Environment 中是否存在 DYNO | DYNO |
KUBERNETES | Environment 中是否存在 KUBERNETES_SERVICE_HOST | KUBERNETES_SERVICE_HOST |
10. ConfigurationCondition.ConfigurationPhase 阶段区分
10.1 接口定义
// ConfigurationCondition.java
public interface ConfigurationCondition extends Condition {
/** 获取此条件应在哪个阶段评估 */
ConfigurationPhase getConfigurationPhase();
enum ConfigurationPhase {
/** 在 ConfigurationClassParser 解析 @Configuration 类时评估 */
PARSE_CONFIGURATION,
/** 在注册 BeanDefinition 时评估 */
REGISTER_BEAN
}
}10.2 两个阶段的差异
| 阶段 | 时机 | 可用的 BeanDefinition | 典型用途 |
|---|---|---|---|
PARSE_CONFIGURATION | ConfigurationClassParser 解析 @Configuration 类时 | 尚无明确的 BeanDefinition | @ConditionalOnClass(只需要 ClassLoader) |
REGISTER_BEAN | BeanDefinition 注册阶段 | 部分 BeanDefinition 已就绪 | @ConditionalOnBean(需要查找 BeanDefinition) |
10.3 自动配置类中的实现
// OnBeanCondition 实现了 ConfigurationCondition
public class OnBeanCondition extends SpringBootCondition
implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
// OnBeanCondition 需要在 REGISTER_BEAN 阶段评估
// 因为需要其他配置类的 BeanDefinition 已注册
return ConfigurationPhase.REGISTER_BEAN;
}
}
// OnClassCondition 也实现了 ConfigurationCondition
public class OnClassCondition extends SpringBootCondition
implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
// OnClassCondition 只需要 ClassLoader,可以在 PARSE_CONFIGURATION 阶段评估
return ConfigurationPhase.PARSE_CONFIGURATION;
}
}10.4 阶段对自动配置加载的影响
PARSE_CONFIGURATION 阶段:
ConfigurationClassParser 处理 @Configuration 类
├─ 加载 @Configuration 类的注解元数据
├─ 处理 @Import、@ComponentScan
└─ 评估 ConfigurationPhase.PARSE_CONFIGURATION 的条件
└─ OnClassCondition:检查 classpath 是否存在指定类
REGISTER_BEAN 阶段:
ConfigurationClassBeanDefinitionReader 注册 BeanDefinition
├─ 注册 @Bean 方法生成的 BeanDefinition
├─ 注册 @Import 导入的 BeanDefinition
└─ 评估 ConfigurationPhase.REGISTER_BEAN 的条件
└─ OnBeanCondition:检查容器中是否有指定 Bean10.5 @ConditionalOnBean 为什么需要 REGISTER_BEAN 阶段
@Configuration
@ConditionalOnBean(DataSource.class) // ← 需要 PARSE_CONFIGURATION 阶段已经解析了 DataSource 的配置
public class JdbcTemplateAutoConfiguration {
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}如果 @ConditionalOnBean 在 PARSE_CONFIGURATION 阶段就评估,此时 DataSource 的 BeanDefinition 可能尚未注册,会导致误判为不匹配。
11. @ConditionalOnMissingBean 与 @ConditionalOnBean 的互补关系
11.1 核心逻辑
// OnBeanCondition.java
private boolean matches(ListableBeanFactory beanFactory,
ConditionalOnBean annotation) {
// 1. 获取注解属性
SearchStrategy search = annotation.search();
String[] beanNames = extractBeanNames(annotation);
Class<?>[] beanClasses = extractBeanClasses(annotation);
// 2. 执行查询
boolean beanExists = matches(beanFactory, search, beanNames, beanClasses);
// 3. 判断匹配结果
if (annotation instanceof ConditionalOnBean) {
// @ConditionalOnBean:有 Bean 才匹配
return beanExists;
} else if (annotation instanceof ConditionalOnMissingBean) {
// @ConditionalOnMissingBean:没有 Bean 才匹配
return !beanExists;
}
return false;
}11.2 matchIfMissing 属性的语义
@ConditionalOnMissingBean(DataSource.class) // matchIfMissing 不适用
@ConditionalOnBean(name = "dataSource") // 必须有 dataSource 这个 Bean
@ConditionalOnBean(name = "dataSource",
matchIfMissing = false) // 显式指定:必须有(默认)
@ConditionalOnBean(name = "dataSource",
matchIfMissing = true) // 没有 dataSource 时也匹配11.3 典型使用模式
| 模式 | 代码 | 效果 |
|---|---|---|
| 用户自定义优先 | @ConditionalOnMissingBean(DataSource.class) | 用户自定义了 DataSource 则跳过自动配置 |
| 依赖存在时才生效 | @ConditionalOnBean(DataSource.class) | 没有 DataSource 就不创建 JdbcTemplate |
| 缺失时提供默认 | @ConditionalOnMissingBean | 用户没定义则提供默认 Bean |
| 覆盖自动配置 | @ConditionalOnMissingBean(RedisTemplate.class) | 自动配置的 RedisTemplate 被用户自定义替代 |
11.4 自动配置中的互补示例
@AutoConfiguration
@ConditionalOnClass(RedisOperations.class)
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // ← 用户没定义才创建
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class) // ← 用户没定义才创建
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}规则:
@ConditionalOnMissingBean(name = "redisTemplate")—— 用户自定义了redisTemplateBean → 跳过;否则 → 创建默认实现@ConditionalOnMissingBean(StringRedisTemplate.class)—— 用户自定义了StringRedisTemplate→ 跳过;否则 → 创建默认实现
总结
| # | 细节点 | 核心要点 |
|---|---|---|
| ① | OnClassCondition.getOutcomes() 批量检测 | ClassUtils.isPresent() + ClassNameFilter + ConcurrentHashMap 缓存 |
| ② | OnBeanCondition 4 种 searchStrategy | CURRENT / PARENT / ALL / ANCESTORS,CURRENT 最快 |
| ③ | Outcomes 内部类 4 种查询 | ClassQuery / AnnotationQuery / BeanNameQuery / BeanTypeQuery |
| ④ | OnPropertyCondition 宽松匹配 | Binder.bind() + RelaxedNames 生成所有变体 + havingValue/matchIfMissing |
| ⑤ | OnResourceCondition 资源探测 | ResourceLoader.getResource() + exists() 文件/InputStream 检测 |
| ⑥ | OnExpressionCondition SpEL | resolvePlaceholders() 替换 ${...} → SpelExpressionParser 求值 |
| ⑦ | OnJndiCondition JNDI 探测 | InitialContext.lookup() → NamingException 统一视为不存在 |
| ⑧ | OnCloudPlatformCondition 云平台 | CloudPlatform.getActive() 从 Environment 检测环境变量 |
| ⑨ | ConfigurationPhase 阶段区分 | PARSE_CONFIGURATION(OnClassCondition)vs REGISTER_BEAN(OnBeanCondition) |
| ⑩ | @ConditionalOnMissingBean 互补 | matchIfMissing 控制缺失时的匹配行为,@MissingBean 实现用户自定义优先 |