2-4 条件注解
概念解析
@Conditional 家族
| 注解 | 说明 |
|---|---|
| @Conditional | 基础条件注解 |
| @ConditionalOnClass | 类路径包含指定类时生效 |
| @ConditionalOnMissingClass | 类路径不包含指定类时生效 |
| @ConditionalOnBean | 容器中存在指定 Bean 时生效 |
| @ConditionalOnMissingBean | 容器中不存在指定 Bean 时生效 |
| @ConditionalOnProperty | 配置属性满足条件时生效 |
| @ConditionalOnResource | 资源文件存在时生效 |
| @ConditionalOnWebApplication | 是 Web 应用时生效 |
| @ConditionalOnNotWebApplication | 非 Web 应用时生效 |
条件组合
| 注解 | 说明 |
|---|---|
| @ConditionalOnBean + @ConditionalOnProperty | 组合条件 |
代码示例
1. 基础 @Conditional
// 自定义条件:只有 Linux 环境才创建
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment()
.getProperty("os.name")
.contains("Linux");
}
}
// 使用自定义条件
@Configuration
public class ConditionalConfig {
@Bean
@Conditional(LinuxCondition.class)
public Runnable linuxTask() {
return () -> System.out.println("Running on Linux");
}
}
2. 常用条件注解
@Configuration
public class AutoConfiguration {
// 条件1:类路径中存在某个类
@Bean
@ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
// 条件2:类路径中不存在某个类
@Bean
@ConditionalOnMissingClass("com.google.gson.Gson")
public StringHttpMessageConverter stringConverter() {
return new StringHttpMessageConverter();
}
// 条件3:容器中存在某个 Bean
@Bean
@ConditionalOnBean(DataSource.class)
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// 条件4:容器中不存在某个 Bean
@Bean
@ConditionalOnMissingBean(CacheManager.class)
public SimpleCacheManager simpleCacheManager() {
return new SimpleCacheManager();
}
// 条件5:配置属性满足条件
@Bean
@ConditionalOnProperty(
prefix = "app.cache",
name = "enabled",
havingValue = "true",
matchIfMissing = true // 缺失时也算 true
)
public CacheService cacheService() {
return new RedisCacheService();
}
// 条件6:是 Web 应用
@Bean
@ConditionalOnWebApplication(type = Type.SERVLET)
public WebInterceptor webInterceptor() {
return new WebInterceptor();
}
}
3. 条件注解组合
@Configuration
@ConditionalOnClass(RedisOperations.class)
@ConditionalOnProperty(
prefix = "spring.data.redis",
name = "enabled",
havingValue = "true",
matchIfMissing = false
)
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
}
4. 自定义条件注解
// 定义组合注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DatabaseTypeCondition.class)
@Documented
public @interface ConditionalOnDatabase {
DatabaseType value();
enum DatabaseType {
MYSQL, POSTGRESQL, ORACLE, SQLSERVER
}
}
// 实现条件逻辑
public class DatabaseTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata
.getAnnotationAttributes(ConditionalOnDatabase.class.getName());
DatabaseType requiredType = (DatabaseType) attributes.get("value");
String url = context.getEnvironment()
.getProperty("spring.datasource.url");
if (requiredType == DatabaseType.MYSQL) {
return url != null && url.contains("mysql");
}
return false;
}
}
// 使用
@Configuration
@ConditionalOnDatabase(DatabaseType.MYSQL)
public class MySqlConfig {
@Bean
public MySqlService mySqlService() {
return new MySqlService();
}
}
5. Spring Boot 自动配置中的条件
// Spring Boot 内部大量使用条件注解
@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyBatisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(SqlSessionFactory.class)
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) {
// ...
}
@Bean
@ConditionalOnProperty(
name = "mybatis.mapper-locations",
matchIfMissing = true
)
public MapperScannerConfigurer mapperScannerConfigurer() {
// ...
}
}
源码解读
@Conditional 解析流程
@Configuration 类加载
↓
ConfigurationClassParser.parse()
↓
处理 @Conditional 注解
↓
ConditionEvaluator.shouldSkip()
↓
遍历所有 Condition.matches()
↓
全部返回 true → 配置类生效
任一返回 false → 配置类跳过
Condition 接口实现
public interface Condition {
// context: 条件上下文(Environment、BeanFactory 等)
// metadata: 注解元数据
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
public class ConditionContextImpl implements ConditionContext {
private final Environment environment;
private final BeanDefinitionRegistry registry;
private final ResourceLoader resourceLoader;
private final ClassLoader classLoader;
// 提供各种获取信息的方法
}
常见坑点
⚠️ 坑 1:条件顺序导致不生效
问题:条件 Bean 之间有依赖但顺序不对
// ❌ orderService 可能还不存在
@Bean
@ConditionalOnBean(OrderService.class) // 可能找不到
public OrderController orderController() {
return new OrderController(orderService);
}
// ✅ 应该声明依赖关系
@Configuration
@AutoConfigureAfter(OtherAutoConfiguration.class)
public class MyAutoConfiguration { }
⚠️ 坑 2:@ConditionalOnProperty 配置错误
// ❌ 常见错误:前缀和属性名写错
@ConditionalOnProperty(prefix = "app", name = "cache.enable") // 实际是 cache.enabled
// ✅ 检查配置文件中的实际名称
// application.yml 中:
// app:
// cache:
// enabled: true
@ConditionalOnProperty(
prefix = "app.cache",
name = "enabled",
havingValue = "true"
)
⚠️ 坑 3:条件类的类加载问题
问题:ClassNotFoundException
// ⚠️ 直接使用类可能触发加载
@ConditionalOnClass(Gson.class)
public class GsonConfig {
// 如果 Gson 不在 classpath,配置类加载时就报错
}
// ✅ 使用类名字符串
@ConditionalOnClass(name = "com.google.gson.Gson")
public class GsonConfig { }
面试题
Q1:@Conditional 的执行顺序?
参考答案:
- 解析顺序:按注解在类上的声明顺序执行
- 短路机制:遇到第一个返回 false 的条件立即跳过
- 设计原则:更精确的条件放前面
@Configuration
@Conditional({ConditionA.class, ConditionB.class})
public class MyConfig {
// 先检查 A,再检查 B
// A && B 都为 true 才生效
}
Q2:Spring Boot 如何实现自动配置的条件化?
参考答案:
Spring Boot 的自动配置大量使用 @ConditionalOn* 系列注解:
// 典型的自动配置类
@Configuration
@ConditionalOnClass(UserService.class) // 类存在
@ConditionalOnProperty(prefix = "app", // 配置存在
name = "user.enabled", havingValue = "true")
@ConditionalOnMissingBean(UserService.class) // Bean 不存在
@EnableConfigurationProperties(UserProperties.class)
public class UserAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public UserService userService() {
return new UserServiceImpl();
}
}
Q3:如何排查条件不生效?
参考答案:
-
启用自动配置报告:
debug: true -
查看日志:搜索 "CONDITIONS EVALUATION REPORT"
-
常用原因:
- @ConditionalOnBean 找不到依赖的 Bean
- @ConditionalOnClass 类不存在
- @ConditionalOnProperty 配置值不匹配
- @ConditionalOnMissingBean 已存在该 Bean
-
强制包含:
@SpringBootApplication(exclude = {UserAutoConfiguration.class})