1

I am trying to dynamically register Beans in Spring Boot, however the order of how the beans are created always result in a NoSuchBeanDefinitionException if it try to autowire one of the dynamic beans.

My setup consists of two projects, one spring-boot-starter project and the actual spring-boot application.

The actual application registers a BeanDefinitionRegistryPostProcessor that adds the bean definitions. The instances itself are constructed via another bean defined in the starter project that itself takes another bean as dependency.

In order to consume the dynamically registered bean, I created a class annotated with @Component and defined a constructor expecting said bean as parameter. When I debug the application by setting @Autowired(required=false), I can see that the constructor of my component is called before the dynamic bean has been created. Moreover, not even the factory bean has been created at that time.

Added @DependsOn with the factory bean's name to component resulted the factory being created first, but not the dynamic beans.

Setting @DependsOn with the dynamic bean's name works, but that doesn't seem to be the correct way to solve this issue.

Why is Spring creating my beans in the wrong order and what can I do solve this?

EDIT:

I was able to reproduce the issue in a sample repository:
https://github.com/maveeee/spring-dynamic-bean-demo/

Community
  • 1
  • 1
MaVe
  • 41
  • 9

2 Answers2

1

You can use the @Order annotation which defines the sort order for an annotated component or bean.

Take into account that before Spring 4.0, this annotation was used only for the AspectJ execution order. After Spring 4.0, it is supported the ordering of injected components to a collection. Thus, Spring will inject the auto-wired beans of the same type based on their order value.

For example:

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

Will print:

---@Bean---

BeanY
BeanX

Because BeanY has higher precedence (0, lower value) over BeanX (higher value, 1).

GitHub Demo

Related articles:

lealceldeiro
  • 14,342
  • 6
  • 49
  • 80
  • Thanks for your reply! I tried using `@Order` as well as implementing `PriorityOrdered` neither had the expected outcome. – MaVe Oct 30 '18 at 21:08
  • @MaVe do you have a demo repo in which I can take a look at? – lealceldeiro Oct 30 '18 at 21:09
  • 1
    I will extract the relevant parts and setup a demo repository – MaVe Oct 30 '18 at 21:21
  • thank you for all the effort you put into your answer! I tried extracting the relevant parts into a separate project as I cannot provide the whole code base in which my issue occurs. Unfortunately, I was not able to reproduce the issue. The original code uses 3 different spring boot starter, two of them I control myself and one is third party. Maybe I oversimplified things in my demo project. I will continue trying to reproduce the issue... – MaVe Oct 30 '18 at 23:46
  • 1
    @MaVe you're welcome :) If you finally are able to reproduce the issue do not hesitate, drop me a comment here and I'll try to help you. – lealceldeiro Oct 31 '18 at 11:25
  • 1
    I could finally reproduce the issue: A sample can be seen at https://github.com/maveeee/spring-dynamic-bean-demo/ – MaVe Oct 31 '18 at 11:51
  • 1
    @MaVe when you get a chance check this [pull request](https://github.com/maveeee/spring-dynamic-bean-demo/pull/1) in order to see if that solves you problem and let me know if it solved the problem so I can update the answer for future readers. – lealceldeiro Oct 31 '18 at 20:51
  • Thank you for your pull request, unfortunately it does not solve my problem. The `BeanDefinitionRegistryPostProcessor` you added registers the beans in a similar fashion, however, I need to create them through a factory bean which itself has autowired properties. The way I created the beans in `DynamicBeanGenerator` is just an oversimplification of what I am doing in my project. In reality a more complex data structure is passed to the factory instead of just the `Class>` object. So unfortunately my problem still exists, however, it seems that the issue is related to using a factory bean – MaVe Oct 31 '18 at 21:54
  • 1
    @MaVe yes, indeed! Check this: https://stackoverflow.com/a/41444078/5640649 – lealceldeiro Nov 01 '18 at 12:48
0

I figured out that my issue resulted from how I created the bean definition. I was using a GenericBeanDefinition instead of a RootBeanDefinition. Using the later allowed me to use setTargetType() instead of setBeanClass() which immediately resolved the issue and resulted in Spring figuring out the correct order to create the beans so that I could inject the dynamically created bean via @Autowired.

Before:

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

After:

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

I will update the sample code in the repository for further reference.

MaVe
  • 41
  • 9