写一个没有顺序问题的 @ConditionalOnBean

247 阅读2分钟

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 入口

image.png

2.2 @ConditionalOnBean层级关系

image.png

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 正常顺序

image.png

2.3.2.2 不正常顺序

image.png

2.3.3 看下 @bean 对象是什么时候 注册到 beanDefinitionNames里的

核心类:ConfigurationClassPostProcessor org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

image.png

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {  
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();  
    for (ConfigurationClass configClass : configurationModel) {  
        //TODO 循环处理配置类
        loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);  
    }  
}

image.png

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();  
}  
  

正确输出: