一、核心概念
Spring Boot 的条件注解(Conditional Annotations)是其“自动配置”(Auto-configuration)机制的核心。它们允许开发者根据特定条件(如类路径中是否存在某个类、某个 Bean 是否已存在等)来决定是否创建某个 Bean 或加载某个配置类。
核心思想: “约定优于配置” + “按需加载”。Spring Boot 自动配置不是一股脑儿地加载所有组件,而是根据应用的依赖和环境智能地决定哪些配置需要生效。
主要条件注解
@ConditionalOnClass
: 当类路径中存在指定的一个或多个类时,条件成立。@ConditionalOnMissingClass
: 当类路径中不存在指定的一个或多个类时,条件成立。@ConditionalOnBean
: 当容器中存在指定的一个或多个 Bean 时,条件成立。@ConditionalOnMissingBean
: 当容器中不存在指定的 Bean 时,条件成立。@ConditionalOnProperty
: 当指定的配置属性具有特定值时,条件成立。@ConditionalOnResource
: 当类路径中存在指定的资源文件时,条件成立。@ConditionalOnWebApplication
: 当应用是 Web 应用时,条件成立。@ConditionalOnNotWebApplication
: 当应用不是 Web 应用时,条件成立。@ConditionalOnExpression
: 当 SpEL 表达式结果为true
时,条件成立。@Conditional
: 通用注解,可以指定一个实现了Condition
接口的类,进行更复杂的条件判断。
二、操作步骤(非常详细)
步骤 1:理解 @Conditional
接口(底层原理)
所有条件注解最终都依赖于 @Conditional
。它接受一个或多个 Condition
接口的实现。
Condition
接口:public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
matches
方法返回true
表示条件满足,对应的配置类或 Bean 将被加载/创建。ConditionContext
: 提供了访问BeanFactory
,Environment
,ResourceLoader
,ClassLoader
等上下文信息。AnnotatedTypeMetadata
: 提供了访问注解元数据(如注解的属性值)的能力。
步骤 2:使用 @ConditionalOnClass
(最常用)
场景: 只有在类路径中存在 DataSource
类时,才加载 DataSourceAutoConfiguration
。
创建配置类(或自动配置类):
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import javax.sql.DataSource; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) // 标记为配置类 @ConditionalOnClass(DataSource.class) // 核心条件注解 public class MyDataSourceConfiguration { // 这个配置类中的所有 @Bean 方法,只有在 DataSource 类存在时才会被处理 // ... }
@Configuration(proxyBeanMethods = false)
:proxyBeanMethods=false
是 Spring Boot 2.2+ 推荐的,用于提升性能,表示不代理 Bean 方法(即不处理@Bean
方法间的调用),因为自动配置通常不需要。@ConditionalOnClass(DataSource.class)
: 指定条件。DataSource
是javax.sql.DataSource
。
指定多个类:
@ConditionalOnClass({DataSource.class, JdbcTemplate.class}) public class MyDataConfiguration { // 当 DataSource 和 JdbcTemplate 都存在时才生效 }
使用
name
属性(避免编译时依赖):@ConditionalOnClass(name = "com.example.MyService") public class MyServiceConfiguration { // 当类路径中存在名为 "com.example.MyService" 的类时生效 // 优点:不需要在编译时将 MyService 加入依赖,只在运行时检查。 }
- 重要区别: 使用
value
(如DataSource.class
) 需要在编译时有该类的依赖。使用name
属性则不需要,只在运行时通过类加载器检查。
- 重要区别: 使用
步骤 3:使用 @ConditionalOnMissingBean
(防止 Bean 冲突)
场景: 只有在用户没有自己定义 MyService
Bean 时,才创建一个默认的 MyService
Bean。
在自动配置类中定义 Bean:
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MyService.class) // 确保 MyService 类存在 public class MyServiceAutoConfiguration { @Bean @ConditionalOnMissingBean // 核心条件 public MyService myService() { return new DefaultMyService(); // 返回一个默认实现 } }
@ConditionalOnMissingBean
放在@Bean
方法上,作用于该方法创建的 Bean。- Spring 容器在创建
myService
Bean 之前,会检查当前容器中是否已经存在类型为MyService
的 Bean。 - 如果不存在,则创建
DefaultMyService
实例。 - 如果存在(例如,用户在自己的
@Configuration
类中定义了@Bean MyService myCustomService()
),则跳过myService()
方法,不创建默认 Bean。
更精确的匹配(指定名称、类型、搜索范围):
@Bean @ConditionalOnMissingBean( value = MyService.class, // 指定 Bean 类型 name = "customMyService", // 指定 Bean 名称(可选) search = SearchStrategy.CURRENT // 搜索范围:仅在当前配置类中搜索(默认是 ALL) ) public MyService myService() { return new DefaultMyService(); }
value
: Bean 的类型。name
: Bean 的名称(@Bean
注解的name
属性或方法名)。parameterizedContainer
: 用于泛型容器(如List
,Map
)。search
: 搜索策略 (ALL
,CURRENT
,ANCESTORS
)。
步骤 4:组合使用多个条件注解
场景: 只有在类路径有 RedisConnectionFactory
且容器中没有 RedisTemplate
Bean 时,才创建默认的 RedisTemplate
。
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class) // 类路径有 Redis 连接工厂
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 容器中没有 RedisTemplate
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<?, ?> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// ... 其他配置
return template;
}
}
- 这里
@ConditionalOnClass
作用于整个配置类,@ConditionalOnMissingBean
作用于具体的@Bean
方法。 - 条件是“与”(AND)关系:两个条件都必须满足。
步骤 5:使用 @ConditionalOnProperty
场景: 只有当配置文件中 app.feature.enabled=true
时,才启用某个功能。
在
application.properties
或application.yml
中设置:# application.properties app.feature.enabled=true
# application.yml app: feature: enabled: true
在配置类或 Bean 方法上使用:
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) public class FeatureConfiguration { @Bean @ConditionalOnProperty( name = "app.feature.enabled", // 配置属性的名称 havingValue = "true", // 期望的值(可选,默认为 "true") matchIfMissing = false // 如果属性不存在,条件是否成立?(可选,默认 false) ) public FeatureService featureService() { return new FeatureServiceImpl(); } }
name
: 必需,属性名。havingValue
: 可选,期望的值。如果省略,只要属性存在且值不为false
(忽略大小写) 就成立。matchIfMissing
: 可选,如果属性在配置文件中不存在,条件是否成立?默认false
。
三、常见错误
@ConditionalOnMissingBean
作用范围错误:- 错误: 将
@ConditionalOnMissingBean
放在配置类上,期望控制类中所有 Bean。 - 正确:
@ConditionalOnMissingBean
必须放在@Bean
方法上,它只对该方法创建的 Bean 生效。要控制整个配置类,应使用@ConditionalOnBean
或@ConditionalOnMissingBean
的value
/name
属性结合@Configuration
,但这不常见且复杂,通常用其他条件注解组合。
- 错误: 将
@ConditionalOnClass
的编译依赖问题:- 错误: 在自动配置模块中直接使用
@ConditionalOnClass(SomeExternalClass.class)
,但SomeExternalClass
来自一个可选依赖(Optional Dependency),导致编译失败。 - 正确: 使用
@ConditionalOnClass(name = "com.external.SomeExternalClass")
避免编译时依赖。或者,将可选依赖声明为provided
或optional
。
- 错误: 在自动配置模块中直接使用
条件检查时机与 Bean 创建顺序:
- 错误: 认为
@ConditionalOnBean
能检查到所有 Bean,包括那些在后续配置中定义的 Bean。 - 正确: 条件检查发生在配置类处理阶段,早于大部分 Bean 的实际创建。
@ConditionalOnBean
通常只能检测到之前已经注册的 Bean(如通过@Import
或更早的自动配置)。@ConditionalOnMissingBean
更安全,因为它通常用于提供默认实现。
- 错误: 认为
@ConditionalOnProperty
的havingValue
匹配:- 错误: 设置
havingValue = "on"
,但在配置中写app.feature.enabled=true
,导致不匹配。 - 正确: 确保
havingValue
的值与配置文件中的值完全匹配(字符串比较),或省略havingValue
让其默认匹配非false
值。
- 错误: 设置
忽略
proxyBeanMethods = false
:- 错误: 在自动配置类上使用
@Configuration
而不设置proxyBeanMethods = false
。 - 正确: 除非配置类内部的
@Bean
方法有相互调用且需要代理(如@Transactional
),否则应设置proxyBeanMethods = false
以提升启动性能。
- 错误: 在自动配置类上使用
四、注意事项
- 顺序很重要: Spring Boot 自动配置的加载顺序由
@AutoConfigureOrder
,@AutoConfigureBefore
,@AutoConfigureAfter
控制。确保你的条件逻辑不依赖于未加载的配置。 @ConditionalOnMissingBean
的宽松性: 它检查的是类型和/或名称。即使用户定义的 Bean 是子类或不同实现,只要类型匹配,它就会阻止默认 Bean 的创建。使用search
属性可以限制搜索范围。@ConditionalOnClass
的局限性: 它只检查类路径,不保证类能被正确实例化或有正确的依赖。它只是一个初步的“门卫”。- 避免过度使用
@ConditionalOnExpression
: SpEL 表达式可能使配置变得复杂且难以调试。优先使用专门的条件注解。 - 测试: 为使用了条件注解的配置编写测试,模拟不同条件(如类存在/不存在,属性设置/未设置)来验证行为。
五、使用技巧
- 组合条件: 灵活组合多个
@Conditional
注解(或其衍生注解)来实现复杂逻辑。它们是“与”关系。 - 利用
name
属性解耦: 在自动配置模块中,优先使用@ConditionalOnClass(name = "...")
和@ConditionalOnMissingClass(name = "...")
来避免对可选库的编译时硬依赖。 - 提供合理的默认值:
@ConditionalOnProperty
的matchIfMissing = true
可以用于启用某些默认功能,除非用户明确禁用。 - 日志记录: 在复杂的
Condition
实现中(自定义@Conditional
),添加日志有助于调试条件判断过程。 - 利用 IDE 提示: 现代 IDE (如 IntelliJ IDEA) 通常能识别 Spring Boot 条件注解,并在代码中给出提示或警告。
六、最佳实践
- 优先使用
@ConditionalOnMissingBean
: 在提供自动配置时,这是防止与用户自定义 Bean 冲突的黄金标准。 - 明确的条件: 使用最具体的条件注解。例如,用
@ConditionalOnWebApplication
而不是@ConditionalOnClass(name = "org.springframework.web.context.support.GenericWebApplicationContext")
。 - 模块化配置: 将相关的自动配置放在独立的配置类中,并用适当的条件注解标记。
- 文档化: 在自动配置类或文档中说明其生效的条件。
- 遵循 Spring Boot 命名约定: 自动配置类通常命名为
*AutoConfiguration
。 proxyBeanMethods = false
: 对于不涉及内部@Bean
方法调用代理的配置类,务必设置此属性以优化性能。
七、性能优化
proxyBeanMethods = false
: 这是最直接的性能优化。它避免了为配置类创建 CGLIB 代理,减少了启动时间和内存占用。这是 Spring Boot 自动配置的最佳实践。- 尽早排除: 使用
@ConditionalOnClass
,@ConditionalOnProperty
等注解在配置类加载的早期就排除掉不需要的配置,避免解析和处理其中的@Bean
方法。 - 避免复杂的
Condition
实现: 自定义Condition
的matches
方法应尽量轻量,避免进行耗时的 I/O 操作或复杂计算。 - 合理使用
@ConditionalOnBean
: 尽量避免依赖@ConditionalOnBean
来检查在后续阶段才会注册的 Bean,这可能导致不必要的配置类被加载和检查。优先使用@ConditionalOnMissingBean
或其他前置条件。 - 启用启动分析: 使用 Spring Boot Actuator 的
/startup
端点(如果可用)或启动日志来分析哪些自动配置被应用/排除,找出潜在的优化点。
总结: Spring Boot 的条件注解是构建灵活、智能自动配置的基石。熟练掌握 @ConditionalOnClass
和 @ConditionalOnMissingBean
是关键。遵循最佳实践,特别是 proxyBeanMethods = false
,能有效提升应用性能。通过组合使用这些注解,可以创建出既强大又健壮的配置。