Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do Spring prototype beans need to be destroyed manually?

I noticed that the @PreDestroy hooks of my prototype scoped Spring beans were not getting executed.

I have since read here that this is actually by design. The Spring container will destroy singleton beans but will not destroy prototype beans. It is unclear to me why. If the Spring container will create my prototype bean and execute its @PostConstruct hook, why will it not destroy my bean as well, when the container is closed? Once my Spring container has been closed, does it even make sense to continue using any of its beans? I cannot see a scenario where you would want to close a container before you have finished with its beans. Is it even possible to continue using a prototype Spring bean after its container has been closed?

The above describes the puzzling background to my primary question which is: If the Spring container is not destroying prototype beans, does that mean a memory leak could occur? Or will the prototype bean get garbage-collected at some point?

The Spring documentations states:

The client code must clean up prototype-scoped objects and release expensive resources that the prototype bean(s) are holding. To get the Spring container to release resources held by prototype-scoped beans, try using a custom bean post-processor, which holds a reference to beans that need to be cleaned up.

What does that mean? The text suggests to me that I, as the programmer am responsible for explicitly (manually) destroying my prototype beans. Is this correct? If so, how do I do that?

like image 268
IqbalHamid Avatar asked Jun 04 '18 12:06

IqbalHamid


People also ask

What are prototype beans?

Prototype: means a single bean definition to any number of object instances.

How do you destroy a Java Bean?

We can use init-method and destroy-method attribute in bean configuration file to mark init method and destroy method in Java Bean. The init-method is called during initialization and the destroy-method is called during destruction.

What is destroy method in spring?

The init-method attribute specifies a method that is to be called on the bean immediately upon instantiation. Similarly, destroymethod specifies a method that is called just before a bean is removed from the container.


2 Answers

For the benefit of others, I will present below what I have gathered from my investigations:

As long as the prototype bean does not itself hold a reference to another resource such as a database connection or a session object, it will get garbage collected as soon as all references to the object have been removed or the object goes out of scope. It is therefore usually not necessary to explicitly destroy a prototype bean.

However, in the case where a memory leak may occur as described above, prototype beans can be destroyed by creating a singleton bean post-processor whose destruction method explicitly calls the destruction hooks of your prototype beans. Because the post-processor is itself of singleton scope, its destruction hook will get invoked by Spring:

  1. Create a bean post processor to handle the destruction of all your prototype beans. This is necessary because Spring does not destroy prototype beans and so any @PreDestroy hooks in your code will never get called by the container.

  2. Implement the following interfaces:

    1.BeanFactoryAware
    This interface provides a callback method which receives a Beanfactory object. This BeanFactory object is used in the post-processor class to identify all prototype beans via its BeanFactory.isPrototype(String beanName) method.

    2. DisposableBean
    This interface provides a Destroy() callback method invoked by the Spring container. We will call the Destroy() methods of all our prototype beans from within this method.

    3. BeanPostProcessor
    Implementing this interface provides access to post-process callbacks from within which, we prepare an internal List<> of all prototype objects instantiated by the Spring container. We will later loop through this List<> to destroy each of our prototype beans.


3. Finally implement the DisposableBean interface in each of your prototype beans, providing the Destroy() method required by this contract.

To illustrate this logic, I provide some code below taken from this article:

/**
* Bean PostProcessor that handles destruction of prototype beans
*/
@Component
public class DestroyPrototypeBeansPostProcessor implements BeanPostProcessor, BeanFactoryAware, DisposableBean {

    private BeanFactory beanFactory;

    private final List<Object> prototypeBeans = new LinkedList<>();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanFactory.isPrototype(beanName)) {
            synchronized (prototypeBeans) {
                prototypeBeans.add(bean);
            }
        }
        return bean;
    }

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

    @Override
    public void destroy() throws Exception {
        synchronized (prototypeBeans) {
            for (Object bean : prototypeBeans) {
                if (bean instanceof DisposableBean) {
                    DisposableBean disposable = (DisposableBean)bean;
                    try {
                        disposable.destroy();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            prototypeBeans.clear();
        }
    }
}
like image 150
IqbalHamid Avatar answered Oct 24 '22 08:10

IqbalHamid


Your answer is great. I'd also like to share some notes on an alternative solution that allows prototype members which are natively managed by the Spring IoC container lifecycle through the use of the inner beans.

I recently wrote an answer to a separate question on inner beans. Inner beans are created by assigning bean property values as BeanDefinition objects. Bean definition property values are automatically resolved to (inner) instances (as managed singleton beans) of the bean that they define.

The following XML context configuration element can be used to create distinct autowireable ForkJoinPool beans for each reference which will be managed (@PreDestroy will be called on context shutdown):

<!-- Prototype-scoped bean for creating distinct FJPs within the application -->
<bean id="forkJoinPool" class="org.springframework.beans.factory.support.GenericBeanDefinition" scope="prototype">
    <property name="beanClass" value="org.springframework.scheduling.concurrent.ForkJoinPoolFactoryBean" />
</bean>

This behavior is contingent upon the reference being assigned as a property value of a bean definition, though. This means that @Autowired- and constructor-injection do not work with this by default, since these autowiring methods resolve the value immediately rather than using the property value resolution in AbstractAutowireCapableBeanFactory#applyPropertyValues. Autowiring by type will also not work, as type-resolution will not propagate through beans that are BeanDefinitions to find the produced type.

This method will only work if either of the two conditions are true:

  • The dependent beans are also defined in XML
  • Or if the autowire mode is set to AutowireCapableBeanFactory#AUTOWIRE_BY_NAME

<!-- Setting bean references through XML -->
<beans ...>
    <bean id="myOtherBean" class="com.example.demo.ForkJoinPoolContainer">
        <property name="forkJoinPool" ref="forkJoinPool" />
    </bean>
</beans>

<!-- Or setting the default autowire mode -->
<beans default-autowire="byName" ...>
    ...
</beans>

Two additional changes could likely be made to enable constructor-injection and @Autowired-injection.

  • Constructor-injection:

    The bean factory assigns an AutowireCandidateResolver for constructor injection. The default value (ContextAnnotationAutowireCandidateResolver) could be overridden (DefaultListableBeanFactory#setAutowireCandidateResolver) to apply a candidate resolver which seeks eligible beans of type BeanDefinition for injection.

  • @Autowired-injection:

    The AutowiredAnnotationBeanPostProcessor bean post processor directly sets bean values without resolving BeanDefinition inner beans. This post processor could be overridden, or a separate bean post processor could be created to process a custom annotation for managed prototype beans (e.g., @AutowiredManagedPrototype).

like image 34
Mike Hill Avatar answered Oct 24 '22 08:10

Mike Hill