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.
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.
packagecom.spring.core.spring.bean.scopes.and.lifecycle.annotation;importorg.springframework.stereotype.Component;@ComponentpublicclassEnglishCoachimplementsCoach{@OverridepublicStringgetDailyHomeWork(){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.
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 910111213141516171819202122232425
packagecom.spring.core.spring.bean.scopes.and.lifecycle.annotation;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassSpringApplication{publicstaticvoidmain(String[]args){//Load Spring Configuration FileClassPathXmlApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//retrieve bean from the spring containerCoachenglishCoach=context.getBean("englishCoach",Coach.class);CoachenglishCoach2=context.getBean("englishCoach",Coach.class);;booleanresult=(englishCoach==englishCoach2);//Check Bean scope resultSystem.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 contextcontext.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.
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 9101112131415
packagecom.spring.core.spring.bean.scopes.and.lifecycle.annotation;importorg.springframework.context.annotation.Scope;importorg.springframework.stereotype.Component;@Component@Scope("prototype")publicclassHistoryCoachimplementsCoach{@OverridepublicStringgetDailyHomeWork(){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.
packagecom.spring.core.spring.bean.scopes.and.lifecycle.annotation;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassSpringApplication{publicstaticvoidmain(String[]args){//Load Spring Configuration FileClassPathXmlApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//retrieve bean from the spring containerCoachenglishCoach=context.getBean("englishCoach",Coach.class);CoachenglishCoach2=context.getBean("englishCoach",Coach.class);CoachhistoryCoach=context.getBean("historyCoach",Coach.class);CoachhistoryCoach2=context.getBean("historyCoach",Coach.class);booleanresult=(englishCoach==englishCoach2);booleanresult2=(historyCoach==historyCoach2);//Check Bean scope resultSystem.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 contextcontext.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.
12345678
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.
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 9101112131415161718192021
packagecom.spring.core.spring.bean.scopes.and.lifecycle.annotation;importorg.springframework.stereotype.Component;@ComponentpublicclassEnglishCoachimplementsCoach{@OverridepublicStringgetDailyHomeWork(){return"Spend 1 hour to practise Speaking Skill!";}publicvoidinitAdHocMethod(){System.out.println("EnglishCoach: The initAdHocMethod() is called!");}publicvoiddestroyAdHocMethod(){System.out.println("EnglishCoach: The destroyAdHocMethod() is called!");}}
packagecom.spring.core.spring.bean.scopes.and.lifecycle.annotation;importorg.springframework.stereotype.Component;importjavax.annotation.PostConstruct;importjavax.annotation.PreDestroy;@ComponentpublicclassEnglishCoachimplementsCoach{@OverridepublicStringgetDailyHomeWork(){return"Spend 1 hour to practise Speaking Skill!";}@PostConstructpublicvoidinitAdHocMethod(){System.out.println("EnglishCoach: The initAdHocMethod() is called!");}@PreDestroypublicvoiddestroyAdHocMethod(){System.out.println("EnglishCoach: The destroyAdHocMethod() is called!");}}
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 910
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.
Firstly, let's create the CustomBeanProcessor java class which will implement BeanPostProcessor, BeanFactoryAware and DisposableBean and override methods as below.
packagecom.spring.core.spring.bean.scopes.and.lifecycle.annotation;importorg.springframework.beans.BeansException;importorg.springframework.beans.factory.BeanFactory;importorg.springframework.beans.factory.BeanFactoryAware;importorg.springframework.beans.factory.DisposableBean;importorg.springframework.beans.factory.config.BeanPostProcessor;importorg.springframework.stereotype.Component;importjava.util.Collections;importjava.util.LinkedList;importjava.util.List;@ComponentpublicclassCustomBeanProcessorimplementsBeanPostProcessor,BeanFactoryAware,DisposableBean{privateBeanFactorybeanFactory;privatefinalList<Object>prototypeBeans=Collections.synchronizedList(newLinkedList<>());@OverridepublicvoidsetBeanFactory(BeanFactorybeanFactory)throwsBeansException{this.beanFactory=beanFactory;}@Overridepublicvoiddestroy()throwsException{// loop through the prototype beans and call the destroy() method on each oneSystem.out.println("CustomBeanProcessor: the destroy() method is called!");for(Objectbean:prototypeBeans){if(beaninstanceofDisposableBean){DisposableBeandisposable=(DisposableBean)bean;try{disposable.destroy();}catch(Exceptione){e.printStackTrace();}}}prototypeBeans.clear();System.out.println("CustomBeanProcessor: the destroy() method is finished!");}@OverridepublicObjectpostProcessAfterInitialization(Objectbean,StringbeanName)throwsBeansException{// after start up, keep track of the prototype scoped beans.// we will need to know who they are for later destructionif(beanFactory.isPrototype(beanName)){prototypeBeans.add(bean);}returnbean;}}
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.
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 91011121314151617181920
packagecom.spring.core.spring.bean.scopes.and.lifecycle.annotation;importorg.springframework.beans.factory.DisposableBean;importorg.springframework.context.annotation.Scope;importorg.springframework.stereotype.Component;@Component@Scope("prototype")publicclassHistoryCoachimplementsCoach,DisposableBean{@OverridepublicStringgetDailyHomeWork(){return"Spend 20 minutes to read history books";}@Overridepublicvoiddestroy()throwsException{System.out.println("HistoryCoach: The destroy() method is called!: "+this);}}
This destroy() method will be called from the destroy() method in the customBeanProcessor bean.
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 91011121314
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