Take you to thoroughly grasp the life cycle of Bean

Abstract: We will delve into the core part of Spring Framework - the life cycle of Spring Bean.

This article is shared from Huawei Cloud Community " The Road to Spring Master 5 - Thoroughly Mastering the Bean Life Cycle ", author: Zhuanyeyang__.

1. Understand the Bean life cycle

1.1 Phases of the life cycle

In the Spring IOC container, the life cycle of a bean is roughly as follows:

  1. Instantiation: When starting a Spring application, the IOC container creates an instance for each <bean> declared in the configuration file.
  2. Attribute assignment: After instantiation, Spring assigns values ​​to Bean attributes through the reflection mechanism.
  3. Call the initialization method: If the bean is configured with an initialization method, Spring will call it. The initialization method is called after the Bean is created and assigned. You can write some business processing code or do some initialization work in this method.
  4. Bean runtime: At this point, the Bean is ready to be used by the program, it has been initialized and assigned.
  5. Application Shutdown: When shutting down the IOC container, Spring processes the Bean configured with the destroy method.
  6. Call the destruction method: If the bean is configured with the destruction method, Spring will call it before all the beans have been used and the IOC container is closed. You can do some resource release work in the destruction method, such as closing the connection and clearing the cache.

This is how the Spring IOC container manages the life cycle of beans, helps us manage the creation and destruction of objects, and does the right thing at the right time.

We can call the triggering of the life cycle a callback, because the method of the life cycle is defined by ourselves, but the invocation of the method is done by the framework for us, so it can be called a "callback".

2. Understand init-method and destroy-method

Let's start with one of the easiest lifecycle phases to understand: the initialization and destroy methods. These methods can work during the bean's initialization and destruction phases, and we demonstrate this approach through examples.

In order to facilitate the demonstration of XML and annotation methods, next we will create two classes to demonstrate separately, namely Lion and Elephant, let us compare and observe step by step.

2.1 Create a Bean from the XML configuration to see the life cycle

First create a class Lion

package com.example.demo.bean;
public class Lion {
 private String name;
 public void setName(String name) {
 this.name = name;
 }
 public void init() {
 System.out.println(name + " has been initialized...");
 }
 public void destroy() {
 System.out.println(name + " has been destroyed...");
 }
}

In XML, we use the <bean> tag to register Lion:

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean class="com.example.demo.bean.Lion"
 init-method="init" destroy-method="destroy">
 <property name="name" value="simba"/>
 </bean>
</beans>

plus the main program

package com.example.demo.application;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@ComponentScan("com.example")
public class DemoApplication {
 public static void main(String[] args) {
 System.out.println("Spring容器初始化开始");
 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
 System.out.println("Spring容器初始化完成。");
 System.out.println("==================");
 System.out.println("Spring容器准备关闭");
 context.close();
 System.out.println("Spring容器已关闭。");
 }
}

operation result

In the <bean> tag, there are two attributes: init-method and destroy-method, which are used to specify the initialization and destruction methods.

Here "simba has been initialized...", which proves that the init() method has been called. When context.close() is called, see "simba has been destroyed...", which proves that the destroy() method has been called.

Before the IOC container is initialized, the Bean has been created by default and the initialization action has been completed; when the container invokes the destroy action, all Beans are destroyed first, and finally the IOC container is completely destroyed.

This example shows the Spring bean lifecycle through a simple Spring application. We can use these lifecycle methods as needed while creating the bean.

2.2 Create a Bean from the configuration class annotation configuration to see the life cycle

Create another class Elephant here to compare with the above

package com.example.demo.bean;
public class Elephant {
 private String name;
 public void setName(String name) {
 this.name = name;
 }
 public void init() {
 System.out.println(name + " has been initialized...");
 }
 public void destroy() {
 System.out.println(name + " has been destroyed...");
 }
}

For annotations, there are similar attributes in @Bean annotations: initMethod and destroyMethod, these two attributes have the same function as in XML configuration.

package com.example.demo.configuration;
import com.example.demo.bean.Elephant;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource("classpath:applicationContext.xml")
public class AnimalConfig {
 @Bean(initMethod = "init", destroyMethod = "destroy")
 public Elephant elephant() {
 Elephant elephant = new Elephant();
 elephant.setName("Dumbo");
 return elephant;
 }
}

Here, @ImportResource("classpath:applicationContext.xml") is used to introduce xml configuration to create beans for comparison.

The main program is changed to the following:

package com.example.demo.application;
import com.example.demo.configuration.AnimalConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.example")
public class DemoApplication {
 public static void main(String[] args) {
 System.out.println("Spring容器初始化开始");
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
 System.out.println("Spring容器初始化完成。");
 System.out.println("==================");
 System.out.println("Spring容器准备关闭");
 context.close();
 System.out.println("Spring容器已关闭。");
 }
}

operation result

Note: In Spring, if a Bean is defined in the Java configuration and a Bean with the same id or name is defined in the XML, the last registered Bean will override the previously registered one, depending on the configuration file loading order, Regardless of the initMethod or destroyMethod defined in the Java configuration or XML configuration, the last effective one is always defined in the post-loaded configuration.

"init-method" is the collective term for attributes that specify the initialization callback method, whether it is used in XML configuration or Java configuration. Likewise, "destroy-method" is a collective term for attributes that specify the destruction callback method. Later, when we explain the coexistence of multiple statement cycles, we will continue this statement.

2.3 Characteristics of initialization and destruction methods

When configuring Bean initialization and destruction methods in the Spring framework, you need to configure these methods according to Spring specifications, otherwise Spring may not be able to call them correctly. An explanation and examples are provided below for each feature:

1. Unrestricted access to the method: This means that Spring can call it no matter whether the method is public, protected or private. Spring invokes these methods through reflection, so it can ignore Java's access restrictions. Example:

public class MyBean {
 private void init() {
 // 初始化代码
 }
}

In the above code, even if the init method is private, Spring can call it normally.

2. The method has no parameters: Since Spring does not know what parameters need to be passed to these methods, these methods cannot have parameters. Example:

public class MyBean {
 public void init() {
 // 初始化代码
 }
}

In the above code, the init method has no parameters. If parameters are added, such as public void init(String arg), Spring will not be able to call this method.

3. The method has no return value: Since the returned value is meaningless to Spring, these methods should not have a return value. Example:

public class MyBean {
 public void init() {
 // 初始化代码
 }
}

In the above code, the init method is void. If you let this method return a value, such as public String init(), then Spring will ignore this return value.

4. Methods can throw exceptions: If errors occur during initialization or destruction, these methods can throw exceptions to notify Spring. Example:

public class MyBean {
 public void init() throws Exception {
 // 初始化代码
 if (somethingGoesWrong) {
 throw new Exception("Initialization failed.");
 }
 }
}

In the above code, if there is an error in the initialization code in the init method, it will throw an exception. The Spring framework will stop the creation of beans by default and throw an exception.

2.4 Explore the sequence of Bean initialization process

In the above code, we can see that the bean has been created and initialized in the IOC container initialization phase, so how does the initialization action of each bean proceed? Let's modify Lion to add console printing to the constructor and setName method, so that when these methods are called, they will get feedback on the console.

package com.example.demo.bean;
public class Lion {
 private String name;
 public Lion() {
 System.out.println("Lion's constructor is called...");
 }
 public void setName(String name) {
 System.out.println("setName method is called...");
 this.name = name;
 }
 public void init() {
 System.out.println(name + " has been initialized...");
 }
 public void destroy() {
 System.out.println(name + " has been destroyed...");
 }
}

We rerun the main program:

@ComponentScan("com.example")
public class DemoApplication {
 public static void main(String[] args) {
 System.out.println("Spring容器初始化开始");
 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
 System.out.println("Spring容器初始化完成。");
 System.out.println("==================");
 System.out.println("Spring容器准备关闭");
 context.close();
 System.out.println("Spring容器已关闭。");
 }
}

operation result

We can draw a conclusion: in the life cycle of the Bean, the attribute assignment is performed first, and then the method marked by init-method is executed.

3. @PostConstruct和@PreDestroy

In the JSR250 specification, there are two annotations related to the Bean life cycle, namely @PostConstruct and @PreDestroy. These two annotations correspond to the initialization and destruction phases of the Bean.

The method marked with the @PostConstruct annotation will be called after the bean properties are set (that is, dependency injection is completed), but before the bean is exposed to the outside world (that is, it can be referenced by other beans). This timing is usually used to complete some initialization work.

The method marked with the @PreDestroy annotation will be called before the Spring container destroys the bean, which is usually used to release resources.

3.1 Example: Use of @PostConstruct and @PreDestroy

We still use the Lion class to create this example here, and modify the Lion class to use @PostConstruct and @PreDestroy annotations

package com.example.demo.bean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class Lion {
 private String name;
 public void setName(String name) {
 this.name = name;
 }
 @PostConstruct
 public void init() {
 System.out.println("Lion is going through init.");
 }
 @PreDestroy
 public void destroy() {
 System.out.println("Lion is going through destroy.");
 }
 @Override
 public String toString() {
 return "Lion{" + "name=" + name + '}';
 }
}

Add the @Component annotation to the Lion class and let the IOC container manage this class. We will not add the Elephant class here to increase the difficulty of understanding.

The methods annotated with @PostConstruct and @PreDestroy have the same initialization and destruction requirements as the init-method/destroy-method methods. There are no restrictions on access modifiers, and private ones are also available.

We can comment out the previous configuration class and XML configuration, because it has nothing to do with the example here, let's take a look at the main program:

package com.example.demo.application;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
 public static void main(String[] args) {
 System.out.println("Spring容器初始化开始");
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.example.demo.bean");
 System.out.println("Spring容器初始化完成。");
 System.out.println("==================");
 System.out.println("Spring容器准备关闭");
 context.close();
 System.out.println("Spring容器已关闭。");
 }
}

operation result

Here you can see that the @PostConstruct and @PreDestroy annotations are correctly applied during Lion's initialization and destruction.

3.2 Initialization and Destruction - Annotation and init-method coexistence comparison

How do @PostConstruct and @PreDestroy annotations coexist with init-method/destroy-method attributes? let's see

We only use the Lion class as an example, and add new open() and close() methods to the Lion class

All the code needed is as follows:

Lion.java

package com.example.demo.bean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class Lion {
 private String name;
 public Lion() {
 System.out.println("Lion构造器");
 }
 public void setName(String name) {
 System.out.println("Lion设置name");
 this.name = name;
 }
 public void open() {
 System.out.println("配置类initMethod - 打开Lion。。。");
 }
 public void close() {
 System.out.println("配置类destroyMethod - 关闭Lion。。。");
 }
 @PostConstruct
 public void init() {
 System.out.println("@PostConstruct - Lion正在进行初始化。。。");
 }
 @PreDestroy
 public void destroy() {
 System.out.println("@PreDestroy - Lion正在进行销毁。。。");
 }
 @Override
 public String toString() {
 return "Lion{" + "name=" + name + '}';
 }
}

Configuration class AnimalConfig.java

package com.example.demo.configuration;
import com.example.demo.bean.Lion;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
 @Bean(initMethod = "open", destroyMethod = "close")
 public Lion lion() {
 return new Lion();
 }
}

main program

package com.example.demo.application;
import com.example.demo.configuration.AnimalConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
 public static void main(String[] args) {
 System.out.println("Spring容器初始化开始");
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
 System.out.println("Spring容器初始化完成。");
 System.out.println("==================");
 System.out.println("Spring容器准备关闭");
 context.close();
 System.out.println("Spring容器已关闭。");
 }
}

operation result

Here you can see that the @PostConstruct and @PreDestroy annotations always have higher priority than the initMethod and destroyMethod attributes of the @Bean annotation in the configuration class.

4. Implement the InitializingBean and DisposableBean interfaces

These two interfaces are two interfaces about life cycle predefined by Spring. They are triggered at the same time as the init-method/destroy-method above and the annotations of the JSR250 specification, and they are all called back during the initialization and destruction phases of the Bean. The following demonstrates how to use these two interfaces.

4.1 Example: Implementing InitializingBean and DisposableBean interfaces

To create a Bean, we let the Lion class implement these two interfaces:

Lion.java

package com.example.demo.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class Lion implements InitializingBean, DisposableBean {
 private Integer energy;
 @Override
 public void afterPropertiesSet() throws Exception {
 System.out.println("狮子已经充满能量。。。");
 this.energy = 100;
 }
 @Override
 public void destroy() throws Exception {
 System.out.println("狮子已经消耗完所有能量。。。");
 this.energy = 0;
 }
 @Override
 public String toString() {
 return "Lion{" + "energy=" + energy + '}';
 }
}

The InitializingBean interface has only one method: afterPropertiesSet(). In the Spring framework, this method is called when all properties of a bean have been set. That is, once the bean is initialized, Spring will call this method. We can do some custom initialization after all the properties of the bean are set.

The DisposableBean interface also has only one method: destroy(). This method will be called when the Spring container shuts down and destroys the bean. We can do some cleanup before the bean is destroyed.

Main program:

package com.example.demo.application;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
 public static void main(String[] args) {
 System.out.println("Spring容器初始化开始");
 AnnotationConfigApplicationContext context
 = new AnnotationConfigApplicationContext("com.example.demo.bean");
 System.out.println("Spring容器初始化完成。");
 System.out.println("==================");
 System.out.println("Spring容器准备关闭");
 context.close();
 System.out.println("Spring容器已关闭。");
 }
}

operation result:

4.2 Three life cycles coexist

In the Spring framework, the three ways to control the life cycle of beans are:

  1. Use Spring's init-method and destroy-method (customized initialization and destruction methods in XML configuration or Java configuration);
  2. Use the @PostConstruct and @PreDestroy annotations of the JSR-250 specification;
  3. Implement Spring's InitializingBean and DisposableBean interfaces.

Next, let's test that a Bean defines the init-method and destroy-method methods at the same time, uses @PostConstruct, @PreDestroy annotations, and implements the InitializingBean and DisposableBean interfaces. What is the execution order?

We create a new class Lion2, and perform lifecycle control in three ways at the same time:

All the code that needs to be run is as follows:

package com.example.demo.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class Lion2 implements InitializingBean, DisposableBean {
 private Integer energy;
 public void open() {
 System.out.println("init-method - 狮子开始行动。。。");
 }
 public void close() {
 System.out.println("destroy-method - 狮子结束行动。。。");
 }
 @PostConstruct
 public void gainEnergy() {
 System.out.println("@PostConstruct - 狮子已经充满能量。。。");
 this.energy = 100;
 }
 @PreDestroy
 public void loseEnergy() {
 System.out.println("@PreDestroy - 狮子已经消耗完所有能量。。。");
 this.energy = 0;
 }
 @Override
 public void afterPropertiesSet() throws Exception {
 System.out.println("InitializingBean - 狮子准备行动。。。");
 }
 @Override
 public void destroy() throws Exception {
 System.out.println("DisposableBean - 狮子行动结束。。。");
 }
}

Next, we register Lion2:

package com.example.demo.configuration;
import com.example.demo.bean.Lion2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
 @Bean(initMethod = "open", destroyMethod = "close")
 public Lion2 lion2() {
 return new Lion2();
 }
}

Then let the annotation IOC container drive this configuration class, the main program is as follows:

package com.example.demo.application;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
 public static void main(String[] args) {
 System.out.println("Spring容器初始化开始");
 AnnotationConfigApplicationContext context
 = new AnnotationConfigApplicationContext("com.example.demo");
 System.out.println("Spring容器初始化完成。");
 System.out.println("==================");
 System.out.println("Spring容器准备关闭");
 context.close();
 System.out.println("Spring容器已关闭。");
 }
}

operation result:

From the above results, we can draw the following conclusions that the initialization and destruction process of a single instance bean in the Spring framework has such an execution order:

Initialization sequence: @PostConstruct → InitializingBean → init-method
Destruction sequence: @PreDestroy → DisposableBean → destroy-method

When initializing a bean, the @PostConstruct annotation method will be executed first, then the afterPropertiesSet method that implements the InitializingBean interface, and finally the method specified by init-method.

When destroying a bean, the @PreDestroy annotation method will be executed first, then the destroy method that implements the DisposableBean interface, and finally the method specified by destroy-method

Combined with the attribute assignment (constructor method and setter method) mentioned above, briefly summarize the process of the Spring Bean life cycle:

  1. Instantiate (via constructor method);
  2. Set the properties of the Bean (through the setter method);
  3. Call the Bean's initialization method (@PostConstruct, afterPropertiesSet method or the method specified by init-method);
  4. Bean can be used by the application;
  5. When the container is closed, the Bean's destruction method (@PreDestroy, destroy method or method specified by destroy-method) is called.

5. The life cycle of the prototype bean

The creation and initialization process of a prototype bean is similar to that of a singleton bean, but due to the nature of the prototype bean, its life cycle is not the same as that of the IOC container.

Here is all the code needed.

Lion2.java

package com.example.demo.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class Lion2 implements InitializingBean, DisposableBean {
 private Integer energy;
 public void roar() {
 System.out.println("The lion is roaring...");
 }
 public void rest() {
 System.out.println("The lion is resting...");
 }
 @PostConstruct
 public void gainEnergy() {
 System.out.println("@PostConstruct - 狮子已经充满能量。。。");
 this.energy = 100;
 }
 @PreDestroy
 public void loseEnergy() {
 System.out.println("@PreDestroy - 狮子已经消耗完所有能量。。。");
 this.energy = 0;
 }
 @Override
 public void afterPropertiesSet() throws Exception {
 System.out.println("InitializingBean - 狮子准备行动。。。");
 }
 @Override
 public void destroy() throws Exception {
 System.out.println("DisposableBean - 狮子行动结束。。。");
 }
}

Then declare and set it as a prototype bean in Spring's Java configuration

package com.example.demo.configuration;
import com.example.demo.bean.Lion2;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class PrototypeLifecycleConfiguration {
 @Bean(initMethod = "roar", destroyMethod = "rest")
 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
 public Lion2 lion() {
 return new Lion2();
 }
}

If we just started the IOC container, but did not request an instance of Lion2, the initialization of Lion Bean will not happen immediately. In other words, the prototype bean will not be initialized with the startup of the IOC container. The following is the code that starts the container but does not request the bean:

package com.example.demo.application;
import com.example.demo.configuration.PrototypeLifecycleConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
 public static void main(String[] args) {
 System.out.println("Spring容器初始化开始");
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
 PrototypeLifecycleConfiguration.class);
 }
}

operation result:

When we explicitly request an instance of Lion2, we will see that all initialization methods are executed in a predetermined order, which is exactly the same as that of a singleton bean:

package com.example.demo.application;
import com.example.demo.bean.Lion2;
import com.example.demo.configuration.PrototypeLifecycleConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
 public static void main(String[] args) {
 System.out.println("Spring容器初始化开始");
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
 PrototypeLifecycleConfiguration.class);
 System.out.println("Ready to get a Lion instance...");
 Lion2 lion = context.getBean(Lion2.class);
 System.out.println("A Lion instance has been fetched...");
 System.out.println("Lion instance is no longer needed, preparing to destroy...");
 context.getBeanFactory().destroyBean(lion);
 System.out.println("Lion instance has been destroyed...");
 }
}

operation result:

After comparing the three life cycles of the prototype Bean and the singleton Bean, it is found that when the destroyBean() method of the IOC container is called to destroy the prototype bean, only the @PreDestroy annotation and the destroy method of the DisposableBean interface will be triggered and marked by the destroy-method The custom destroy method of will not be executed.

From here we can conclude that when destroying the prototype bean, Spring will not execute the custom destruction method marked by destroy-method, so the destroy-method of the prototype bean also has limitations. If there is important cleanup logic that needs to be executed when the Bean is destroyed, then this part of the logic should be placed in the @PreDestroy annotated method or the destroy method of the DisposableBean interface.

6. Summary of three ways to control the Bean life cycle in Spring

 

Click to follow and learn about Huawei Cloud's fresh technologies for the first time~

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4526289/blog/10083868