Spring Boot的条件注解基于Spring Framework的@Conditional注解扩展而来。其核心思想是:在特定条件满足时才创建特定的Bean或加载特定的配置。这种机制让Spring Boot能够:
所有的条件注解都基于@Conditional实现:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
每个具体的条件注解都需要实现Condition接口:
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
@ConditionalOnClass
:当classpath中存在指定类时条件成立@ConditionalOnMissingClass
:当classpath中不存在指定类时条件成立@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnMissingClass {
String[] value() default {}; // 需要检查不存在的类
}
实现原理:
class OnClassCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
// 获取注解中指定的所有类
List<String> classNames = getCandidateClasses(metadata);
for (String className : classNames) {
try {
// 尝试加载类
Class.forName(className, false, classLoader);
} catch (ClassNotFoundException e) {
return ConditionOutcome.noMatch("Class " + className + " not found");
}
}
return ConditionOutcome.match();
}
}
@ConditionalOnBean
:当容器中存在指定Bean时条件成立@ConditionalOnMissingBean
:当容器中不存在指定Bean时条件成立@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
Class<?>[] value() default {}; // Bean的类型
Class<?>[] type() default {}; // Bean的类型(别名)
String[] name() default {}; // Bean的名称
String[] annotation() default {}; // Bean应该具有的注解
SearchStrategy search() default SearchStrategy.ALL; // 搜索策略
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
Class<?>[] value() default {}; // 检查不存在的Bean类型
String[] name() default {}; // 检查不存在的Bean名称
Class<?>[] type() default {}; // 检查不存在的Bean类型(别名)
String[] annotation() default {}; // 检查不具有的注解
SearchStrategy search() default SearchStrategy.ALL; // 搜索策略
}
实现示例:
public class OnBeanCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 获取bean工厂
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 检查指定的bean是否存在
String[] beanNames = metadata.getAnnotationAttributes(
ConditionalOnMissingBean.class.getName()).getStringArray("name");
for (String beanName : beanNames) {
if (beanFactory.containsBean(beanName)) {
return ConditionOutcome.noMatch("Bean " + beanName + " found");
}
}
return ConditionOutcome.match();
}
}
@ConditionalOnProperty
:基于配置属性值的条件判断@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
String[] value() default {}; // 属性名
String prefix() default ""; // 属性前缀
String[] name() default {}; // 属性名(与prefix组合)
String havingValue() default ""; // 期望的属性值
boolean matchIfMissing() default false; // 属性不存在时是否匹配
}
实现示例:
// Property条件的实现示例
class OnPropertyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(
ConditionalOnProperty.class.getName());
String prefix = attributes.get("prefix").toString();
String[] names = (String[]) attributes.get("name");
String havingValue = attributes.get("havingValue").toString();
for (String name : names) {
String key = prefix.isEmpty() ? name : prefix + "." + name;
String value = context.getEnvironment().getProperty(key);
if (value == null || !value.equals(havingValue)) {
return ConditionOutcome.noMatch("Property " + key +
" not found or not equal to " + havingValue);
}
}
return ConditionOutcome.match();
}
}
@ConditionalOnResource
:当类路径下存在指定资源时条件成立@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnResourceCondition.class)
public @interface ConditionalOnResource {
String[] resources() default {}; // 需要存在的资源路径
}
实现示例:
class OnResourceCondition extends SpringBootCondition {
private final ResourceLoader defaultResourceLoader = new DefaultResourceLoader();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(
ConditionalOnResource.class.getName());
String[] resources = (String[]) attributes.get("resources");
ResourceLoader loader = context.getResourceLoader() != null ?
context.getResourceLoader() : defaultResourceLoader;
List<String> missing = new ArrayList<>();
for (String resource : resources) {
Resource resourceInstance = loader.getResource(resource);
if (!resourceInstance.exists()) {
missing.add(resource);
}
}
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch("Resources not found: " +
String.join(", ", missing));
}
return ConditionOutcome.match();
}
}
使用示例
@Configuration
@ConditionalOnResource(resources = {
"classpath:config/app-config.properties",
"file:/opt/app/external-config.yaml"
})
public class ResourceDependentConfig {
// 只有当指定的资源文件存在时,此配置类才会生效
@Bean
public MyService myService() {
return new MyService();
}
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
Type type() default Type.ANY; // Web应用类型(SERVLET, REACTIVE, ANY)
}
实现示例:
class OnWebApplicationCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(
ConditionalOnWebApplication.class.getName());
Type type = (Type) attributes.get("type");
// 检查是否为Web环境
boolean isServletWeb = isServletWebApplication(context);
boolean isReactiveWeb = isReactiveWebApplication(context);
switch (type) {
case SERVLET:
return new ConditionOutcome(isServletWeb,
"Found servlet web application");
case REACTIVE:
return new ConditionOutcome(isReactiveWeb,
"Found reactive web application");
case ANY:
return new ConditionOutcome(isServletWeb || isReactiveWeb,
"Found web application");
default:
return ConditionOutcome.noMatch("Unknown web application type");
}
}
private boolean isServletWebApplication(ConditionContext context) {
// 检查是否存在Servlet相关类
return ClassUtils.isPresent("javax.servlet.Servlet", context.getClassLoader()) &&
ClassUtils.isPresent("org.springframework.web.context.support.GenericWebApplicationContext",
context.getClassLoader());
}
private boolean isReactiveWebApplication(ConditionContext context) {
// 检查是否存在Reactive相关类
return ClassUtils.isPresent("org.springframework.web.reactive.HandlerResult",
context.getClassLoader());
}
}
使用示例
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
public class WebMvcConfig {
@Bean
public HandlerInterceptor securityInterceptor() {
return new SecurityInterceptor();
}
}
@Configuration
@ConditionalOnWebApplication(type = Type.REACTIVE)
public class WebFluxConfig {
@Bean
public WebFilter securityWebFilter() {
return new SecurityWebFilter();
}
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnExpressionCondition.class)
public @interface ConditionalOnExpression {
String value(); // SpEL表达式
}
实现示例
class OnExpressionCondition extends SpringBootCondition {
private final SpelExpressionParser parser = new SpelExpressionParser();
private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(
ConditionalOnExpression.class.getName());
String expression = attributes.get("value").toString();
// 设置环境属性到评估上下文
evaluationContext.setVariable("env", context.getEnvironment());
try {
Expression spelExpression = parser.parseExpression(expression);
boolean result = spelExpression.getValue(evaluationContext, Boolean.class);
return new ConditionOutcome(result,
"SpEL expression '" + expression + "' evaluated to " + result);
} catch (Exception ex) {
return ConditionOutcome.noMatch("Error evaluating expression: " + ex.getMessage());
}
}
}
使用示例:
// 使用示例
@Configuration
@ConditionalOnExpression("${app.feature.enabled:false} and " +
"T(java.lang.System).getProperty('java.version').startsWith('11')")
public class Java11FeatureConfig {
// 只有当app.feature.enabled为true且Java版本为11时才生效
@Bean
public FeatureManager featureManager() {
return new Java11FeatureManager();
}
}
复杂表达式示例:
@Configuration
@ConditionalOnExpression(
"#{environment['spring.profiles.active'] != 'test' and " +
"T(org.springframework.util.ClassUtils).isPresent(" +
"'com.mysql.jdbc.Driver', @beanFactory.getBeanClassLoader()) and " +
"@environment.getProperty('db.minimum-connections', Integer, 0) > 0}")
public class DatabaseConfig {
// 条件:
// 1. 当前profile不是test
// 2. MySQL驱动类存在
// 3. 最小连接数配置大于0
@Bean
public DataSource dataSource() {
return new PooledDataSource();
}
}
public class AutoConfigurationImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 加载所有自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata);
// 2. 去除重复
configurations = removeDuplicates(configurations);
// 3. 排除不需要的配置
configurations = exclude(configurations, getExclusions(annotationMetadata));
// 4. 过滤不满足条件的配置
configurations = filter(configurations, getFilter());
return configurations.toArray(new String[0]);
}
}
public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor {
private void processConfigurationClass(ConfigurationClass configClass) {
// 处理配置类上的条件注解
if (!conditionEvaluator.shouldSkip(configClass.getMetadata())) {
// 如果条件满足,继续处理配置类
processConfigurationClass(configClass);
}
}
}
Spring Boot通过@Order注解控制条件的执行顺序:
public class AutoConfigurationImportSelector {
private List<String> filter(List<String> configurations) {
// 按Order注解排序所有条件
List<Condition> conditions = getConditions();
Collections.sort(conditions, ConditionPriorityComparator.INSTANCE);
// 依次执行条件判断
for (String configuration : configurations) {
if (!matches(conditions, configuration)) {
configurations.remove(configuration);
}
}
return configurations;
}
}
常见条件注解的执行顺序:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnEnvironmentCondition.class)
public @interface ConditionalOnEnvironment {
String[] value();
}
public class OnEnvironmentCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attrs = metadata.getAnnotationAttributes(
ConditionalOnEnvironment.class.getName());
String[] environments = (String[]) attrs.get("value");
String activeProfile = context.getEnvironment()
.getProperty("spring.profiles.active");
return Arrays.asList(environments).contains(activeProfile);
}
}
@Configuration
@ConditionalOnEnvironment({"dev", "test"})
public class DevTestConfiguration {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
// 推荐
@ConditionalOnProperty(prefix = "app", name = "feature", havingValue = "true")
// 而不是自己实现
@Conditional(CustomCondition.class)
@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)
@ConditionalOnProperty(prefix = "spring.redis", name = "enabled", havingValue = "true")
@ConditionalOnMissingBean(RedisTemplate.class)
public class RedisConfiguration {
// ...
}
// 不推荐
@ConditionalOnBean(name = {"bean1", "bean2", "bean3", ...}) // 过多的bean检查
// 推荐
@ConditionalOnClass(RequiredClass.class) // 轻量级的类检查
debug=true
logging.level.org.springframework.boot.autoconfigure=DEBUG
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.setListeners(Arrays.asList(
new ConditionEvaluationReportLoggingListener(new LoggingSystem())));
application.run(args);
}
}
问题:多个条件注解的评估顺序不符合预期
解决方案:
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 自定义条件逻辑
}
}
常见原因:
检查方法:
@Autowired
private ConfigurableListableBeanFactory beanFactory;
public void debugConditions() {
ConditionEvaluationReport report = ConditionEvaluationReport
.get(beanFactory);
Map<String, ConditionAndOutcome> conditions = report
.getConditionAndOutcomesBySource();
conditions.forEach((source, condition) -> {
System.out.println(source + ": " + condition.getOutcome());
});
}
Spring Boot的条件注解和配置加载机制是一个精心设计的体系: