Spring @PostConstruct and @PreDestroy Example

In this article, we will discuss how to use method-level annotations @PostConstruct and @PreDestroy to customize the nature of a Bean.

The JSR-250 @PostConstruct and @PreDestroy annotations are generally considered best practices for receiving lifecycle callbacks in a modern Spring application. Using these annotations means that your beans are not coupled to Spring-specific interfaces.
 
The Spring internally uses CommonAnnotationBeanPostProcessor Class to handle JSR-250 @PostConstruct and @PreDestroy annotations.

The @PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.
 
The @PreDestroy annotation is used on methods as a callback notification to signal that the instance is in the process of being removed by the container.
 
In this article, to quickly demonstrate we use Spring 6.0.5 the latest release.

Spring @PostConstruct and @PreDestroy Example

In real-time projects, we populate a database table with some records during application startup and will delete records from the same database table during application shutdown.
In this example, we will populate the in-memory List data structure with a few user objects during application startup using the init() method annotated with @PostConstruct annotation. We will also delete user objects from the List during application shutdown using destroy() method annotated with @PreDestroy annotation.

Tools and technologies used

  • Spring Framework - 6.0.5
  • JDK - 17 or later
  • Maven - 3.2+
  • IDE - Eclipse Mars/STS

Create a Simple Maven Project

Create a simple maven project using your favorite IDE and refer below section for packaging structure. If you are new to maven then read this article How to Create a Simple Maven Project.

Project Structure

The below diagram shows a project structure for your reference -

The pom.xml File

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>net.javaguides.spring</groupId>
	<artifactId>spring-bean-lifecycle</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<properties>
		<maven.compiler.target>17</maven.compiler.target>
		<maven.compiler.source>17</maven.compiler.source>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>6.0.6</version>
		</dependency>
	</dependencies>
</project>

DatabaseInitiaizer.java

Consider the DatabaseInitiaizer bean, whose init() and destroy() methods are annotated with @PostConstruct and @PreDestroy annotations respectively.
package net.javaguides.spring;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.stereotype.Component;

@Component
public class DatabaseInitiaizer {

    private List < User > listOfUsers = new ArrayList < > ();

    @PostConstruct
    public void init() {
        User user = new User(1, "User");
        User user1 = new User(2, "Admin");
        User user2 = new User(3, "SuperAdmin");

        listOfUsers.add(user);
        listOfUsers.add(user1);
        listOfUsers.add(user2);
        System.out.println("-----------List of users added in init() method ------------");
        for (Iterator < User > iterator = listOfUsers.iterator(); iterator.hasNext();) {
            User user3 = (User) iterator.next();
            System.out.println(user3.toString());
        }
        // save to database

    }

    @PreDestroy
    public void destroy() {
        // Delete from database
        listOfUsers.clear();
        System.out.println("-----------After of users removed from List in destroy() method ------------");
        for (Iterator < User > iterator = listOfUsers.iterator(); iterator.hasNext();) {
            User user3 = (User) iterator.next();
            System.out.println(user3.toString());
        }
    }
}

Create POJO - User.java

package net.javaguides.spring;

public class User {
    private Integer id;
    private String name;

    public User() {}

    public User(Integer id, String name) {
        super();
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + "]";
    }
}

Annotation-Based Configuration - AppConfig.java

Create AppConfig class and write the following code in it.
package net.javaguides.spring;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "net.javaguides.spring")
public class AppConfig {
 
}
Note - @ComponentScan annotation scans all beans, whose class is annotated by the @Component annotation in a package, specified by the basePackages attribute. 

Running Spring Application - Application.java

Let's create a main class and run an application.
package net.javaguides.spring;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        context.close();
    }
}

Output

-----------List of users added in init() method ------------
User [id=1, name=User]
User [id=2, name=Admin]
User [id=3, name=SuperAdmin]
-----------After of users removed from List in destroy() method -------
Note that the destroy() method clears the list of user objects and nothing was displayed in a console.
As you can see, the init() and destroy() methods of the DatabaseInitiaizer bean are called only once, when the scope of a bean is a singleton (default scope).
In the case of prototype scope, the destroy method of DatabaseInitiaizer bean will not work.
The source code of this article is available on my GitHub repository https://github.com/RameshMF/spring-core-tutorial

Comments