1.@ConditionalOnBean顺序问题演示
1.1 正常情况演示
@Configuration
public class Demo {
@Bean
public AAA aaa(){
return new AAA();
}
@Bean
@ConditionalOnBean(value = {AAA.class})
public BBB bbb(){
return new BBB();
}
}
class AAA {
public AAA() {
System.out.println("aaa constructor");
}
}
class BBB {
public BBB() {
System.out.println("bbb constructor");
}
}
正常输出:
aaa constructor
bbb constructor
1.2 顺序问题演示
@Bean
@ConditionalOnBean(value = {AAA.class})
public BBB bbb(){
return new BBB();
}
@Bean
public AAA aaa(){
return new AAA();
}
将2个bean 方法顺序调换后输出:
aaa constructor
1.3 问题小结
从使用的角度来看,aaa对象既然已经创建了 那么就应该加载bbb对象 但是由于顺序问题 ,并没有按照预期的执行
2.原因排查
2.1 @ConditionalOnBean 入口
2.2 @ConditionalOnBean层级关系
2.3 核心关注点
org.springframework.boot.autoconfigure.condition.OnBeanCondition#collectBeanNamesForType
private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy,
Class<?> type, Set<Class<?>> parameterizedContainers, Set<String> result) {
//TODO type 就是 conditionalOnBean 的属性
//TODO 这边通过beanFactory 查找相关的对象 来判断依赖的对象存不存在
result = addAll(result, beanFactory.getBeanNamesForType(type, true, false));
for (Class<?> container : parameterizedContainers) {
ResolvableType generic = ResolvableType.forClassWithGenerics(container, type);
result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false));
}
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory();
if (parent instanceof ListableBeanFactory) {
result = collectBeanNamesForType((ListableBeanFactory) parent, considerHierarchy, type,parameterizedContainers, result);
}
}
return result;
}
2.3.1 看下 beanFactory.getBeanNamesForType 的查找逻辑
org.springframework.beans.factory.support.DefaultListableBeanFactory#doGetBeanNamesForType
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
for (String beanName : this.beanDefinitionNames){
......
}
}
这边主要就是遍历 beanDefinitionNames (存储了所有spring加载的对象)
2.3.2 针对这块代码 打个端点比较下 2种情况
2.3.2.1 正常顺序
2.3.2.2 不正常顺序
2.3.3 看下 @bean 对象是什么时候 注册到 beanDefinitionNames里的
核心类:ConfigurationClassPostProcessor org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
//TODO 循环处理配置类
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
// Do we need to mark the bean as skipped by its condition?
//TODO 这里就是 @ConditionalOnBean 调用入口
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
......此处省略100行
//TODO 这边就是往 beanDefinitionNames add 值的
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
2.4 小结
产生这个问题的原因主要是因为 @ConditionalOnBean 注解是基于是否创建beanDefinition对象来判断的,但是他的调用入口是在创建 beanDefinition的过程中 所以对顺序上会有依赖
3.自定义注解思路
先把所有的beanDefinition全部加载完,然后再删除那些不需要加载的
加载beanDefinition的核心类是ConfigurationClassPostProcessor 其核心接口是 BeanDefinitionRegistryPostProcessor
那么我们通过 下一个扩展接口 BeanFactoryPostProcessor 去实现这个功能(2个接口的顺序问题看 org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors)
4.@XrConditionalOnBean 开造
4.1 类定义
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XrConditionalOnBean {
String[] name();
}
@Component
public class ConditionalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//TODO 获取所有 XrConditionalOnBean修饰的bean
String[] beanNamesForAnnotation = beanFactory.getBeanNamesForAnnotation(XrConditionalOnBean.class);
if (!(beanFactory instanceof BeanDefinitionRegistry))
return;
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (int i = 0; i < beanNamesForAnnotation.length; i++) {
//TODO 获取BeanDefinition
BeanDefinition beanDefinition = registry.getBeanDefinition(beanNamesForAnnotation[i]);
Set<String> set = new HashSet<>();
if (!(beanDefinition instanceof AnnotatedBeanDefinition))
continue;
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
//TODO 获取类注解参数
Map<String, Object> annotationAttributes = annotatedBeanDefinition.getMetadata().getAnnotationAttributes(XrConditionalOnBean.class.getName(), false);
if (annotationAttributes != null) {
set.addAll(Stream.of((String[]) annotationAttributes.get("name")).collect(Collectors.toSet()));
}
if (annotatedBeanDefinition.getFactoryMethodMetadata() != null) {
//TODO 获取方法上的注解参数
Map<String, Object> methodAnnotation = annotatedBeanDefinition.getFactoryMethodMetadata().getAnnotationAttributes(XrConditionalOnBean.class.getName(), false);
if (methodAnnotation != null) {
set.addAll(Stream.of((String[]) methodAnnotation.get("name")).collect(Collectors.toSet()));
}
}
if (set.isEmpty())
continue;
for (String beanName : set) {
//TODO 校验如果不存在指定bean 则删除该对象
if (!registry.containsBeanDefinition(beanName)) {
registry.removeBeanDefinition(beanNamesForAnnotation[i]);
}
}
}
}
}
4.2 运行
@Bean
@XrConditionalOnBean(name = {"aaa"})
public BBB bbb(){
return new BBB();
}
@Bean
public AAA aaa(){
return new AAA();
}
正确输出:
bbb constructor
aaa constructor
@Bean
@XrConditionalOnBean(name = {"aaa"})
public BBB bbb(){
return new BBB();
}
正确输出:
空