@Condition是Spring中很关键的一个注解,它能够帮助我们只注册符合条件的Bean,我们可以通过配置、容器中的Bean、Java版本等筛选符合bean注册。
1.注解逻辑
以ConditionOnBean为例
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
}
本质上引入了OnBeanCondition类来进行筛选,而这个类是继承于Condition类来实现的。
@FunctionalInterface
public interface Condition {
/**
* 该方法用于判断该注解条件是否符合,符合返回true
**/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
2.SpringBoot扩展
SpringBoot为了使这个注解能够更加全面,定义了抽象类SpringBootCondition,所有的条件注解都是继承于此类,但SB提供的所有注解都没有实现matches方法,只有在SpringBootCondition中有,那么我们可以得出其实是使用了模版方法。
@Override
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();
}
catch (NoClassDefFoundError ex) {
xxx
}
catch (RuntimeException ex) {
xxx
}
}
3.具体的getMatchOutcome方法
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
MergedAnnotations annotations = metadata.getAnnotations();
// 如果是ConditionOnBean则会执行以下判断逻辑
if (annotations.isPresent(ConditionalOnBean.class)) {
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());
}
// 省略其他条件注解判断逻辑
}
定位到getMatchingBeans方法,知道本质就是通过去容器获取该类型的bean集合,如果获取到了说明容器已经有该bean就会封装ConditionOutcome条件结果返回。
// 核心逻辑
Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type, parameterizedContainers);
同理我们可以看其他条件注解 @ConditionalOnClass @ConditionalOnProperty等具体实现也是获取环境配置或者class.forName去判断是否有这个类。
2.调用入口
1.BeanFactoryPostProcessor
condition和BeanFactoryPostProcessor有什么关系呢? 我们都知道@condition注解的作用就是避免注册某些我们不需要的bean。那我们入手就在bean的扫描过程。那么我们定义到自动配置类扫描代码处,即ConfigurationClassPostProcessor类
我们定位到配置类注册逻辑处,postProcessBeanDefinitionRegistry() -> processConfigBeanDefinitions()
可以发现内部有parse类就是用于解析配置。最后跟踪到processConfigBeanDefinitions处会发现关键调用入口
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
最后我们跟踪到ConditionEvaluator就能发现本质就是调用了matches方法进行匹配
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);
}
// 解析注解condition
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();
}
// matches方法进行判断
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
看到这里会不会有个疑问,明明是按顺序解析Bean信息,如果使用了@ConditionOnBean,但解析该配置类时并未引入相同类型的Bean,解析完再解析其他同类型的Bean,该条件注解不就不起作用了吗?
是的,解析顺序在条件注解判断时候也很重要,像ConditionOnBean判断容器中是否有该bean,但该bean还未注册到容器中,相当于判断就失效了,所有bean的解析顺序也很重要,所以我们会发现一般我们会先注册自己代码中的bean,然后扫描自动配置的bean。而同一配置类内我们可以用@AutoConfigureBefore等注解指定扫描顺序
3.注解扫描顺序
我们查看AutoConfigurationSorter中的getInPriorityOrder方法
- 按照字母顺序排序
- 按照@AutoConfigureOrder排序
- 按照@AutoConfigureBefore @AutoConfigureAfter排序
List<String> getInPriorityOrder(Collection<String> classNames) {
AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
this.autoConfigurationMetadata, classNames);
List<String> orderedClassNames = new ArrayList<>(classNames);
// 按字母顺序排序
Collections.sort(orderedClassNames);
// 然后按照order顺序排序
orderedClassNames.sort((o1, o2) -> {
int i1 = classes.get(o1).getOrder();
int i2 = classes.get(o2).getOrder();
return Integer.compare(i1, i2);
});
// 最后按照 @AutoConfigureBefore @AutoConfigureAfter排序
orderedClassNames = sortByAnnotation(classes, orderedClassNames);
return orderedClassNames;
}
经过上面的排序后就能保证配置类的加载顺序,保证Bean创建流程。
总结
@Condition注解帮助我们选择适当的bean进行注册,而不同的bean注册时候又有错综复杂的依赖关系,在SpringBoot中都帮我们很好解决了。
评论