spring中存在各种条件注解,用于按条件生成bean,可以结合应用动态创建bean,扩展性非常好,常见有:
1. 核心条件注解(Spring Framework)
- @Conditional:最底层的条件注解,需要自定义实现
Condition
接口。
2. Spring Boot 条件注解
2.1 按 Bean 条件
- @ConditionalOnBean:容器中存在指定 Bean 时生效
- @ConditionalOnMissingBean:容器中不存在指定 Bean 时生效
2.2 按 Class 条件
- @ConditionalOnClass:classpath 中存在指定类时生效
- @ConditionalOnMissingClass:classpath 中不存在指定类时生效
2.3 按配置属性条件
- @ConditionalOnProperty:当配置文件中存在指定属性,并且值符合要求时生效
2.4 按资源条件
- @ConditionalOnResource:classpath 下存在指定资源文件时生效
2.5 按 Web 环境条件
- @ConditionalOnWebApplication:当前是 Web 应用环境时生效
- @ConditionalOnNotWebApplication:当前不是 Web 应用环境时生效
2.6 按表达式 / Java 版本条件
- @ConditionalOnExpression:基于 SpEL 表达式的结果决定是否生效
- @ConditionalOnJava:基于 JVM 版本判断是否生效
2.7 按候选 Bean 条件
- @ConditionalOnSingleCandidate:指定类型只有一个候选 Bean 或标记了
@Primary
时生效
2.8 按云平台条件(Spring Boot 2.0+)
- @ConditionalOnCloudPlatform:基于 Cloud 平台环境判断(如 Heroku, Cloud Foundry)
所有的的条件注解继承了@Conditional
,每个条件注解都指定了Condition
接口的实现,处理条件注解的方法是shouldSkip,参数metadata表示注解元数据,包含注解的属性,phase表示条件判断的阶段。处理的逻辑是
- 如果注解不存在,则不用判断是否跳过,直接进行处理。
- 没有告知哪个阶段,按解析配置阶段算
- 提取bean的class上所有的
Condition
接口 - 调用每个
Condition
接口的matches
方法判断是否处理
java
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
//没有@Condition注解默认需要处理
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
//是spring的配置类,则默认解析配置阶段
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
//否则按注册bean阶段处理
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
//获取配置的Conditional接口
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();
}
//如果Condition没有声明作用的阶段或作用的阶段和实际调用shouldSkip方法判断时一样,通过Condition接口判断
//作用的阶段和实际调用shouldSkip方法判断时不一样,返回false表示需要处理
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
//调用实现类的getMatchOutcome方法根据条件判断
ConditionOutcome outcome = getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
}
......
}
下面以@ConditionalOnClass
、ConditionalOnBean
为例看下。
java
@ConditionalOnClass({ Servlet.class, ServerContainer.class })
public class WebSocketServletAutoConfiguration {
....
}
WebSocketServletAutoConfiguration
上存在@ConditionalOnClass
注解,指定当Servlet.class, ServerContainer.class存在时,才解析WebSocketServletAutoConfiguration配置类。首先根据@ConditionalOnClass({ Servlet.class, ServerContainer.class })
获取条件生效的类Servlet.class, ServerContainer.class
的类路径字符串,根据类路径加载,如果加载不到就返回无法加载到的类,再检查是否存在类无法加载就行,最后返回ConditionOutcome表示测试结果。
java
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
//获取@ConditionalOnClass注解指定的类路径字符串
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));
}
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class", "required classes")
.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
}
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
if (!present.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class", "unwanted classes")
.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
}
//返回加载为true的ConditionOutcome
return ConditionOutcome.match(matchMessage);
}
springmvc的自动装配类CacheAutoConfiguration
上存在@ConditionalOnBean
,即bean CacheAspectSupport
存在时,才会解析CacheAutoConfiguration
。
java
@ConditionalOnBean(CacheAspectSupport.class)
public class CacheAutoConfiguration {}
使用Spec
封装配置类的@ConditionalOnBean
条件bean的class,然后到容器中根据类型CacheAspectSupport
搜索bean,如果找到则返回为true的ConditionOutcome。
java
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
MergedAnnotations annotations = metadata.getAnnotations();
if (annotations.isPresent(ConditionalOnBean.class)) {
//提取@ConditionalOnBean中声明的bean类型
Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
matchResult.getNamesOfAllMatches());
}
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {
return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
}
else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
spec.getStrategy() == SearchStrategy.ALL)) {
return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")
.items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
}
matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE,
matchResult.getNamesOfAllMatches());
}
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
ConditionalOnMissingBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (matchResult.isAnyMatched()) {
String reason = createOnMissingBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage);
}
protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
ClassLoader classLoader = context.getClassLoader();
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
"Unable to use SearchStrategy.ANCESTORS");
beanFactory = (ConfigurableListableBeanFactory) parent;
}
MatchResult result = new MatchResult();
//搜索忽略的bean名字
Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
spec.getIgnoredTypes(), parameterizedContainers);
for (String type : spec.getTypes()) {
//按类型搜索bean的名字
Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
parameterizedContainers);
//从满足类型要求的bean中去掉要忽略的bean
Iterator<String> iterator = typeMatches.iterator();
while (iterator.hasNext()) {
String match = iterator.next();
if (beansIgnoredByType.contains(match) || ScopedProxyUtils.isScopedTarget(match)) {
iterator.remove();
}
}
if (typeMatches.isEmpty()) {
result.recordUnmatchedType(type);
}
else {
result.recordMatchedType(type, typeMatches);
}
}
for (String annotation : spec.getAnnotations()) {
Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
considerHierarchy);
annotationMatches.removeAll(beansIgnoredByType);
if (annotationMatches.isEmpty()) {
result.recordUnmatchedAnnotation(annotation);
}
else {
result.recordMatchedAnnotation(annotation, annotationMatches);
}
}
for (String beanName : spec.getNames()) {
if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
result.recordMatchedName(beanName);
}
else {
result.recordUnmatchedName(beanName);
}
}
return result;
}
总结下,spring通过条件注解声明进行条件判断的Condition接口,然后根据接口的matches方法判断条件是否满足。有不对的地方请大神指出,欢迎大家一起讨论交流,共同进步,更多请关注微信公众号 葡萄开源