条件注解机制
深入理解SpringBoot条件注解的工作原理和实现机制,掌握Condition接口、条件评估器的工作原理,学会使用和自定义条件注解。
📋 条件注解概览
SpringBoot的条件注解允许我们根据特定条件来决定是否加载Bean或配置类。
🎯 一、Condition接口
1.1 Condition接口定义
Condition是Spring条件注解的基础接口:
java
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}方法说明:
- matches:判断条件是否满足
- ConditionContext:条件上下文,提供环境、BeanFactory等信息
- AnnotatedTypeMetadata:注解元数据,提供注解信息
1.2 ConditionContext
java
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
ClassLoader getClassLoader();
}ConditionContext提供的能力:
- BeanDefinitionRegistry:检查Bean定义
- ConfigurableListableBeanFactory:检查Bean实例
- Environment:获取配置属性
- ResourceLoader:加载资源
- ClassLoader:检查类是否存在
1.3 AnnotatedTypeMetadata
java
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationName);
Map<String, Object> getAnnotationAttributes(String annotationName);
Map<String, Object> getAnnotationAttributes(String annotationName,
boolean classValuesAsString);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
// ...
}🎯 二、条件注解体系
2.1 类条件注解
@ConditionalOnClass
作用:当类路径存在指定类时生效。
java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}使用示例:
java
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
// 只有当类路径存在DataSource类时才加载
}实现原理:
java
// OnClassCondition.getMatchOutcome
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes")
.items(Style.QUOTE, missing));
}
}
// ...
return ConditionOutcome.match(matchMessage);
}@ConditionalOnMissingClass
作用:当类路径不存在指定类时生效。
java
@Configuration
@ConditionalOnMissingClass("com.example.OldService")
public class NewServiceAutoConfiguration {
// 只有当类路径不存在OldService时才加载
}2.2 Bean条件注解
@ConditionalOnBean
作用:当容器中存在指定Bean时生效。
java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
Class<?>[] value() default {};
String[] name() default {};
Class<?>[] type() default {};
}使用示例:
java
@Configuration
@ConditionalOnBean(DataSource.class)
public class JdbcTemplateAutoConfiguration {
// 只有当容器中存在DataSource Bean时才加载
}实现原理:
java
// OnBeanCondition.getMatchOutcome
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
// 检查Bean是否存在
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
ConditionalOnBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(ConditionMessage.forCondition(
ConditionalOnBean.class, spec).because(reason));
}
matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
.found("bean", "beans").items(Style.QUOTE, matchResult.getNames());
}
// ...
return ConditionOutcome.match(matchMessage);
}@ConditionalOnMissingBean
作用:当容器中不存在指定Bean时生效。
java
@Bean
@ConditionalOnMissingBean
public MyService myService() {
// 只有当容器中不存在MyService Bean时才创建
return new MyService();
}2.3 属性条件注解
@ConditionalOnProperty
作用:当配置属性满足条件时生效。
java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
String[] value() default {};
String prefix() default "";
String[] name() default {};
String havingValue() default "";
boolean matchIfMissing() default false;
}使用示例:
java
@Configuration
@ConditionalOnProperty(prefix = "my.feature", name = "enabled",
havingValue = "true", matchIfMissing = false)
public class MyFeatureAutoConfiguration {
// 只有当my.feature.enabled=true时才加载
}配置示例:
yaml
# application.yml
my:
feature:
enabled: true实现原理:
java
// OnPropertyCondition.getMatchOutcome
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
List<AnnotationAttributes> allAnnotationAttributes =
metadata.getAnnotations(ConditionalOnProperty.class);
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
ConditionOutcome outcome = determineOutcome(annotationAttributes,
context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.match(ConditionMessage.of(match));
}@ConditionalOnExpression
作用:当SpEL表达式结果为true时生效。
java
@Configuration
@ConditionalOnExpression("${my.feature.enabled:false} && ${my.feature.mode} == 'advanced'")
public class AdvancedFeatureConfiguration {
// 只有当表达式为true时才加载
}2.4 Web条件注解
@ConditionalOnWebApplication
作用:当应用是Web应用时生效。
java
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class WebMvcAutoConfiguration {
// 只有当应用是Servlet Web应用时才加载
}@ConditionalOnNotWebApplication
作用:当应用不是Web应用时生效。
java
@Configuration
@ConditionalOnNotWebApplication
public class BatchAutoConfiguration {
// 只有当应用不是Web应用时才加载
}🎯 三、条件评估流程
3.1 条件评估时机
条件注解在以下时机被评估:
- 配置类解析阶段:
ConfigurationClassPostProcessor.processConfigBeanDefinitions - Bean定义加载阶段:
invokeBeanFactoryPostProcessors - Bean实例化阶段:
finishBeanFactoryInitialization
3.2 条件评估流程
3.3 ConditionEvaluator
Spring使用ConditionEvaluator来评估条件:
java
public class ConditionEvaluator {
private final ConditionContext context;
public ConditionEvaluator(ResourceLoader resourceLoader,
Environment environment) {
// ...
}
public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
return shouldSkip(metadata, null);
}
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata,
@Nullable ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass,
this.context.getClassLoader());
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if (requiredPhase == null || requiredPhase == phase) {
if (!condition.matches(this.context, metadata)) {
return true;
}
}
}
return false;
}
}🎯 四、自定义条件注解
4.1 创建自定义Condition
示例:环境条件判断
java
public class OnEnvironmentCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(
ConditionalOnEnvironment.class.getName());
String[] environments = (String[]) attributes.get("value");
String currentEnvironment = context.getEnvironment()
.getProperty("spring.profiles.active", "default");
return Arrays.asList(environments).contains(currentEnvironment);
}
}4.2 创建条件注解
java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnEnvironmentCondition.class)
public @interface ConditionalOnEnvironment {
String[] value();
}4.3 使用自定义条件注解
java
@Configuration
@ConditionalOnEnvironment("production")
public class ProductionOnlyConfiguration {
// 只在生产环境加载
}🎯 五、条件注解最佳实践
5.1 条件组合
多个条件组合:
java
@Configuration
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnBean(DataSource.class)
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
public class JdbcTemplateAutoConfiguration {
// 多个条件必须同时满足
}5.2 条件优先级
条件注解按以下顺序评估:
- 类条件:
@ConditionalOnClass、@ConditionalOnMissingClass - Bean条件:
@ConditionalOnBean、@ConditionalOnMissingBean - 属性条件:
@ConditionalOnProperty、@ConditionalOnExpression - Web条件:
@ConditionalOnWebApplication、@ConditionalOnNotWebApplication
5.3 条件注解顺序
使用@Order注解控制条件评估顺序:
java
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
@ConditionalOnClass(MyService.class)
public class MyServiceAutoConfiguration {
// 高优先级
}📖 学习资源
视频教程
网站资源
✅ 学习检查清单
- [ ] 已理解Condition接口的作用
- [ ] 已理解ConditionContext的作用
- [ ] 已掌握类条件注解的使用
- [ ] 已掌握Bean条件注解的使用
- [ ] 已掌握属性条件注解的使用
- [ ] 已理解条件评估流程
- [ ] 已能够创建自定义条件注解
上一章节:Starter机制
下一章节:配置属性绑定
返回目录:SpringBoot源码深度解析