@Conditional
系列注解是 Spring 框架中实现条件化 Bean 注册和配置类生效的核心工具,广泛用于 Spring Boot 的自动配置机制中。它们通过判断特定条件(如类路径存在性、Bean 是否已存在、环境属性值等)决定是否加载某个 Bean 或配置类,从而避免冗余配置并提升应用的灵活性。以下从注解分类、源码解析、核心功能、使用场景及源码实现细节展开详细说明。
一、@Conditional
系列注解分类
Spring 提供了多个 @Conditional
系列注解,按功能可分为以下几类:
注解 | 作用 | 典型场景 |
---|---|---|
@ConditionalOnClass | 当类路径中存在指定类时生效 | 数据库驱动存在时自动配置 DataSource 。 |
@ConditionalOnMissingBean | 当容器中不存在指定类型的 Bean 时生效 | 避免覆盖用户自定义的 DataSource Bean。 |
@ConditionalOnProperty | 当配置文件中指定属性满足条件时生效 | 根据 application.properties 中的 env=prod 启用生产环境配置。 |
@ConditionalOnWebApplication | 当应用是 Web 应用时生效 | 仅在 Web 环境中配置 DispatcherServlet 。 |
@ConditionalOnNotWebApplication | 当应用不是 Web 应用时生效 | 仅在非 Web 环境中配置 CommandLineRunner 。 |
@ConditionalOnResource | 当类路径中存在指定资源文件时生效 | 仅当 META-INF/beans.xml 存在时加载传统 XML 配置。 |
@ConditionalOnExpression | 当 SpEL 表达式计算结果为 true 时生效 | 自定义复杂条件(如 ${my.property} > 100 )。 |
二、核心注解详解与源码解析
1. @ConditionalOnClass
:类路径存在性检查
@ConditionalOnClass
用于判断类路径中是否存在指定的类(通过类加载器加载),若存在则生效。
源码定义(Spring Framework 6.1):
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/**
* 需要检查的类(支持多个)
*/
Class<?>[] value() default {};
/**
* 类名的字符串形式(支持通配符,如 "com.example.*Service")
*/
String[] names() default {};
}
关键逻辑:
通过 OnClassCondition
类实现条件评估,核心方法是 matches
,用于检查类路径中是否存在目标类:
// OnClassCondition 核心方法(简化)
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取 @ConditionalOnClass 注解的 value 或 names 属性
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
Class<?>[] classes = (Class<?>[]) attributes.get("value");
String[] classNames = (String[]) attributes.get("names");
// 检查类路径中是否存在任意一个类
for (Class<?> clazz : classes) {
if (ClassUtils.isPresent(clazz.getName(), context.getClassLoader())) {
return true;
}
}
for (String className : classNames) {
if (ClassUtils.isPresent(className, context.getClassLoader())) {
return true;
}
}
return false;
}
使用示例:
仅在类路径存在 DataSource
时生效(常见于数据库自动配置):
@Configuration
@ConditionalOnClass(DataSource.class) // 仅当类路径有 DataSource 时生效
public class DataSourceAutoConfiguration {
// 自动配置 DataSource...
}
2. @ConditionalOnMissingBean
:Bean 不存在性检查
@ConditionalOnMissingBean
用于判断 Spring 容器中是否不存在指定类型的 Bean(或其别名),若不存在则生效。常用于覆盖默认自动配置,允许用户自定义 Bean。
源码定义:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnMissingBeanCondition.class)
public @interface ConditionalOnMissingBean {
/**
* 需要检查的 Bean 类型(支持多个)
*/
Class<?>[] value() default {};
/**
* Bean 的名称(支持 SpEL 表达式)
*/
String[] name() default {};
/**
* 是否考虑父容器中的 Bean(默认 false)
*/
boolean considerParent() default false;
}
关键逻辑:
通过 OnMissingBeanCondition
评估容器中是否存在目标 Bean。若用户已定义同名或同类型的 Bean,则自动配置类不会生效。
使用示例:
用户自定义 DataSource
时,自动跳过 Spring Boot 默认的数据源配置:
@Configuration
public class MyDataSourceConfig {
@Bean
public DataSource myDataSource() {
// 用户自定义数据源
}
}
// Spring Boot 默认数据源配置(仅当用户未定义 DataSource 时生效)
@Configuration
@ConditionalOnMissingBean(DataSource.class) // 用户无 DataSource 时生效
public class DataSourceAutoConfiguration {
@Bean
public DataSource defaultDataSource() {
// 默认数据源配置
}
}
3. @ConditionalOnProperty
:配置属性检查
@ConditionalOnProperty
用于判断配置文件中指定属性的值是否满足条件(如等于某个值、存在等),若满足则生效。常用于根据环境动态启用功能。
源码定义:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
/**
* 配置属性的名称(支持 SpEL 表达式)
*/
String[] name();
/**
* 期望的属性值(可选,若不指定则仅检查属性存在)
*/
String[] havingValue() default {};
/**
* 是否忽略属性不存在的情况(默认 false,即属性不存在时条件不满足)
*/
boolean matchIfMissing() default false;
}
关键逻辑:
通过 OnPropertyCondition
读取配置文件中的属性值,与 havingValue
比较(或仅检查存在性)。
使用示例:
仅当 application.properties
中 env=prod
时启用生产环境监控:
@Configuration
@ConditionalOnProperty(
name = "env",
havingValue = "prod",
matchIfMissing = false // 属性不存在时不生效
)
public class ProductionMonitorAutoConfiguration {
@Bean
public MonitorService monitorService() {
return new ProductionMonitor();
}
}
4. @ConditionalOnWebApplication
:Web 应用检查
@ConditionalOnWebApplication
用于判断当前应用是否为 Web 应用(通过 ServletContext
是否存在),若为 Web 应用则生效。常用于区分 Web 和非 Web 环境的配置。
源码定义:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
/**
* 是否严格检查(默认 true,要求是完整的 Web 应用)
*/
boolean strict() default true;
}
关键逻辑:
通过 OnWebApplicationCondition
检查 ServletContext
是否存在(strict=true
时要求是完整的 Web 应用,strict=false
时允许 Servlet 环境)。
使用示例:
仅在 Web 应用中配置 DispatcherServlet
:
@Configuration
@ConditionalOnWebApplication // 仅当是 Web 应用时生效
public class WebMvcAutoConfiguration {
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
}
5. @ConditionalOnExpression
:SpEL 表达式检查
@ConditionalOnExpression
允许使用 SpEL(Spring 表达式语言)自定义复杂条件,若表达式结果为 true
则生效。适用于需要动态计算的场景。
源码定义:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnExpressionCondition.class)
public @interface ConditionalOnExpression {
/**
* SpEL 表达式(结果为 true 时生效)
*/
String value();
}
关键逻辑:
通过 OnExpressionCondition
解析并计算 SpEL 表达式,判断结果是否为 true
。
使用示例:
仅当 my.property
大于 100 时启用高级功能:
@Configuration
@ConditionalOnExpression("${my.property} > 100") // SpEL 表达式
public class AdvancedFeatureAutoConfiguration {
@Bean
public AdvancedService advancedService() {
return new AdvancedService();
}
}
三、@Conditional
系列注解的协同工作流程
在 Spring Boot 自动配置中,@Conditional
系列注解通常与 @EnableAutoConfiguration
、AutoConfigurationImportSelector
协同工作,流程如下:
- 自动配置类加载:
AutoConfigurationImportSelector
从META-INF/spring.factories
中加载所有EnableAutoConfiguration
类型的自动配置类。 - 条件评估:对每个自动配置类,使用
ConditionEvaluator
评估其上的@Conditional
注解(如@ConditionalOnClass
、@ConditionalOnMissingBean
)。 - 生效类导入:仅条件满足的自动配置类会被导入到 Spring 容器中,生成
BeanDefinition
并注册。
四、典型使用场景与最佳实践
1. 避免重复 Bean 注册
通过 @ConditionalOnMissingBean
允许用户自定义 Bean,覆盖默认自动配置:
@Configuration
public class UserConfig {
@Bean
public UserDetailsService userDetailsService() {
// 用户自定义的用户详情服务
}
}
// Spring Boot 默认的用户详情服务(仅当用户未定义时生效)
@Configuration
@ConditionalOnMissingBean(UserDetailsService.class)
public class DefaultUserDetailsServiceConfig {
@Bean
public UserDetailsService defaultUserDetailsService() {
return new InMemoryUserDetailsManager();
}
}
2. 环境差异化配置
通过 @ConditionalOnProperty
根据环境启用不同功能:
@Configuration
public class LogConfig {
@Bean
@ConditionalOnProperty(name = "log.level", havingValue = "DEBUG")
public Logger debugLogger() {
return LoggerFactory.getLogger("DEBUG_LOGGER");
}
@Bean
@ConditionalOnProperty(name = "log.level", havingValue = "INFO", matchIfMissing = true)
public Logger infoLogger() {
return LoggerFactory.getLogger("INFO_LOGGER");
}
}
3. 类路径依赖检查
通过 @ConditionalOnClass
确保依赖存在时才配置:
@Configuration
@ConditionalOnClass(RedisTemplate.class) // 仅当 RedisTemplate 存在时生效
public class RedisCacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.create(connectionFactory);
}
}
4. Web 环境专属配置
通过 @ConditionalOnWebApplication
区分 Web 和非 Web 环境:
@Configuration
public class WebConfig {
@Bean
@ConditionalOnWebApplication // 仅 Web 环境生效
public WebMvcConfigurer webMvcConfigurer() {
return new MyWebMvcConfigurer();
}
}
@Configuration
public class NonWebConfig {
@Bean
@ConditionalOnNotWebApplication // 仅非 Web 环境生效
public CommandLineRunner commandLineRunner() {
return args -> System.out.println("非 Web 环境启动");
}
}
五、源码实现细节与注意事项
1. 条件评估的顺序
Spring 会按以下顺序评估 @Conditional
注解的条件:
@ConditionalOnClass
@ConditionalOnMissingBean
@ConditionalOnProperty
@ConditionalOnWebApplication
@ConditionalOnExpression
2. 类加载器的作用
@ConditionalOnClass
和 @ConditionalOnMissingBean
等注解的条件评估依赖 ClassLoader
,需确保类加载器能正确加载目标类(如自定义类加载器可能导致条件判断失败)。
3. SpEL 表达式的安全性
@ConditionalOnExpression
使用 SpEL 表达式时,需避免使用危险操作(如 T(java.lang.Runtime).getRuntime().exec(...)
),防止代码注入攻击。
4. 条件缓存的优化
Spring 会对条件评估结果进行缓存,避免重复计算。若条件依赖的属性或类动态变化(如通过 @RefreshScope
刷新配置),需手动清除缓存。
六、总结
@Conditional
系列注解是 Spring Boot 自动配置的“开关”,通过灵活的条件判断控制 Bean 和配置类的生效逻辑。掌握这些注解的使用(如 @ConditionalOnClass
、@ConditionalOnMissingBean
、@ConditionalOnProperty
),可以帮助开发者:
- 编写更健壮的自动配置类,避免冗余。
- 实现环境差异化配置,提升应用的可维护性。
- 精准控制 Bean 的注册,避免冲突。
理解其源码和评估逻辑,有助于解决自动配置不生效、Bean 重复注册等常见问题,是 Spring Boot 开发的高级必备技能。