条件装配:@Conditional 与 @Profile
引言
同一套代码要在“开发/测试/生产”环境给出不同 Bean(如内存数据库 vs 正式库、假短信 vs 真短信)。条件装配让 Bean 的注册与否取决于运行环境或任意自定义条件:@Profile 做环境开关,@Conditional 做通用条件机制——@Profile 本身就是基于 @Conditional 实现的。
核心概念
@Profile("dev"):标注在@Configuration/@Component/@Bean方法上,仅当该 profile 激活时该 Bean 才注册。表达式支持!dev(非)、dev | prod(或)、dev & cloud(与,以官方文档为准)。- 激活方式:启动参数
-Dspring.profiles.active=dev、环境变量SPRING_PROFILES_ACTIVE,或编程ctx.getEnvironment().setActiveProfiles("dev")(须在 refresh 之前)。 @Conditional(org.springframework.context.annotation):配合Condition接口使用;Condition.matches(ConditionContext, AnnotatedTypeMetadata)返回 true 才注册。- 两者关系:
@Profile是元注解@Conditional(ProfileCondition.class)的组合注解。 - 应用场景还包括 Boot 的
@ConditionalOnClass/@ConditionalOnProperty(那是 Boot 的扩展,机制同源)。
代码示例
public interface MessageSender { void send(String msg); }
// 开发环境:日志假实现
@Profile("dev")
@Component
public class LogSender implements MessageSender {
public void send(String msg) { System.out.println("[dev-log] " + msg); }
}
// 生产环境:真短信实现
@Profile("prod")
@Component
public class RealSmsSender implements MessageSender {
public void send(String msg) { /* 调用短信网关 */ }
}
// 自定义条件:JDK 版本 >= 17 才启用
public class Jdk17Condition implements Condition {
@Override
public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta) {
return Runtime.version().feature() >= 17;
}
}
@Configuration
public class FeatureConfig {
@Bean
@Conditional(Jdk17Condition.class)
public MessageSender jdkSender() { return new LogSender(); }
}
// 启动:-Dspring.profiles.active=dev → 只有 LogSender 被注册
注意点
- 条件方法/类在“刷新容器、实例化 Bean 前”评估:
@Conditional作用于@Bean方法时,条件 false 则整个方法跳过。 - 一个 profile 都没激活时,带
@Profile的 Bean 全部不注册;靠@Profile("dev | prod")兜底不要误以为有默认值。 - 自定义
Condition.matches里别做重量级 IO;它可能被多次调用。 - 条件只决定“注册与否”,被跳过 Bean 的依赖方若没有替代 Bean,启动时会因找不到依赖而失败——注意成组设计。
- Boot 下
@Profile与@ConditionalOnProperty常组合使用,前者管环境、后者管开关。
小结
@Profile 是环境维度的高层开关(dev/prod…),@Conditional 是底层通用机制(任意条件)。理解“条件在 Bean 实例化前评估、false 即跳过注册”这一点,就能解释环境相关 Bean 的装配行为,也是 Boot 自动配置的灵魂。