The Lifecycle of Spring Beans

Nallathambi Ramasamy
The Startup
Published in
6 min readFeb 23, 2020
Photo by Rachel Clark on Unsplash

What are Spring Beans?

Spring Beans are Java Objects or Instances which will be created and managed by Spring IOC/DI container.

Spring will take over the control of a bean life cycle using ApplicationContext or BeanFactory interfaces.

ApplicationContext is an extension of BeanFactory interface, which is providing more useful features overBeanFactory interface.

Below, We have asked Spring to take over the control of Foo class bean life cycle using @Component annotation.

@Component
Class Foo { //variables and methods ...}

The Spring Bean Lifecycle

Let us look at the Spring Bean creation and destroy life cycle steps.

Photo by Laura Gilchrist on Unsplash

1. Bean Definition
Spring Bean will be defined using stereotype annotations or XML Bean configurations.

2. Bean Creation and Instantiate
As soon as bean created and It will be instantiated and loaded into ApplicationContext and JVM memory.

3. Populating Bean properties
Spring container will create a bean id, scope, default values based on the bean definition.

4. Post-initialization
Spring provides Aware interfaces to access application bean meta-data details and callback methods to hook into the bean life cycle to execute custom application-specific logic.

5. Ready to Serve
Now, Bean is created and injected all the dependencies and should be executed all the Aware and callback methods implementation. Bean is ready to serve.

6. Pre-destroy
Spring provides callback methods to execute custom application-specific logic and clean-ups before destroying a bean from ApplicationContext.

7. Bean Destroyed
Bean will be removed or destroyed from and JVM memory.

Hooking into the Bean Lifecycle

Let us look at the 5 different ways of tapping into the spring bean life cycle.

Photo by Herry Sucahya on Unsplash

Using Spring Aware Interfaces

Foo class implements BeanNameAware interface setBeanName() the method will provide the bean name created by the spring container.

@Component
class Foo implements BeanNameAware {
@Override
public void setBeanName(String name) {
System.out.println("Spring Set Bean Name Method Call");
}
}

Foo class implements BeanClassLoaderAware interface setBeanClassLoder() the method will notify the application, class is loaded into the current bean factory.

@Component
class Foo implements BeanClassLoaderAware {
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
System.out.println("Spring Set Bean Class Loader Method
Call");
}
}

Foo class implements BeanFactoryAware interface setBeanFactory() the method will provide the bean type and dependencies.

@Component
class Foo implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) {
System.out.println("Spring Set Bean Factory Method Call");
}
}

Foo class implements ApplicationContextAware interface setApplicationContextAware() the method will provide the application Name, Environment and the total number of beans created in the application, etc....

@Component
class Foo implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext
applicationContext) {
System.out.println("Spring Set Application Context Method
Call");
}
}

Using Spring @PostConstruct and @PreDestroy annotations

The Foo class postConstructMethod() method annotated by @PostConstruct will be called after Aware interfaces implementations execute.

The Foo class preDestroy() method annotated by @PreDestroy will be executed at the time of bean destroy.

@Component
class Foo {
@PostConstruct
public void postConstructMethod() {
System.out.println("Spring Bean Post
Construct Annotation Method ");
}
@PreDestroy
public void preDestroy() {
System.out.println("Spring Bean Pre Destroy
Annotation Method");
}
}

Using Spring InitializingBean and DisposableBean Interfaces

Foo class implements InitializingBean interface afterPropertiesSet() the method will be called after populating bean properties.

Foo class implements DisposableBean interface destroy() the method will be called at the time of bean destroy.

@Component
public class Foo implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() {
System.out.println("Spring Bean Post Contract After
Properties Set Method ");
}
@Override
public void destroy() {
System.out.println("Spring Disposable Bean
Destroy Method ");
}
}

Using @Bean annotation and Attributes

@Component
public class Foo {
public void init() {
System.out.println("Spring @Bean Initialization Method
Call");
}
public void destroy() {
System.out.println("Spring @Bean Destroy Method");
}
}

The Config class getFooInstance() method annotated by @Bean and specified init() method inside initMethod an attribute will be called at the time of bean creation.

The Config class getFooInstance() method annotated by @Bean and specified destroy() method inside destroyMethod an attribute will be called at the time of bean destroy.

@Configuration
public class Config {
@Bean(initMethod = "init", destroyMethod = "destroy")
public Foo getFooInstance() {
return new Foo();
}
}

Using XML configuration(Bean Tag)

modern-day spring applications are not using the traditional XML Bean configurations. For more information refer Spring reference manual for bean Creation and Destruction callback methods.

Why Would I Need to Hook into the Bean Lifecycle?

Below are some of the use cases.

Photo by Evan Dennis on Unsplash
  • Assigning Default values for bean properties (ex: FILE PATH, MIN_VALUE, and MAX_VALUE) while creating bean.
  • Opening and Closing the Files and Database connections as part of the bean creation and destruction.
  • Loading application Metadata (ex: US state code values) information while creating a bean and clean up on bean destruction.
  • Starting and Terminating a Process or Thread as part of the bean creation and destruction.
  • To make sure application dependency (ex: Remote Database and External Services etc...) modules are up and running while creating bean.

How It Works

Photo by Campaign Creators on Unsplash

Let’s consider an example of Default values and Files opening and closing while creating and destroying a bean with Movie Rental.

MovieRental class is used to check out the movies from NetFlix for rental. Every user will have a file with a name as a file name. It records movie Name, Date and Time for each rental.

import org.springframework.beans.factory.annotation.Value;import java.io.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
public class MovieRental {@Value("MovieRentalUser1")
private String name;
@Value("c:/MovieRental")
private String filePath;
private BufferedWriter bufferedWriter;
public void openMovieRentalFile() throws IOException {
File file = new File(filePath, name + ".txt");
bufferedWriter = new BufferedWriter(new
OutputStreamWriter(new FileOutputStream(file, true)));
}
public void movieCheckout(List<String> movieList, BufferedWriter bufferedWriter) throws IOException {
bufferedWriter.write(movieList.stream().collect(Collectors.joining(",")) + " , " + LocalDateTime.now());
}
public void closeMovieRentalFile() throws IOException {
bufferedWriter.close();
}
}
MovieRental movieRental = new MovieRental();
movieRental.movieCheckout(movieList, bufferedWriter);

Now, If we create a bean using constructor like above will result in NullPointerException.
The reason is we are not assigned any values for name, file path and also trying to write before opening the file.

Let us see how we can avoid NullPointerException applying tapping options explained in previous sections.

Annotating openMovieRentalFile() with @PostConstruct will be called during MovieRental bean creation.

Annotating closeMovieRentalFile() with @PreDestroy will be called during MovieRental bean destroy.

@PostConstruct
public void openMovieRentalFile() throws IOException { }
@PreDestroy
public void closeMovieRentalFile() throws IOException { }

Calling openMovieRentalFile() and closeMovieRentalFile() methods inside afterPropertiesSet() and destroy() callback methods.

@Override
public void afterPropertiesSet() throws IOException {
openMovieRentalFile();
}
@Override
public void destroy() throws IOException {
closeMovieRentalFile();
}

Setting @Baen initMethod attribute value as openMovieRentalFile and destroyMethod attribute value as closeMovieRentalFile.

@Bean(initMethod = "openMovieRentalFile", destroyMethod = "closeMovieRentalFile")
public MovieRental getMovieRentalInstance() {
return new MovieRental();
}

Example code Github repository.

Conclusion

It is always good vs bad practice debts about tapping into spring container internals.

Best Practise standpoint Spring Document preference is to use JSR-250 @PostConstruct and @PreDestroy annotations and then @Bean init-method and destroy-method tapping options.

Spring Document specifically suggesting not to use and tied with Spring specific InitializingBean and DisposableBean interfaces.

Spring bean life cycle callback methods are alternates for Constructor based bean setters to avoid bean dependencies and NullPointerException issues.

--

--