1. Introduction
In this tutorial, we’ll explore how the Spring Framework handles prototype beans and manages their lifecycle. Understanding how to use beans and their scopes is an important and useful aspect of application development. We’ll discover whether manually destroying prototype beans is necessary, when, and how to do it.
Although Spring provides us with various helpful bean scopes, prototype will be the main topic of this lesson.
2. Prototype Bean and Its Lifecycle
Scope determines the bean’s lifecycle and visibility within the context in which it exists. According to their defined scope, the IoC container is responsible for managing the lifecycle of beans. A prototype scope dictates that the container creates a new instance of the bean every time it’s requested using getBean() or injected into another bean. In the case of creation and initialization, we can safely rely on Spring. However, the process of destroying beans is different.
Before we check the necessity of destroying the bean, let’s first look at how to create a prototype bean:
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeExample {
}
3. Do Prototype Beans Need Manual Destruction?
Spring doesn’t automatically destroy prototype beans. Unlike singleton scope, where the IoC container takes care of the bean’s entire lifecycle, with a prototype, that’s not the case. The container will instantiate, configure, and assemble the prototype bean, but then it will stop keeping track of its state.
In Java, an object becomes eligible for garbage collection when it’s no longer reachable through any references. it’s usually sufficient to leave a prototype bean instance after its usage for the garbage collector to pick it up. In other words, we don’t have to bother destroying prototype beans in most use cases.
On the other hand, let’s consider scenarios where it’s advisable to destroy beans manually. For example while working with processes that require resources such as handling files, database connections, or networking. Since prototype scope states that a bean is created each time we use it, that means resources are utilized and consumed as well. As a result, accumulation of usage over time can lead to potential issues such as memory leaks and the exhaustion of connection pools. That happens because we never release those resources, we keep creating new ones simply by using prototype beans.
That’s why we must ensure that we properly destroy prototype beans after we use them, closing all the resources that we created or used.
4. How to Destroy Prototype Bean?
There are several ways how to destroy beans in Spring manually. it’s important to note that the container will apply each mechanism if we use multiple of them, but we need to use at least one.
Each example requires manually invoking the method destroyBean() from BeanFactory, except in the custom method approach where we can invoke our custom method. We’ll get BeanFactory from ApplicationContext and invoke bean destruction:
applicationContext.getBeanFactory().destroyBean(prototypeBean);
4.1. Using @PreDestroy Annotation
Annotation @PreDestroy is used to mark the method of our bean which is responsible for destroying the bean. A method is not allowed to have any parameters nor to be static. We’ll see how that looks in practice:
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PreDestroyBeanExample {
@PreDestroy
private void destroy() {
// release all resources that the bean is holding
}
}
4.2. DisposableBean Interface
DisposableBean interface has a singular callback method destroy(), which we have to implement. Spring team doesn’t recommend using the DisposableBean interface because it couples the code to Spring. Nevertheless, we’ll take a look at how to use it:
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class DisposableBeanExample implements DisposableBean {
@Override
public void destroy() {
// release all resources that the bean is holding
}
}
4.3. DestructionAwareBeanPostProcessor Interface
DestructionAwareBeanPostProcessor, like other BeanPostProcessor variants, customizes the initialization of beans. One key difference is that it includes an additional method to execute custom logic before destroying a bean.
Before implementing an interface, we must ensure that we have a way to release resources from our bean. We can use a DisposableBean, just as in the previous example, or a custom method.
The next step is to implement an interface where we’ll invoke our destruction method:
@Component
public class CustomPostProcessor implements DestructionAwareBeanPostProcessor {
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
if (bean instanceof PostProcessorBeanExample) {
((PostProcessorBeanExample) bean).destroy();
}
}
}
4.4. Custom Method With POJOs
There could be a scenario in which we have a POJO that we want to define as a prototype bean. While defining a bean, we can use the property destroyMethod to specify a particular method that will be responsible for destroying the bean. Let’s see how is that done:
public class CustomMethodBeanExample {
public void destroy() {
// release all resources that the bean is holding
}
}
@Configuration
public class DestroyMethodConfig {
@Bean(destroyMethod = "destroy")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public CustomMethodBeanExample customMethodBeanExample() {
return new CustomMethodBeanExample();
}
}
We successfully marked our custom method as a destroyMethod callback, but it will never be invoked. This is because the container only invokes it for beans whose lifecycle it fully controls. In scenarios like this, we could utilize DestructionAwareBeanPostProcessor or simply invoke our custom destruction method when we stop using prototype bean.
5. Conclusion
In this article, we explored what are prototype beans and how Spring handles initialization but then leaves clients to take care of the destruction.
Although it may not be necessary to manually destroy prototype beans, it’s recommended to do so if they handle resources like file handling, database connections, or networking. Since prototype bean instances are created each time we request it, we’ll stack up resources pretty quickly. To avoid any unwanted problems, such as memory leaks, we have to release resources.
We learned several approaches that we can use to destroy beans, including @PreDestroy, DisposableBean interface, DestructionAwareBeanPostProcessor interface, and custom methods.
As always, full code examples are available over on GitHub.