副标题:让你的Bean像变色龙一样智能地出现和消失 🦎
🎬 开场白:Bean的"选秀"现场
嘿,小伙伴们!👋 今天我们要聊一个超级有趣的话题——Spring的@Conditional注解。
想象一下,你是一档选秀节目的导演,手里有一堆选手(Bean),但不是所有选手都能上台表演。你得根据不同的条件来决定:
- 🎤 如果是唱歌节目,就让歌手上台
- 🎸 如果是乐器演奏,就让乐手上台
- 💃 如果观众太少,就取消演出
Spring的@Conditional注解就是这样一个"智能导演",它能根据各种条件来决定要不要创建某个Bean!
📚 第一幕:什么是@Conditional?
基本概念
@Conditional是Spring 4.0引入的一个超级灵活的注解,它的作用就像一个"门卫大叔"🚪:
@Configuration
public class MyConfig {
@Bean
@Conditional(OnWindowsCondition.class)
public MessageService windowsService() {
return new WindowsMessageService();
}
@Bean
@Conditional(OnLinuxCondition.class)
public MessageService linuxService() {
return new LinuxMessageService();
}
}
在这个例子中:
- 如果运行在Windows系统上,就创建
windowsService - 如果运行在Linux系统上,就创建
linuxService
就像你去餐厅点菜,服务员会问:"您是吃辣还是不吃辣?" 🌶️
🎪 第二幕:生活中的比喻
让我用一个超级接地气的例子来解释:
📱 智能家居场景
想象你家有个智能管家系统:
早上7点:
- 如果是工作日 → 播放闹钟 ⏰
- 如果是周末 → 让你继续睡 😴
天气情况:
- 如果下雨 ☔ → 提醒你带伞
- 如果晴天 ☀️ → 建议你出去运动
温度情况:
- 如果 < 10°C → 开启暖气 🔥
- 如果 > 30°C → 开启空调 ❄️
Spring的@Conditional就是这样的"智能管家",根据不同条件来决定要不要创建某些Bean!
🔧 第三幕:Condition接口的核心原理
Condition接口长这样
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata);
}
这个接口超级简单! 就一个方法:
- 返回true → "老板,这个Bean可以要!" ✅
- 返回false → "算了,这个Bean不要了。" ❌
实战案例:自定义一个"操作系统"条件
public class OnWindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 获取当前操作系统
String os = context.getEnvironment()
.getProperty("os.name")
.toLowerCase();
// 判断是否包含"windows"
return os.contains("windows");
}
}
就像门卫大叔检查你的工牌:
- "哦,你是Windows部门的?进!" 🚪✅
- "什么?你是Mac?不好意思,走错门了。" 🚪❌
🎨 第四幕:Spring Boot提供的常用条件注解
Spring Boot贴心地给我们准备了一堆"现成的条件",就像外卖平台的"套餐"🍱:
1. @ConditionalOnClass 📦
作用: 当classpath中存在指定的类时,才创建Bean
@Configuration
@ConditionalOnClass(DataSource.class)
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}
生活比喻:
"只有你家有烤箱🔥,我才教你做蛋糕🎂。没烤箱?那就算了。"
2. @ConditionalOnMissingBean 🕳️
作用: 当容器中不存在指定Bean时,才创建
@Configuration
public class DefaultConfig {
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource defaultDataSource() {
return new SimpleDataSource();
}
}
生活比喻:
"如果你没带雨伞☂️,我就借你一把。你已经有了?那就不用了。"
3. @ConditionalOnProperty 🔧
作用: 根据配置文件中的属性决定是否创建
@Configuration
@ConditionalOnProperty(
name = "app.feature.enabled",
havingValue = "true"
)
public class FeatureConfig {
@Bean
public AwesomeFeature awesomeFeature() {
return new AwesomeFeature();
}
}
在application.properties中:
app.feature.enabled=true
生活比喻:
"你在菜单上勾选了'加辣'🌶️,我才给你加辣椒。没勾?那就不加。"
4. @ConditionalOnBean 🤝
作用: 当容器中存在指定Bean时,才创建
@Configuration
public class ServiceConfig {
@Bean
@ConditionalOnBean(DataSource.class)
public UserService userService(DataSource dataSource) {
return new UserService(dataSource);
}
}
生活比喻:
"只有你买了游戏机🎮,我才给你配手柄🕹️。没游戏机?手柄也没用。"
5. @ConditionalOnExpression 🧮
作用: 根据SpEL表达式的结果决定
@Configuration
@ConditionalOnExpression("${app.mode} == 'dev' and ${app.debug} == true")
public class DebugConfig {
@Bean
public DebugTool debugTool() {
return new DebugTool();
}
}
生活比喻:
"如果你是VIP会员💎 并且 今天是你生日🎂,就送你礼物🎁。"
🎯 第五幕:手把手教你自定义Condition
场景:只在开发环境启用某个功能
Step 1:创建自定义注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnDevEnvironmentCondition.class)
public @interface ConditionalOnDevEnvironment {
}
Step 2:实现Condition接口
public class OnDevEnvironmentCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 获取环境信息
Environment env = context.getEnvironment();
String[] activeProfiles = env.getActiveProfiles();
// 判断是否包含"dev"环境
for (String profile : activeProfiles) {
if ("dev".equalsIgnoreCase(profile)) {
System.out.println("🎉 检测到开发环境,启用调试功能!");
return true;
}
}
System.out.println("❌ 非开发环境,禁用调试功能。");
return false;
}
}
Step 3:使用自定义注解
@Configuration
public class AppConfig {
@Bean
@ConditionalOnDevEnvironment
public DebugController debugController() {
return new DebugController();
}
}
效果:
- 开发环境(dev):✅ 创建DebugController
- 生产环境(prod):❌ 不创建DebugController
🌟 第六幕:高级玩法 - ConfigurationCondition
什么是ConfigurationCondition?
普通的Condition有个小问题:它不关心Bean的创建顺序。
ConfigurationCondition是个升级版,它可以指定什么时候检查条件!
public interface ConfigurationCondition extends Condition {
ConfigurationPhase getConfigurationPhase();
enum ConfigurationPhase {
PARSE_CONFIGURATION, // 解析@Configuration时检查
REGISTER_BEAN // 注册Bean时检查
}
}
实战案例:
public class OnSpecialBeanCondition
implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
// 在注册Bean阶段检查,确保依赖的Bean已经注册
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 检查容器中是否存在某个Bean
return context.getBeanFactory()
.containsBean("specialBean");
}
}
生活比喻:
普通Condition: "我现在就要检查!" 🏃♂️
ConfigurationCondition: "我等一会儿,等其他人都到齐了再检查。" 🧘♂️
🎬 第七幕:ConditionContext提供了哪些信息?
ConditionContext就像一个"信息情报员"🕵️,它能告诉你:
public interface ConditionContext {
// 1. 获取Bean定义注册表(查看已注册的Bean)
BeanDefinitionRegistry getRegistry();
// 2. 获取Bean工厂(查看Bean实例)
ConfigurableListableBeanFactory getBeanFactory();
// 3. 获取环境信息(配置、系统属性)
Environment getEnvironment();
// 4. 获取资源加载器(读取文件)
ResourceLoader getResourceLoader();
// 5. 获取类加载器(检查类是否存在)
ClassLoader getClassLoader();
}
实战案例:根据多个条件综合判断
public class SmartCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 条件1:检查类是否存在
try {
context.getClassLoader()
.loadClass("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.out.println("❌ MySQL驱动不存在");
return false;
}
// 条件2:检查配置属性
String dbUrl = context.getEnvironment()
.getProperty("spring.datasource.url");
if (dbUrl == null || dbUrl.isEmpty()) {
System.out.println("❌ 数据库URL未配置");
return false;
}
// 条件3:检查是否已存在DataSource Bean
if (context.getBeanFactory().containsBean("dataSource")) {
System.out.println("❌ DataSource已存在");
return false;
}
System.out.println("✅ 所有条件满足,可以创建Bean!");
return true;
}
}
🎪 第八幕:Spring Boot自动配置的秘密武器
Spring Boot的自动配置魔法,核心就是@Conditional!
案例:DataSource自动配置
@Configuration
@ConditionalOnClass(DataSource.class) // 1. 有DataSource类
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 2. 用户没自定义DataSource
@ConditionalOnProperty( // 3. 配置文件中指定了URL
name = "spring.datasource.url"
)
public DataSource dataSource(DataSourceProperties properties) {
return DataSourceBuilder
.create()
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword())
.build();
}
}
工作流程图:
检查DataSource类是否存在?
↓ YES
检查用户是否自定义了DataSource?
↓ NO
检查配置文件中是否有spring.datasource.url?
↓ YES
✅ 自动创建DataSource!
生活比喻:
就像智能家居系统:
- 你家有智能灯泡吗?💡 → 有
- 你手动开灯了吗? → 没有
- 现在天黑了吗?🌙 → 是的
✅ 好的,我帮你自动开灯!
🎯 第九幕:实战案例 - 多数据源自动切换
场景描述
假设我们的系统需要:
- 生产环境:使用MySQL
- 测试环境:使用H2内存数据库
- 开发环境:使用本地MySQL
实现步骤
1. 定义条件注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnProductionCondition.class)
public @interface ConditionalOnProduction {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnTestCondition.class)
public @interface ConditionalOnTest {
}
2. 实现Condition
public class OnProductionCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String[] profiles = context.getEnvironment()
.getActiveProfiles();
return Arrays.asList(profiles).contains("prod");
}
}
public class OnTestCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String[] profiles = context.getEnvironment()
.getActiveProfiles();
return Arrays.asList(profiles).contains("test");
}
}
3. 配置不同环境的数据源
@Configuration
public class MultiDataSourceConfig {
@Bean
@ConditionalOnProduction
public DataSource productionDataSource() {
System.out.println("🏭 创建生产环境MySQL数据源");
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://prod-server:3306/db");
dataSource.setUsername("prod_user");
dataSource.setPassword("prod_pass");
return dataSource;
}
@Bean
@ConditionalOnTest
public DataSource testDataSource() {
System.out.println("🧪 创建测试环境H2数据源");
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource defaultDataSource() {
System.out.println("💻 创建默认开发环境数据源");
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}
效果:
java -jar app.jar --spring.profiles.active=prod→ 🏭 生产数据源java -jar app.jar --spring.profiles.active=test→ 🧪 测试数据源java -jar app.jar→ 💻 开发数据源
🔍 第十幕:常见坑点与注意事项
⚠️ 坑点1:条件判断的时机
@Configuration
public class BadExample {
@Bean
public BeanA beanA() {
return new BeanA();
}
@Bean
@ConditionalOnBean(BeanB.class) // ❌ BeanB还没创建呢!
public BeanC beanC() {
return new BeanC();
}
@Bean
public BeanB beanB() {
return new BeanB();
}
}
解决方案: 使用ConfigurationCondition并指定REGISTER_BEAN阶段
⚠️ 坑点2:条件注解的组合
多个条件注解是AND关系:
@Bean
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(name = "db.enabled", havingValue = "true")
public DataSource dataSource() {
// 必须同时满足:
// 1. DataSource类存在
// 2. db.enabled=true
return new HikariDataSource();
}
⚠️ 坑点3:调试条件注解
启用自动配置报告:
# application.properties
debug=true
或者启动时加参数:
java -jar app.jar --debug
输出示例:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required class 'javax.sql.DataSource' (OnClassCondition)
- @ConditionalOnProperty (spring.datasource.url) matched (OnPropertyCondition)
Negative matches:
-----------------
RedisAutoConfiguration did not match:
- @ConditionalOnClass did not find required class 'org.springframework.data.redis.core.RedisOperations' (OnClassCondition)
🎓 第十一幕:最佳实践
✅ 1. 语义化的条件注解
不好的方式:
@Conditional(MyComplexCondition.class)
好的方式:
@ConditionalOnDevEnvironment
@ConditionalOnFeatureEnabled("awesome-feature")
✅ 2. 条件类要易于测试
public class OnDevEnvironmentCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
return isDevEnvironment(context.getEnvironment());
}
// 提取为独立方法,便于单元测试
protected boolean isDevEnvironment(Environment env) {
String[] profiles = env.getActiveProfiles();
return Arrays.asList(profiles).contains("dev");
}
}
✅ 3. 提供清晰的日志
public class SmartCondition implements Condition {
private static final Logger log = LoggerFactory.getLogger(SmartCondition.class);
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
boolean result = doCheck(context);
if (result) {
log.info("✅ 条件满足:{}", getDescription());
} else {
log.info("❌ 条件不满足:{}", getReason());
}
return result;
}
}
🎉 总结:@Conditional的威力
| 特性 | 说明 | 表情 |
|---|---|---|
| 灵活性 | 根据任意条件决定Bean是否创建 | 🦎 |
| 可组合 | 多个条件注解可以组合使用 | 🧩 |
| 强大 | Spring Boot自动配置的核心 | 💪 |
| 易扩展 | 可以自定义各种条件 | 🔧 |
| 智能 | 让应用自动适应不同环境 | 🧠 |
🚀 课后作业
- 初级: 创建一个
@ConditionalOnWeekend注解,只在周末创建某个Bean - 中级: 实现一个根据JDK版本决定Bean创建的条件注解
- 高级: 设计一个多条件组合的注解,支持OR和AND逻辑
📚 参考资料
- Spring Framework官方文档
- Spring Boot自动配置源码
- 《Spring揭秘》
最后的彩蛋: 🎁
Spring的@Conditional就像生活中的"智能助手",它让你的应用变得超级聪明!
记住这句话:
"不是所有的Bean都要创建,而是根据条件智能地创建!" 💡
关注我,下期更精彩! 🌟
用代码改变世界,用幽默改变编程! 😎
#Spring #条件注解 #自动配置 #最佳实践