When calling BeanFactory#isPrototype during bean post-processing, beans seem to disappear, causing the context to fail to finish startup. I am not 100% sure if one is allowed to call this method at this point of time, but neither the Javadoc says that this is forbidden nor does the method name suggest that the context/bean factory gets modified as a side effect of calling this method.

Steps to reproduce: Create an empty spring-boot application with latest version (2.1.3) using initializr and drop in the following post processor:

@Component
public class LogPrototypeBeansPostProcessor implements BeanFactoryAware, BeanPostProcessor {

    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (beanFactory.isPrototype(beanName)) {
            System.out.println(beanName);
        }
        return bean;
    }
}

Comment From: lgxbslgx

@slinstaedt In my opinion, if we don't use createBean directly in all the source code, we can use the code you describe above.

The createBean can apply bean post processors. When using the createBean directly, we cannot ensure the bean is already managed by spring container. That is we cannot ensure whether the BeanDefinition is in the beanFactory.beanDefinitionMap. If the bean is not managed by spring container, it would cause the NoSuchBeanDefinitionException when using beanFactory.isPrototype(beanName). It is obvious that we cannot judge whether the bean is prototype when the BeanDefinition is not known.

You may doubt that you didn't use createBean at all. But it is used in spring. For example:

org.springframework.web.servlet.DispatcherServlet

protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
    return context.getAutowireCapableBeanFactory().createBean(clazz);
}

When you use the dispatcherServlet, it will create some default strategies that you don't configure, such as org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver. When the AcceptHeaderLocaleResolver is created, the bean post processors will be called. But the spring container doesn't contains the BeanDefinition of AcceptHeaderLocaleResolver. So NoSuchBeanDefinitionException would occur when you use beanFactory.isPrototype(beanName).

In order to fix the problem, you can use beanFactory.containsBean(beanName) firstly before using the beanFactory.isPrototype(beanName). As the code shown below.

public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (beanFactory.containsBean(beanName) && beanFactory.isPrototype(beanName)) {
          System.out.println(beanName);
    }
    return bean;
}

Hope this can help you.

Comment From: slinstaedt

Just to clarify my understanding:

  1. Does the AutowireCapableBeanFactory#createBean method accepts classes (as parameter), which are not spring beans? I am asking, because the doc explicitly states, they are "bean classes", which I am interpreting as "spring bean classes". If we talk any random java bean class (I am just guessing not only java beans would be valid here), that would mean we can have spring created, injected and initialised class instances. All of this probably being defined by spring bean annotations, so there is some kind of "temporary" BeanDefinition, which never gets registered with the BeanDefinitionRegistry.

  2. Or are the instances just being registered as spring beans after being fully injected and initialised, so during this process they are not known yet?

This is at least to say confusing. I thought the whole think about "spring beans" is, their specified (via XML, annotations or programmatically) definition is registered and their lifecycle is then managed by spring.

Comment From: lgxbslgx

I didn't distinguish the bean and the object in my previous comment, which may cause you to be confused. The AutowireCapableBeanFactory.createBean method can accept classes (as parameter) which are not spring beans. And the "temporary" BeanDefinition of the classes never gets registered with the BeanDefinitionRegistry in the createBean method.

If we want the BeanDefinition to be registered, we can register it explicitly before(or after) calling the createBean method. But the DispatcherServlet.getDefaultStrategies or the DispatcherServlet.createDefaultStrategy don't register the BeanDefinition explicitly so that we get NoSuchBeanDefinitionException when using beanFactory.isPrototype(beanName).

Maybe we can suggest the developer of this framework to register the BeanDefinition explicitly in this case. If the main developers agree to do it, I would submit a pull request to perfect the source code.

Comment From: slinstaedt

That clarifies a lot. Thanks for your additional information. From my point of view, this issue is not really a bug and could therefore be closed, but maybe the API might rather need some overhaul to reflect this expected behaviour.

Comment From: snicoll

Sorry this issue got overlooked. I tried to reproduce with a recent Spring Boot version and I can see that the post-processor logs the (few) beans that have a prototype scope and the startup of the application continues successfully. I've updated the main method to request one of those beans and the context managed to resolve it properly.

Sorry that I can't point to what fixes that or provide more details.