Issue
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?
Solution
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:
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.
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();
}
}
}
Answered By - IqbalHamid