springdoc / springdoc-openapi

Library for OpenAPI 3 with spring-boot
https://springdoc.org
Apache License 2.0
3.25k stars 492 forks source link

Change of behaviour in the beans loading order #1965

Closed jorgerod closed 1 year ago

jorgerod commented 1 year ago

Describe the bug

Hello

I wanted to tell you about the problem I am having. The problem is unusual and will not happen easily.

The BeanFactoryPostProcessors SpringdocBeanFactoryConfigurer and SpringdocActuatorBeanFactoryConfigurer are causing a change in the loading order in an application I have with springdoc and spring-cloud-stream when there is a pollable and it is causing an error.

ConfigurableListableBeanFactory#getBeanNamesForType(Class) method is causing factorybeans to be initialised.

https://github.com/springdoc/springdoc-openapi/blob/b274a0e517cc73c3f7a809f48b9b3771cf840308/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringdocBeanFactoryConfigurer.java#L60-L64

Internally this call is being made:

    @Override
    public String[] getBeanNamesForType(@Nullable Class<?> type) {
        return getBeanNamesForType(type, true, true);
    }

And according to the javadoc of ListableBeanFactory, the third parameter can cause dangerous behaviour.

allowEagerInit – whether to initialize lazy-init singletons and objects created by FactoryBeans (or by factory methods with a "factory-bean" reference) for the type check. Note that FactoryBeans need to be eagerly initialized to determine their type: So be aware that passing in "true" for this flag will initialize FactoryBeans and "factory-bean" references.

In my case, BindableFunctionProxyFactory (implements FactoryBean) is being initialised early and internally doing

    protected void populateBindingTargetFactories(BeanFactory beanFactory) {
        this.bindingTargetFactories = ((ListableBeanFactory) beanFactory).getBeansOfType(BindingTargetFactory.class);
    }

. But as it is initialised early, it will not load all the beans it should. That is, the FactoryBean is created without guaranteeing that all the beans have already been created.

To Reproduce Steps to reproduce the behavior:

Solution

This problem can easily be solved in the following way

In SpringdocBeanFactoryConfigurer class, get beans by type with allowEagerInit flag set to false

  /**
   * Init bean factory post processor.
   *
   * @param beanFactory the bean factory
   */
  public static void initBeanFactoryPostProcessor(final ConfigurableListableBeanFactory beanFactory) {
    for (final String beanName : beanFactory.getBeanNamesForType(OpenAPIService.class, true, false)) {
      beanFactory.getBeanDefinition(beanName).setScope(SCOPE_PROTOTYPE);
    }
    for (final String beanName : beanFactory.getBeanNamesForType(OpenAPI.class, true, false)) {
      beanFactory.getBeanDefinition(beanName).setScope(SCOPE_PROTOTYPE);
    }
  }

In fact, I can make a contribution if you like.

bnasslahsen commented 1 year ago

@jorgerod,

You are welcome to propose a Pr with the related unit tests for this enhancement

jorgerod commented 1 year ago

@bnasslahsen Is there a release date for the new 1.6.x version?

This is a very important version for us.

Thank you very much and sorry for the inconvenience

bnasslahsen commented 1 year ago

@jorgerod,

Not yet planned. When you say this is very important for us. Can you be more specific. Who is the us?

jorgerod commented 1 year ago

Hi @bnasslahsen

We have a Spring-boot based framework that uses OpenApi contracts and we support SpringDoc to use it.