Spring Boot 工厂 Bean 创建顺序

Posted

技术标签:

【中文标题】Spring Boot 工厂 Bean 创建顺序【英文标题】:Spring Boot Factory Bean Creation Order 【发布时间】:2019-04-03 23:43:09 【问题描述】:

我正在尝试在 Spring Boot 中动态注册 Bean,但是如果尝试自动装配其中一个动态 bean,则创建 bean 的顺序总是会导致 NoSuchBeanDefinitionException

我的设置包括两个项目,一个 spring-boot-starter 项目和实际的 spring-boot 应用程序。

实际的应用程序注册了一个BeanDefinitionRegistryPostProcessor,它添加了 bean 定义。实例本身是通过在启动项目中定义的另一个 bean 构建的,该项目本身将另一个 bean 作为依赖项。

为了使用动态注册的 bean,我创建了一个用 @Component 注释的类,并定义了一个期望该 bean 作为参数的构造函数。 当我通过设置@Autowired(required=false) 调试应用程序时,我可以看到在创建动态bean 之前调用了我的组件的构造函数。而且,当时连工厂 bean 都没有创建。

将带有工厂 bean 名称的 @DependsOn 添加到组件导致首先创建工厂,而不是动态 bean。

使用动态 bean 的名称设置 @DependsOn 有效,但这似乎不是解决此问题的正确方法。

为什么 Spring 以错误的顺序创建我的 bean,我能做些什么来解决这个问题?

编辑:

我能够在示例存储库中重现该问题:https://github.com/maveeee/spring-dynamic-bean-demo/

【问题讨论】:

【参考方案1】:

您可以使用@Order 注释来定义带注释的组件或bean 的排序顺序。

考虑到在 Spring 4.0 之前,这个注解只用于 AspectJ 的执行顺序。 Spring 4.0 之后,支持将注入的组件排序到集合中。因此,Spring 会根据它们的 order 值注入相同类型的自动装配 bean。

例如:

interface IBean 
    String getName();


public class BeanX implements IBean 
    public BeanX() 

    @Override
    public String getName() 
        return "BeanX";
    


public class BeanY implements IBean 
    public BeanY() 

    @Override
    public String getName() 
        return "BeanY";
    


@Component
public class RandomComponent 
    @Autowired
    private List<IBean> beans;

    @PostConstruct
    public void getBeanValues() 
        System.out.println("\n---@Bean---\n");
        for (IBean b : beans) 
            System.out.println(b.getName());
        
    

    @Bean
    @Order(1)
    public IBean getBeanX() 
        return new BeanX();
    

    @Bean
    @Order(0)
    public IBean getBeanY() 
        return new BeanY();
    

将打印:

---@Bean---

BeanY
BeanX

因为BeanY 的优先级(0,较低的值)高于 BeanX(较高的值,1)。

GitHub Demo

相关文章:

@Order in Spring What is the use of @Order annotation in Spring? Spring annotation @Order Order of Configuration in SpringBoot

【讨论】:

我将提取相关部分并设置一个演示库 @MaVe 不客气 :) 如果您最终能够重现该问题,请不要犹豫,在这里给我留言,我会尽力帮助您。 我终于可以重现这个问题了:可以在github.com/maveeee/spring-dynamic-bean-demo看到一个示例 @MaVe 有机会时请检查此pull request 以查看它是否解决了您的问题,如果解决了问题,请告诉我,以便我为未来的读者更新答案。跨度> @MaVe 是的,确实!检查这个:***.com/a/41444078/5640649【参考方案2】:

我发现我的问题是由我创建 bean 定义的方式引起的。我使用的是GenericBeanDefinition 而不是RootBeanDefinition。使用后者允许我使用setTargetType() 而不是setBeanClass(),这立即解决了问题并导致Spring 找出创建bean 的正确顺序,以便我可以通过@Autowired 注入动态创建的bean。

之前:

        var identifier = ...    // Some String identifying the bean
        var clazz = ...         // Some class object coming from a dependency

        var beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(clazz);
        beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
        beanDefinition.setAutowireCandidate(true);
        beanDefinition.setFactoryBeanName(CONTRACT_FACTORY_BEAN_NAME);
        beanDefinition.setFactoryMethodName(CONTRACT_FACTORY_METHOD_NAME);

        registry.registerBeanDefinition(identifier, beanDefinition);

之后:

        var identifier = ...    // Some String identifying the bean
        var clazz = ...         // Some class object coming from a dependency

        var beanDefinition = new RootBeanDefinition();
        beanDefinition.setTargetType(clazz);
        beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
        beanDefinition.setAutowireCandidate(true);
        beanDefinition.setFactoryBeanName(CONTRACT_FACTORY_BEAN_NAME);
        beanDefinition.setFactoryMethodName(CONTRACT_FACTORY_METHOD_NAME);

        registry.registerBeanDefinition(identifier, beanDefinition);

我将更新repository中的示例代码以供进一步参考。

【讨论】:

以上是关于Spring Boot 工厂 Bean 创建顺序的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 学习系列(09)—自定义Bean的顺序加载

Spring Boot 学习系列(09)—自定义Bean的顺序加载

Spring配置bean的方法(工厂方法和Factorybean)

Spring基础 : 静态工厂和实例工厂创建bean

Spring 学习总结 使用静态工厂创建Bean

Spring中Bean的配置方式之通过工厂方法