在Spring的世界里,我们习惯了用@Component、@Service声明Bean,用@Autowired注入依赖。但你是否遇到过这样的场景:同一个接口有多个实现,需要在不同环境加载不同的Bean?或者某个功能模块只有在特定条件满足时才需要注册?这时候,@Conditional注解就是那把被低估的瑞士军刀。
什么是@Conditional
@Conditional是Spring 4.0引入的条件装配注解。它的核心逻辑很简单:只有当指定条件匹配时,被标注的Bean才会注册到容器中。这个注解接受一个或多个Condition接口的实现类,通过matches()方法返回布尔值来决定是否装配。
@Configuration public class MyConfig { @Bean @Conditional(OnProductionCondition.class) public DataSource productionDataSource() { return new ProductionDataSource(); } }Condition接口只有一个方法:boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)。通过ConditionContext,你可以拿到Environment、BeanFactory、ClassLoader等一切需要的信息。
妙用一:多环境配置切换
传统做法是用@Profile,但@Profile只支持简单的环境标识。@Conditional可以做得更精细。比如,根据操作系统加载不同Bean:
public class OnWindowsCondition implements Condition { @Override public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata metadata) { return ctx.getEnvironment().getProperty("os.name").contains("Windows"); } }再比如,根据某个配置项是否存在来决定是否启用功能模块。这比在代码里写if-else优雅得多——条件不满足时,Bean根本不存在,调用方甚至感知不到它的缺失。
妙用二:自定义Starter的自动装配
这是@Conditional最经典的应用场景。Spring Boot的自动配置类大量使用了@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等派生注解。
假设你要写一个Redis Starter,核心逻辑是:当classpath中存在RedisTemplate时自动配置,但如果用户已经手动定义了RedisTemplate,就跳过。这时候:
@Configuration @ConditionalOnClass(RedisTemplate.class) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean public RedisTemplate redisTemplate() { return new RedisTemplate(); } }@ConditionalOnMissingBean保证了用户自定义的Bean优先,@ConditionalOnClass避免了类不存在时的报错。这两个注解组合,让你的Starter既智能又安全。
妙用三:灰度发布与功能开关
在生产环境中,我们经常需要根据用户、租户或流量比例加载不同的Bean实现。用@Conditional可以实现轻量级的功能开关:
public class OnGrayReleaseCondition implements Condition { @Override public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata metadata) { String gray = ctx.getEnvironment().getProperty("feature.gray.enabled"); return "true".equals(gray); } }配合配置中心动态刷新,可以实现不重启应用切换Bean。当然,更复杂的灰度需要结合路由规则,但@Conditional提供了最基础的开关能力。
妙用四:测试环境Mock替换
写集成测试时,我们经常需要替换真实的外部依赖为Mock。传统做法是用@MockBean,但它会替换整个上下文中的Bean。用@Conditional可以更精准:只在测试profile下加载Mock实现,生产代码零侵入。
@Bean @ConditionalOnProperty(name = "test.mock.enabled", havingValue = "true") public PaymentService mockPaymentService() { return new MockPaymentService(); }使用时的三个陷阱
第一,条件评估时机。@Conditional在Bean定义阶段评估,此时BeanFactory尚未完全初始化。不要试图在matches()中获取其他Bean,可能拿到null。
第二,顺序敏感。多个条件注解时,评估顺序不确定。如果需要依赖关系,用@AutoConfigureAfter或@AutoConfigureBefore控制自动配置类的顺序。
第三,调试困难。条件不满足时Bean静默消失,排查问题很头疼。建议开启debug=true,Spring Boot会打印条件评估报告,告诉你哪个条件匹配失败。
总结
@Conditional的价值在于把“要不要装配”这个决策从代码逻辑中抽离出来,变成声明式的元数据。它让配置更灵活、代码更干净、扩展更优雅。无论是写Starter、做多环境适配,还是实现功能开关,它都是Spring生态中不可或缺的一环。下次遇到“根据条件决定是否创建Bean”的需求,别急着写if-else——试试@Conditional,你会发现Spring早就为你准备好了答案。