条件装配体系 - @Conditional/@Profile 与 Spring Boot 条件注解族源码解析
一、引言
条件装配(Conditional Assembly)是 Spring Framework 容器化配置的核心能力之一。它允许 Bean 的注册与否取决于特定的条件(如类路径是否存在某个类、某个属性是否配置、某个 Bean 是否已被注册等),从而实现了配置的声明式动态化。
Spring Framework 3.1 引入了 @Profile 注解用于简单的环境区分,Spring Framework 4.0 正式推出了底层基础设施 @Conditional 注解和 Condition 接口。Spring Boot 在此基础上构建了一套丰富的条件注解族(@ConditionalOnClass、@ConditionalOnBean 等),成为自动配置(Auto-Configuration)的基石。
本文将深入源码,从底层 @Conditional 开始,逐层剖析 @Profile 的原理、Spring Boot 条件注解族的实现机制、ConditionEvaluator.shouldSkip() 的执行逻辑,以及条件评估在 ConfigurationClassParser 中的切入时机,并通过实战案例展示如何利用条件注解构建灵活的支付系统。
二、@Conditional 注解源码解析
2.1 @Conditional 注解定义
@Conditional 是 Spring 4.0 引入的核心注解,位于 org.springframework.context.annotation 包中:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 所有 Condition 必须同时匹配(逻辑与),
* 才会注册该 Bean。
*/
Class<? extends Condition>[] value();
}- 使用范围:可以标注在类上(
ElementType.TYPE)或方法上(ElementType.METHOD),分别对应类级别的条件装配和Bean 方法级别的条件装配。 - 多个条件:当指定多个
Condition类时,它们之间是逻辑与关系,必须全部通过条件评估才会注册 Bean。 - 运行时保留:
RetentionPolicy.RUNTIME确保注解信息在运行时可以被反射读取。
2.2 Condition 接口
Condition 接口定义了单个条件的评估契约:
@FunctionalInterface
public interface Condition {
/**
* 判断条件是否匹配。
*
* @param context 条件评估上下文,提供环境、BeanFactory、类加载器等信息
* @param metadata 标注了 @Conditional 的类或方法的注解元数据
* @return true 表示条件匹配,Bean 会被注册;false 表示跳过
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}这是一个函数式接口(@FunctionalInterface),可以用 Lambda 表达式简化实现。
2.3 ConditionContext 接口
ConditionContext 是条件评估时的上下文信息门面,为 Condition.matches() 提供运行时环境访问能力:
public interface ConditionContext {
/** 获取 BeanDefinition 注册表 */
BeanDefinitionRegistry getRegistry();
/** 获取 ConfigurableListableBeanFactory */
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
/** 获取当前环境信息(profile、属性等) */
Environment getEnvironment();
/** 获取资源加载器 */
ResourceLoader getResourceLoader();
/** 获取类加载器 */
@Nullable
ClassLoader getClassLoader();
}设计意图:ConditionContext 充当门面(Facade)模式的角色,封装了 Spring 容器运行时的多个核心组件,使 Condition 实现类无需直接依赖容器内部复杂结构即可进行条件判断。
2.4 自定义 Condition 实现示例
public class OnWindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String osName = context.getEnvironment().getProperty("os.name");
return osName != null && osName.toLowerCase().contains("windows");
}
}
@Configuration
public class OsConfig {
@Bean
@Conditional(OnWindowsCondition.class)
public FileSystem windowsFileSystem() {
return new WindowsFileSystem();
}
@Bean
@Conditional(OnLinuxCondition.class)
public FileSystem linuxFileSystem() {
return new LinuxFileSystem();
}
}当一个类或方法上标注了多个 @Conditional 时,所有 Condition 都必须返回 true:
@Bean
@Conditional({OnWindowsCondition.class, OnProfileCondition.class})
public DataSource windowsDevDataSource() {
return new EmbeddedDatabaseBuilder().build();
}三、@Profile 注解的原理
3.1 @Profile 注解定义
@Profile 在 Spring 3.1 引入,本质上是对 @Conditional 的封装:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* 接受一个或多个 profile 名称,
* 支持否定运算符 "!"。
*/
String[] value();
}@Profile 本身直接标注了 @Conditional(ProfileCondition.class),这意味着被 @Profile 标注的配置类或 Bean 方法,其条件评估完全委托给 ProfileCondition。
3.2 ProfileCondition 实现
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 1. 从注解元数据中读取 @Profile 的 value
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
// 2. 遍历每个 profile 名称
for (Object value : attrs.get("value")) {
String[] profileNames = (String[]) value;
for (String profileName : profileNames) {
boolean negative = profileName.startsWith("!");
String cleanedName = negative ? profileName.substring(1) : profileName;
// 3. 委托给 Environment.acceptsProfiles() 判断
if (!context.getEnvironment().acceptsProfiles(Profiles.of(cleanedName))) {
// 否定式 profile:如果当前 profile 不匹配,反而条件成立
if (!negative) {
return false;
}
} else {
// 肯定式 profile:匹配则返回 true,除非是否定式
if (negative) {
return false;
}
}
}
}
return true;
}
// 没有 @Profile 注解(不应发生),默认通过
return true;
}
}核心逻辑:
- 从注解元数据中解析出
@Profile的value数组。 - 遍历每个 profile 名称,处理否定前缀
!。 - 调用
Environment.acceptsProfiles()判断当前 Environment 是否激活了指定 profile。 - 多个 profile 之间是逻辑或关系:只要有一个 profile 匹配,条件即成立。
3.3 @Profile 使用示例
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://prod-db:3306/orders");
ds.setUsername("app");
ds.setPassword("${db.password}");
return ds;
}
}@Profile("!dev") 的语义是"当 dev profile 未激活时生效",在 CI/CD 场景中常用于排除开发环境专用的配置。
四、Spring Boot 条件注解族
Spring Boot 在 spring-boot-autoconfigure 模块的 org.springframework.boot.autoconfigure.condition 包中定义了丰富的条件注解,作为自动配置的核心基础设施。
4.1 条件注解族总览
| 注解 | 用途 | 评估依据 |
|---|---|---|
@ConditionalOnClass | 指定类在类路径中存在 | Class.forName |
@ConditionalOnMissingClass | 指定类在类路径中不存在 | Class.forName |
@ConditionalOnBean | 容器中已存在指定 Bean | BeanFactory.getBeanNamesForType / getBeanDefinitionNames |
@ConditionalOnMissingBean | 容器中不存在指定 Bean | 同上 |
@ConditionalOnProperty | 指定属性满足条件 | Environment.getProperty |
@ConditionalOnExpression | SpEL 表达式评估为 true | ExpressionParser.parseExpression |
@ConditionalOnResource | 指定资源在类路径中存在 | ResourceLoader.getResource |
@ConditionalOnWebApplication | 当前是 Web 应用 | 检测 Servlet / WebApplicationContext 等 |
@ConditionalOnNotWebApplication | 当前不是 Web 应用 | 同上 |
@ConditionalOnSingleCandidate | 容器中只有一个候选 Bean | BeanFactory.getBeanNamesForType |
@ConditionalOnJava | Java 版本满足要求 | System.getProperty("java.version") |
@ConditionalOnJndi | 指定 JNDI 资源可用 | InitialContext.lookup |
4.2 @ConditionalOnClass 源码解析
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/** 需要存在的类(Class 字面量) */
Class<?>[] value() default {};
/** 需要存在的类的全限定名(字符串形式) */
String[] name() default {};
}对应的条件评估类 OnClassCondition 继承自 SpringBootCondition:
@Ordered(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 解析 @ConditionalOnClass 和 @ConditionalOnMissingClass
ClassLoader classLoader = context.getClassLoader();
ConditionMessage message = ConditionMessage.empty();
// 处理 @ConditionalOnClass
List<ConditionalOnClass> onClasses = getAll(metadata, ConditionalOnClass.class);
if (onClasses != null) {
for (ConditionalOnClass onClass : onClasses) {
// 检查 Class.forName 是否成功
for (String className : onClass.name()) {
if (!isPresent(className, classLoader)) {
return ConditionOutcome.noMatch(
message.found("unwanted class").items(className));
}
}
}
}
// ... 处理 @ConditionalOnMissingClass
return ConditionOutcome.match(message);
}
private static boolean isPresent(String className, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
ClassUtils.forName(className, classLoader);
return true;
} catch (ClassNotFoundException | NoClassDefFoundError ex) {
return false;
}
}
}设计要点:
@ConditionalOnClass内部标注了@Conditional(OnClassCondition.class)。OnClassCondition优先级最高(Ordered.HIGHEST_PRECEDENCE),因为类路径条件是代价最低的判断,应先执行。- 支持
Class<?>[]和String[] name两种方式,字符串方式可以引用编译期不可见的类。
4.3 @ConditionalOnBean / @ConditionalOnMissingBean 源码解析
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
/** Bean 类型 */
Class<?>[] value() default {};
/** Bean 名称 */
String[] name() default {};
/** Bean 类型(字符串形式) */
String[] type() default {};
/** 注解类型(标注了指定注解的 Bean) */
Class<? extends Annotation>[] annotation() default {};
/** Bean 来源的配置类 */
Class<?>[] parameterizedContainer() default {};
}OnBeanCondition.getMatchOutcome() 的核心逻辑:
class OnBeanCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 将匹配条件封装为 SearchSpecification
SearchSpecification spec = new SearchSpecification(
context, metadata, ConditionalOnBean.class);
// 搜索容器中匹配的 Bean
int matchingBeans = getBeanNamesForSpec(spec, context, metadata).size();
// 对于 @ConditionalOnBean:有匹配的 Bean 则条件成立
// 对于 @ConditionalOnMissingBean:没有匹配的 Bean 则条件成立
boolean required = metadata.isAnnotated(
ConditionalOnBean.class.getName());
return new ConditionOutcome(required == (matchingBeans > 0),
message ...);
}
}设计要点:
@ConditionalOnBean和@ConditionalOnMissingBean共用同一个OnBeanCondition条件类,通过检查元数据中的不同注解来决定"需要存在"还是"需要不存在"。- 支持按类型、名称、注解类型等多种搜索方式。
- 搜索时会考虑 BeanDefinition 的注册状态,包括尚未实例化的 BeanDefinition。
4.4 @ConditionalOnProperty 源码解析
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
/** 属性的前缀 */
String prefix() default "";
/** 属性名 */
String[] value() default {};
/** 属性名(与 value 二选一,但 thisOrHavingValue 场景使用 name) */
String havingValue() default "";
/** 没有该属性时是否匹配 */
boolean matchIfMissing() default false;
}OnPropertyCondition.getMatchOutcome() 的简化逻辑:
class OnPropertyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 解析注解属性
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
ConditionalOnProperty.class.getName(), true);
String prefix = (String) attributes.getFirst("prefix");
List<String> values = (List<String>) attributes.get("value");
String havingValue = (String) attributes.getFirst("havingValue");
boolean matchIfMissing = (Boolean) attributes.getFirst("matchIfMissing");
Environment environment = context.getEnvironment();
for (String value : values) {
String resolvedPrefix = (prefix != null ? prefix + "." : "");
String property = environment.getProperty(resolvedPrefix + value);
if (property == null) {
if (!matchIfMissing) {
return ConditionOutcome.noMatch("property '" +
resolvedPrefix + value + "' is not set");
}
continue;
}
String expected = havingValue.length() > 0 ? havingValue : "true";
if (!expected.equalsIgnoreCase(property)) {
return ConditionOutcome.noMatch("property '" +
resolvedPrefix + value + "'=" + property +
", expected=" + expected);
}
}
return ConditionOutcome.match();
}
}设计要点:
- 当
havingValue不指定时,默认要求属性值为"true"(大小写不敏感)。 matchIfMissing=true允许在属性未配置时仍通过条件,常用于"可选启用"的场景。- Spring Boot 2.x 开始支持
prefix+value的分层配置风格。
4.5 @ConditionalOnExpression 源码解析
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnExpressionCondition.class)
public @interface ConditionalOnExpression {
/** SpEL 表达式 */
String value() default "true";
}OnExpressionCondition 实现:
class OnExpressionCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String expression = (String) metadata.getAnnotationAttributes(
ConditionalOnExpression.class.getName()).get("value");
if (expression.startsWith("#{") || expression.startsWith("${")) {
// 兼容旧版写法
expression = expression.substring(2, expression.length() - 1);
}
// 创建 SpEL 表达式评估上下文
ExpressionParser parser = new SpelExpressionParser();
Expression parsed = parser.parseExpression(expression);
// BeanFactory 解析器上下文,支持 Bean 引用
BeanExpressionContext beanExpressionContext = new BeanExpressionContext(
context.getBeanFactory(), null);
BeanExpressionResolver resolver = context.getBeanFactory()
.getBeanExpressionResolver();
// 评估表达式
Object value = parsed.getValue(evaluationContext);
boolean match = (value instanceof Boolean) ? (Boolean) value
: Boolean.parseBoolean(value.toString());
return new ConditionOutcome(match, ...);
}
}4.6 @ConditionalOnWebApplication / @ConditionalOnNotWebApplication
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
/** 检测策略 */
Type type() default Type.SERVLET;
enum Type {
/** 根据是否有 Servlet API 判断 */
SERVLET,
/** 根据 WebApplicationContext 类型判断 */
REACTIVE,
/** 任意一种 Web 环境 */
ANY
}
}OnWebApplicationCondition 的核心判断逻辑:
class OnWebApplicationCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
boolean webApplication = isWebApplication(context, metadata);
boolean required = metadata.isAnnotated(
ConditionalOnWebApplication.class.getName());
// @ConditionalOnWebApplication 要求是 Web 应用
// @ConditionalOnNotWebApplication 要求不是 Web 应用
return new ConditionOutcome(required == webApplication, ...);
}
private boolean isWebApplication(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 策略1:检查 ClassLoader 能否加载 Servlet API
if (canLoadServletClass(context.getClassLoader())) {
return true;
}
// 策略2:检查 BeanFactory 中是否有 WebApplicationContext
if (hasWebApplicationContextBean(context.getBeanFactory())) {
return true;
}
// 策略3:检查 Environment 中是否有 web 相关属性
if (hasWebEnvironmentProperty(context.getEnvironment())) {
return true;
}
return false;
}
}4.7 SpringBootCondition 基类
所有 Spring Boot 条件类都继承自 SpringBootCondition:
public abstract class SpringBootCondition implements Condition {
@Override
public final boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 1. 获取注解元数据的类名或方法名
String classOrMethodName = getClassNameOrMethodName(metadata);
try {
// 2. 调用子类实现的 getMatchOutcome()
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// 3. 记录条件评估日志
logConditionEvaluation(classOrMethodName, outcome, metadata);
return outcome.isMatch();
} catch (Exception ex) {
throw new IllegalStateException(
"Condition evaluation failed for " + classOrMethodName, ex);
}
}
/** 模板方法:由子类实现具体的匹配逻辑 */
public abstract ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata);
}模板方法模式:matches() 是模板方法,定义了条件评估的通用骨架(获取元数据 → 评估 → 记录日志),而 getMatchOutcome() 是抽象方法,由子类 OnClassCondition、OnBeanCondition 等具体实现。
五、ConditionEvaluator.shouldSkip() 源码精读
ConditionEvaluator 是 Spring Framework 内部用于评估所有 @Conditional 注解的核心类,位于 org.springframework.context.annotation 包中。
5.1 shouldSkip() 方法
class ConditionEvaluator {
/**
* 判断指定的配置类或 Bean 方法是否应该被跳过。
*
* @param metadata 注解元数据
* @param phase 当前处理的阶段(PARSE_CONFIGURATION / REGISTER_BEAN)
* @return true 表示应该跳过,不注册该 Bean
*/
public boolean shouldSkip(AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 1. 检查是否标注了 @Conditional 注解
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 2. 如果没有指定 phase,尝试推断
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
// 类级别且是配置类候选 → PARSE_CONFIGURATION 阶段
phase = ConfigurationPhase.PARSE_CONFIGURATION;
} else {
// 方法级别 → REGISTER_BEAN 阶段
phase = ConfigurationPhase.REGISTER_BEAN;
}
}
// 3. 获取所有 Condition 实现类
List<String[]> conditions = getConditionClasses(metadata);
if (conditions.isEmpty()) {
return false;
}
// 4. 逐一评估 Condition
for (String[] conditionList : conditions) {
for (String conditionClassName : conditionList) {
// 实例化 Condition
Condition condition = getCondition(conditionClassName, this.beanClassLoader);
// 检查 Phase 校验(用于 ConfigurationCondition)
if (condition instanceof ConfigurationCondition) {
ConfigurationPhase configPhase =
((ConfigurationCondition) condition).getConfigurationPhase();
if (configPhase != null && configPhase != phase) {
// 阶段不匹配 → 跳过当前 Condition(延迟到下一阶段评估)
continue;
}
}
// 调用 Condition.matches() 评估
if (!condition.matches(this.context, metadata)) {
// 任意一个 Condition 不匹配 → 跳过
return true;
}
}
}
return false;
}
}5.2 ConfigurationPhase 枚举
public enum ConfigurationPhase {
/** 配置类解析阶段:在 @Configuration 类被解析时评估 */
PARSE_CONFIGURATION,
/** Bean 注册阶段:在 Bean 方法被注册时评估 */
REGISTER_BEAN
}两个阶段的意义:
| 阶段 | 时机 | 用途 |
|---|---|---|
PARSE_CONFIGURATION | ConfigurationClassParser 解析 @Configuration 类时 | 决定整个配置类是否被解析。如果跳过,则该配置类下的所有 Bean 定义都不可见 |
REGISTER_BEAN | ConfigurationClassBeanDefinitionReader 注册具体 Bean 定义时 | 决定单个 @Bean 方法是否被注册 |
5.3 ConfigurationCondition 接口
某些 Condition 需要在特定阶段进行评估,Spring 提供了 ConfigurationCondition 接口:
public interface ConfigurationCondition extends Condition {
/** 返回此条件应在哪个阶段评估 */
ConfigurationPhase getConfigurationPhase();
}例如,OnBeanCondition 实现 ConfigurationCondition 并返回 REGISTER_BEAN 阶段,因为容器中其他 Bean 是否已注册只有在注册阶段才能准确判断。
5.4 getConditionClasses() 实现
private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
Conditional.class.getName(), true);
if (attributes == null) {
return Collections.emptyList();
}
List<String[]> conditionClasses = new ArrayList<>();
// value 的类型是 Class<? extends Condition>[],转成全限定名
for (Object value : attributes.get("value")) {
Class<?>[] classes = (Class<?>[]) value;
String[] classNames = new String[classes.length];
for (int i = 0; i < classes.length; i++) {
classNames[i] = classes[i].getName();
}
conditionClasses.add(classNames);
}
return conditionClasses;
}关键设计决策:Spring 支持重复的 @Conditional 注解(@Repeatable),因此 getAllAnnotationAttributes 返回的是 MultiValueMap。每个 @Conditional 的 value 可能是一个 Condition 数组,最终的匹配策略是所有条件的逻辑与。
5.5 shouldSkip() 执行流程图
┌─────────────────────────────────────────────────────────┐
│ shouldSkip() 入口 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 是否标注了 @Conditional 注解? │
│ metadata.isAnnotated(...) │
├─────────────────────┬───────────────────────────────────┤
│ 否 │ 是 │
├─────────────────────┴───────────────────────────────────┤
│ 返回 false(不跳过 │
│ → 正常注册) │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 推断 ConfigurationPhase │
│ ● 类级别 + 配置类候选 → PARSE_CONFIGURATION │
│ ● 方法级别 → REGISTER_BEAN │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 获取所有 Condition 类名 │
│ getConditionClasses(metadata) │
└─────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────┐
│ 遍历 Condition,逐一评估 │
│ │
│ 对于每个 ConditionClassName │
└────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 实例化 Condition │
│ │
│ ↓ │
│ 是 ConfigurationCondition? │
│ ├─ 是 → 检查 Phase 是否匹配 │
│ │ ├─ 不匹配 → continue(延迟评估) │
│ │ └─ 匹配 → 继续 ↓ │
│ └─ 否 → 直接继续 ↓ │
│ │
│ ↓ │
│ condition.matches(context, meta) │
│ ├─ false → 返回 true(跳过) │
│ └─ true → 继续下一个 Condition │
└──────────────────────────────────────┘
│
▼
┌─────────────────────┐
│ 所有 Condition │
│ 都返回 true? │
├────────┬────────────┤
│ 是 │ 否 │
│ 返回 │ 返回 true │
│ false │(跳过) │
└────────┴────────────┘六、条件评估的执行时机
条件评估的切入点位于 ConfigurationClassParser 的解析流程中。
6.1 ConfigurationClassParser 解析入口
class ConfigurationClassParser {
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
} else {
// 处理其他类型的 BeanDefinition
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
}
protected void parse(AnnotationMetadata metadata, String beanName) {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) {
// ★ 条件评估点:是否需要跳过整个配置类?
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return; // 跳过,不解析此类
}
// 继续解析配置类
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
// ... 处理已存在的配置类
} else {
// 递归解析 @Import、@ComponentScan、@Bean 等
sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
} while (sourceClass != null);
}
this.configurationClasses.put(configClass, configClass);
}
}6.2 doProcessConfigurationClass 中的条件评估
在 doProcessConfigurationClass 中,当处理 @Bean 方法和 @Import 时,也会进行条件评估:
class ConfigurationClassParser {
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, ...) {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignableTo(ImportBeanDefinitionRegistrar.class)) {
// 处理 ImportBeanDefinitionRegistrar
} else {
// ★ 对 @Import 的类进行条件评估
if (this.conditionEvaluator.shouldSkip(candidate.getMetadata(),
ConfigurationPhase.PARSE_CONFIGURATION)) {
continue;
}
// 递归解析被 @Import 的配置类
parse(candidate.getMetadata(), candidateName);
}
}
}
}6.3 Bean 注册阶段的条件评估
在 ConfigurationClassBeanDefinitionReader 中,注册 @Bean 方法时会再次进行条件评估:
class ConfigurationClassBeanDefinitionReader {
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// ★ 注册阶段的条件评估
if (trackedConditionEvaluator.skip(configClass)) {
// 如果整个配置类被跳过,取消所有已注册的 BeanDefinition
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
// 注册 @Bean 方法
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// 注册 @ImportResource 引入的 XML 配置
loadBeanDefinitionsForImportedResources(configClass.getImportedResources());
// 注册 ImportBeanDefinitionRegistrar
loadBeanDefinitionsForRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
// ★ @Bean 方法级别的条件评估
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
// 跳过此 @Bean 方法
configClass.skippedBeanMethods.add(metadata.getMethodName());
return;
}
// ... 实际注册 BeanDefinition
}
}6.4 双重评估机制
Spring 对 @Configuration 类采用了双重评估机制:
- 第一轮(解析阶段):在
ConfigurationClassParser.processConfigurationClass()中调用shouldSkip(metadata, PARSE_CONFIGURATION),决定是否解析这个配置类。 - 第二轮(注册阶段):在
ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass()中再次评估,如果跳过则将从容器中移除已注册的 BeanDefinition。
为什么要双重评估?
因为在 PARSE_CONFIGURATION 阶段,容器尚未完全初始化,部分 Bean 可能尚未注册,此时基于 Bean 是否存在的条件(如 @ConditionalOnBean)无法准确判断。等到 REGISTER_BEAN 阶段,大部分 Bean 定义已就绪,再进行第二次评估可以做出更准确的判断。
6.5 完整的执行流程时间线
应用启动
│
▼
AbstractApplicationContext.refresh()
│
├──→ invokeBeanFactoryPostProcessors()
│ │
│ └──→ ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()
│ │
│ └──→ ConfigurationClassParser.parse()
│ │
│ ├──→ 【第一轮条件评估】PARSE_CONFIGURATION 阶段
│ │ shouldSkip(configClassMetadata, PARSE_CONFIGURATION)
│ │ │
│ │ ├──→ OnClassCondition(类路径检查)
│ │ ├──→ OnPropertyCondition(属性检查)
│ │ └──→ ProfileCondition(profile 检查)
│ │
│ └──→ doProcessConfigurationClass()
│ ├──→ @ComponentScan 扫描
│ ├──→ @Import 处理
│ │ └──→ 【条件评估】对 @Import 类
│ └──→ @Bean 方法收集
│
├──→ registerBeanPostProcessors()
│
└──→ finishBeanFactoryInitialization()
│
└──→ ConfigurationClassBeanDefinitionReader
│
└──→ 【第二轮条件评估】REGISTER_BEAN 阶段
shouldSkip(beanMethodMetadata, REGISTER_BEAN)
│
├──→ OnBeanCondition(Bean 存在性检查)
└──→ OnMissingBeanCondition七、条件注解的组合使用与优先级规则
7.1 注解叠加规则
当多个条件注解叠加在同一个配置类或 Bean 方法上时,遵循以下规则:
| 叠加方式 | 语义 | 示例 |
|---|---|---|
同一个 @Conditional 的多个 value | 逻辑与,必须全部通过 | @Conditional({A.class, B.class}) |
| 多个不同条件注解 | 逻辑与,必须全部通过 | @ConditionalOnClass + @ConditionalOnProperty |
| 同一个注解类重复出现 | 逻辑与,合并所有条件 | 两个 @ConditionalOnClass |
注意:Spring Boot 条件注解族默认是逻辑与关系,不支持直接的"逻辑或"表达。要实现"逻辑或",可以使用 @ConditionalOnExpression 配合 SpEL 的 or 操作符,或自定义 Condition 实现。
7.2 条件评估优先级
条件评估的内部执行顺序受 @Order 注解和 Ordered 接口影响:
// SpringBootCondition 默认优先级
@Ordered(Ordered.LOWEST_PRECEDENCE) // 默认最低优先级
class OnBeanCondition extends SpringBootCondition { ... }
// OnClassCondition 明确指定最高优先级
@Ordered(Ordered.HIGHEST_PRECEDENCE) // 最先执行
class OnClassCondition extends SpringBootCondition { ... }实际执行顺序(从先到后):
OnClassCondition/OnMissingClassCondition— 类路径检查,代价最低,无副作用OnResourceCondition— 资源存在性检查OnPropertyCondition— 属性检查OnWebApplicationCondition— Web 环境判断OnBeanCondition/OnMissingBeanCondition— Bean 存在性检查,需要容器准备就绪OnExpressionCondition— SpEL 表达式评估,依赖前面条件准备的环境ProfileCondition— Profile 匹配检查
7.3 组合使用典型模式
模式1:先检查类路径,再检查 Bean 是否存在
@Configuration
@ConditionalOnClass(name = "redis.clients.jedis.Jedis") // 先检查 Jedis 是否在类路径
@ConditionalOnBean(RedisTemplate.class) // 再检查 Bean 是否已注册
public class RedisCacheAutoConfiguration {
// ...
}模式2:属性开关 + 类路径检查
@Configuration
@ConditionalOnProperty(prefix = "app.cache", name = "enabled", havingValue = "true", matchIfMissing = false)
@ConditionalOnClass(name = "com.github.benmanes.caffeine.cache.Caffeine")
public class CaffeineCacheConfig {
// ...
}模式3:Profile + 属性条件
@Configuration
@Profile("cloud")
@ConditionalOnProperty(prefix = "spring.cloud.config", name = "enabled", matchIfMissing = true)
public class CloudConfigConfiguration {
// ...
}7.4 条件评估中的"短路"行为
ConditionEvaluator.shouldSkip() 的内部实现采用短路评估:
// ConditionEvaluator.shouldSkip() 内部
for (String conditionClassName : conditionList) {
Condition condition = getCondition(conditionClassName, this.beanClassLoader);
// ... Phase 校验 ...
if (!condition.matches(this.context, metadata)) {
// ★ 遇到第一个不匹配就立即返回 true(跳过)
return true;
}
}这意味着多个条件的执行顺序是有意义的:将检查代价低、判断力强的条件放在前面,可以尽早短路,避免不必要的开销。
7.5 自定义 Condition 的 Phase 选择
实现 ConfigurationCondition 时,需谨慎选择 Phase:
public class MyOnServiceCondition implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
// 如果条件依赖于其他 Bean 是否存在,应选择 REGISTER_BEAN 阶段
// 如果只依赖类路径或属性,可以选择 PARSE_CONFIGURATION 阶段
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// ...
}
}选择策略:
| 条件依赖 | 推荐 Phase |
|---|---|
| 仅依赖类路径 | PARSE_CONFIGURATION |
| 仅依赖 Environment 属性 | PARSE_CONFIGURATION |
| 依赖其他 Bean 的注册 | REGISTER_BEAN |
| 依赖 BeanFactory 中 BeanDefinition 数量 | REGISTER_BEAN |
| 不确定时 | REGISTER_BEAN(更安全但略慢) |
八、实战案例:支付系统根据配置动态加载支付宝/微信/银联 DataSource
8.1 需求描述
假设我们正在构建一个聚合支付平台,需要根据配置文件动态决定启用哪些支付渠道:
app.payment.alibaba.enabled=true→ 加载支付宝相关 DataSource 和 Serviceapp.payment.wechat.enabled=true→ 加载微信支付相关 DataSource 和 Serviceapp.payment.unionpay.enabled=true→ 加载银联支付相关 DataSource 和 Service
每个支付渠道都需要独立的数据库连接池(DataSource)。
8.2 项目结构
com.example.payment/
├── config/
│ ├── PaymentDataSourceConfiguration.java ← 主配置类
│ ├── AlipayDataSourceConfiguration.java ← 支付宝数据源配置
│ ├── WechatPayDataSourceConfiguration.java ← 微信支付数据源配置
│ └── UnionPayDataSourceConfiguration.java ← 银联数据源配置
├── condition/
│ ├── PaymentCondition.java ← 自定义条件基类
│ └── AlipayEnabledCondition.java ← 支付宝条件
│ └── WechatPayEnabledCondition.java ← 微信条件
│ └── UnionPayEnabledCondition.java ← 银联条件
├── datasource/
│ ├── AlipayDataSource.java ← 支付宝数据源包装
│ ├── WechatPayDataSource.java ← 微信数据源包装
│ └── UnionPayDataSource.java ← 银联数据源包装
├── service/
│ ├── PaymentService.java ← 支付服务接口
│ ├── AlipayPaymentService.java ← 支付宝支付实现
│ ├── WechatPayPaymentService.java ← 微信支付实现
│ └── UnionPayPaymentService.java ← 银联支付实现
└── PaymentApplication.java ← Spring Boot 启动类8.3 application.yml 配置
app:
payment:
alibaba:
enabled: true
datasource:
url: jdbc:mysql://localhost:3306/alipay_db
username: alipay_app
password: ${ALIPAY_DB_PASSWORD}
pool-size: 10
wechat:
enabled: true
datasource:
url: jdbc:mysql://localhost:3306/wechat_db
username: wechat_app
password: ${WECHAT_DB_PASSWORD}
pool-size: 8
unionpay:
enabled: false
datasource:
url: jdbc:mysql://localhost:3306/unionpay_db
username: unionpay_app
password: ${UNIONPAY_DB_PASSWORD}
pool-size: 5