Skip to content

Spring Bean Scopes And Lifecycle Annotation#

Example Singleton Bean Scope Annotation#

  • In the example above, we had configured the singleton bean scope by xml, for now we will try the second way to configure the singleton bean scope with annotation.
  • Spring Framework had provided for us the @Scope annotation to configure the bean scopes with 2 main values are:
    • singleton. Ex: @Scope("singleton").
    • prototype. Ex: @Scope("prototype").
  • Let's take the example in the follow session below.

Dependencies#

  • We will need to add the dependency spring-context for creating the Spring Container and spring-core for using Spring Annotations for configuring the Spring Bean Scopes.
pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
....  

<dependencies>  

    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-context</artifactId>  
        <version>5.3.24</version>  
    </dependency>  

    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-core</artifactId>  
        <version>5.3.24</version>  
    </dependency>

</dependencies>

....

Configure The Spring Bean#

  • Let's create an interface and implementation class as below.
Coach.java
1
2
3
4
5
6
7
package com.spring.core.spring.bean.scopes.and.lifecycle.annotation;  

public interface Coach {  

    public String getDailyHomeWork();  

}
EnglishCoach.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.spring.core.spring.bean.scopes.and.lifecycle.annotation;  

import org.springframework.stereotype.Component;  

@Component  
public class EnglishCoach implements Coach {  

    @Override  
    public String getDailyHomeWork() {  
        return "Spend 1 hour to practise Speaking Skill!";  
    }  

}
  • As you can see, by default, we have the singleton bean scope already, so we don't need to add more any @Scope annotation. We just need to add the annotation @Component to make the Spring Framework knows that this is a bean and it need to be registered during the scanning.
  • Next, we need to update the applicationContext.xml file as below to enable the Spring bean scanning.
applicationContext.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.spring.core.spring.bean.scopes.and.lifecycle.annotation"/>

</beans>

Testing#

  • Now, let's create the SpringApplication class for getting and using bean englishCoach as below.
SpringApplication.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.spring.core.spring.bean.scopes.and.lifecycle.annotation;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {

        //Load Spring Configuration File
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //retrieve bean from the spring container
        Coach englishCoach = context.getBean("englishCoach", Coach.class);
        Coach englishCoach2 = context.getBean("englishCoach", Coach.class);;

        boolean result = (englishCoach == englishCoach2);

        //Check Bean scope result
        System.out.println("Pointing to the same object: " + result);
        System.out.println("Memory location of englishCoach: " + englishCoach);
        System.out.println("Memory location of englishCoach2: " + englishCoach2);

        //close the context
        context.close();
    }
}
  • As you can see, we will try to get bean englishCoach from the Spring Container 2 times and then we will check them together to make sure they are equal and they are loaded from the same memory area or not. So let's start the application and check the log as below.
Pointing to the same object: true
Memory location of englishCoach: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.EnglishCoach@de3a06f
Memory location of englishCoach2: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.EnglishCoach@de3a06f

Process finished with exit code 0
  • As you can see the bean englishCoach that we get from the Spring Container 2 times are the same bean and stored in the same memory address de3a06f. So it means each time we get the bean englishCoach from the Spring Container, it will check this bean existed or not, if exist Spring Container will return to us that bean and in case if doesn't exist then Spring Container will create it only one time and return to us.

Example Prototype Bean Scope Annotation#

Configure The Spring Bean#

  • Like we did in the example for Singleton scope with annotation, so let's create a new implementation class HistoryCoach as below.
HistoryCoach.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.spring.core.spring.bean.scopes.and.lifecycle.annotation;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class HistoryCoach implements Coach {

    @Override
    public String getDailyHomeWork() {
        return "Spend 20 minutes to read history books";
    }

}
  • As you can see, in this HistoryCoach class, we will use annotation @Scope("prototype"). It means the historyCoach bean will have prototype scope.

Testing#

  • Now, let's update the SpringApplication class for getting and using prototype bean historyCoach as below.
SpringApplication.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.spring.core.spring.bean.scopes.and.lifecycle.annotation;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {

        //Load Spring Configuration File
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //retrieve bean from the spring container
        Coach englishCoach = context.getBean("englishCoach", Coach.class);
        Coach englishCoach2 = context.getBean("englishCoach", Coach.class);
        Coach historyCoach = context.getBean("historyCoach", Coach.class);
        Coach historyCoach2 = context.getBean("historyCoach", Coach.class);

        boolean result = (englishCoach == englishCoach2);
        boolean result2 = (historyCoach == historyCoach2);

        //Check Bean scope result
        System.out.println("Pointing to the same object: " + result);
        System.out.println("Memory location of englishCoach: " + englishCoach);
        System.out.println("Memory location of englishCoach2: " + englishCoach2);

        System.out.println("Pointing to the same object: " + result2);
        System.out.println("Memory location of historyCoach: " + historyCoach);
        System.out.println("Memory location of historyCoach2: " + historyCoach2);

        //close the context
        context.close();
    }
}
  • As you can see, we will try to get bean historyCoach from the Spring Container 2 times and then we will check them together to see they are equal and loaded from the same memory area or not. So let's start the application and check the log as below.
1
2
3
4
5
6
7
8
Pointing to the same object: true
Memory location of englishCoach: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.EnglishCoach@166fa74d
Memory location of englishCoach2: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.EnglishCoach@166fa74d
Pointing to the same object: false
Memory location of historyCoach: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.HistoryCoach@40f08448
Memory location of historyCoach2: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.HistoryCoach@276438c9

Process finished with exit code 0
  • As you can see, the bean historyCoach that we get from the Spring Container 2 times are not the same bean and they are stored in different memory address 40f08448 and 276438c9. So it means each time we get the historyCoach from the Spring Container, It will create a new bean and return to us for using.

Example Bean Lifecycle Hooks Annotation#

Prepare#

  • To avoid duplicated steps, so in this example we will continue with the example that we did in the Example Prototype Bean Scope Annotation.

Define our methods for init and destroy#

  • So, let's go to the EnglishCoach and create 2 methods with any name. In this example they will be named initAdHocMethod and destroyAdHocMethod which are used for bean initialization and bean destruction respectively.
EnglishCoach.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.spring.core.spring.bean.scopes.and.lifecycle.annotation;

import org.springframework.stereotype.Component;

@Component
public class EnglishCoach implements Coach {

    @Override
    public String getDailyHomeWork() {
        return "Spend 1 hour to practise Speaking Skill!";
    }

    public void initAdHocMethod() {
        System.out.println("EnglishCoach: The initAdHocMethod() is called!");
    }

    public void destroyAdHocMethod() {
        System.out.println("EnglishCoach: The destroyAdHocMethod() is called!");
    }

}

Add annotations: @PostConstruct and @PreDestroy#

  • Now, let's add annotations: @PostConstruct and @PreDestroy for methods initAdHocMethod and destroyAdHocMethod respectively as below.
EnglishCoach.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.spring.core.spring.bean.scopes.and.lifecycle.annotation;

import org.springframework.stereotype.Component;

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

@Component
public class EnglishCoach implements Coach {

    @Override
    public String getDailyHomeWork() {
        return "Spend 1 hour to practise Speaking Skill!";
    }

    @PostConstruct
    public void initAdHocMethod() {
        System.out.println("EnglishCoach: The initAdHocMethod() is called!");
    }

    @PreDestroy
    public void destroyAdHocMethod() {
        System.out.println("EnglishCoach: The destroyAdHocMethod() is called!");
    }

}

Testing#

  • Finally, let's run our application and then check the console output, you should see the method initAdHocMethod() is executed before the bean englishCoach is used and the method destroyAdHocMethod() is executed before the application is actually closed.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
EnglishCoach: The initAdHocMethod() is called!
Pointing to the same object: true
Memory location of englishCoach: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.EnglishCoach@6e2c9341
Memory location of englishCoach2: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.EnglishCoach@6e2c9341
Pointing to the same object: false
Memory location of historyCoach: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.HistoryCoach@32464a14
Memory location of historyCoach2: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.HistoryCoach@4e4aea35
EnglishCoach: The destroyAdHocMethod() is called!

Process finished with exit code 0

Example With Destroy Method For Prototype Bean Annotation#

  • As we know in the Bean Lifecycle Hooks section, for the bean with prototype scope, Spring does not call the destroy method although we apply destroy-method property for XML configuration or use @PostDestroy for annotation based configuration.
  • For this case, if we want to call the destroy method on prototype scope beans, we have to add some custom codes and follow the process as below:
    • Create a custom bean processor: This bean processor will keep track of prototype scoped beans. During shutdown it will call the destroy() method on the prototype scoped beans. The custom processor is configured in the spring config file.
    • Implement DisposableBean For Prototype Beans: the prototype scoped beans MUST implement the DisposableBean interface. This interface defines a "destroy()" method.

Prepare#

  • In this example we will continue with the example that we did in the Bean Lifecycle Hooks Annotation.

Create A Custom Bean Processor#

  • Firstly, let's create the CustomBeanProcessor java class which will implement BeanPostProcessor, BeanFactoryAware and DisposableBean and override methods as below.
CustomBeanProcessor.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.spring.core.spring.bean.scopes.and.lifecycle.annotation;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

@Component
public class CustomBeanProcessor implements BeanPostProcessor, BeanFactoryAware, DisposableBean {

    private BeanFactory beanFactory;

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

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

    @Override
    public void destroy() throws Exception {
        // loop through the prototype beans and call the destroy() method on each one
        System.out.println("CustomBeanProcessor: the destroy() method is called!");
        for (Object bean : prototypeBeans) {
            if (bean instanceof DisposableBean) {
                DisposableBean disposable = (DisposableBean) bean;
                try {
                    disposable.destroy();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        prototypeBeans.clear();
        System.out.println("CustomBeanProcessor: the destroy() method is finished!");
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // after start up, keep track of the prototype scoped beans.
        // we will need to know who they are for later destruction
        if (beanFactory.isPrototype(beanName)) {
            prototypeBeans.add(bean);
        }
        return bean;
    }
}
  • As you can see, in the CustomBeanProcessor class. Firstly we will inject the bean beanFactory into the CustomBeanProcessor by overriding the method setBeanFactory.
  • Then for the method postProcessAfterInitialization, we will check every bean after initialized if the bean is the prototype bean then we will add it into a synchronized linked list.
    • Why synchronized linked list? Because we want to maintain the insertion order for calling destroy method of every bean. For example, we have two prototype beans, the first bean is initialized then we add it into the synchronized linked list. Likewise, the second prototype bean is also initialized then we also add it into the synchronized linked list. Then when the customBeanProcessor bean is going to be destroyed, then the destroy() method of it is executed, then we will loop through the prototype beans and the first bean initialized will be executed destroy method first and the second bean will be executed destroy method later.

Implement DisposableBean For Prototype Beans#

  • Now, let's use the class HistoryCoach to implement DisposableBean interface and override the method destroy() as below.
HistoryCoach.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.spring.core.spring.bean.scopes.and.lifecycle.annotation;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class HistoryCoach implements Coach, DisposableBean {

    @Override
    public String getDailyHomeWork() {
        return "Spend 20 minutes to read history books";
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("HistoryCoach: The destroy() method is called!: " + this);
    }
}
  • This destroy() method will be called from the destroy() method in the customBeanProcessor bean.

Testing#

  • Finally, let's run our application and then check the console output, you should see the destroy methods in prototype bean historyCoach() are executed when the destroy method of customBeanProcessor is executed and they are executed with the same order as they were created.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
EnglishCoach: The initAdHocMethod() is called!
Pointing to the same object: true
Memory location of englishCoach: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.EnglishCoach@12028586
Memory location of englishCoach2: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.EnglishCoach@12028586
Pointing to the same object: false
Memory location of historyCoach: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.HistoryCoach@17776a8
Memory location of historyCoach2: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.HistoryCoach@69a10787
EnglishCoach: The destroyAdHocMethod() is called!
CustomBeanProcessor: the destroy() method is called!
HistoryCoach: The destroy() method is called!: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.HistoryCoach@17776a8
HistoryCoach: The destroy() method is called!: com.spring.core.spring.bean.scopes.and.lifecycle.annotation.HistoryCoach@69a10787
CustomBeanProcessor: the destroy() method is finished!

Process finished with exit code 0

See Also#

References#