Extension point of the Bean life cycle: Bean Post Processor

Abstract: In this article, we will delve into an important component in the Spring framework - BeanPostProcessor. First, we will understand its design philosophy and goals, and then learn how to use it based on practical examples, how to change the initialization result of Bean through BeanPostProcessor and how to use it to modify the properties of Bean.

This article is shared from Huawei Cloud Community " The Road to Spring Master 6--Extension Point of Bean Life Cycle: BeanPostProcessor ", author: Zhuanyeyang__.

1.  Explore Spring's post processor (BeanPostProcessor)

1.1 Design concept of BeanPostProcessor

The design goal of BeanPostProcessor is mainly to provide an extension mechanism that allows developers to perform custom operations during the initialization phase of Spring Bean. This design concept mainly reflects an important principle of Spring, that is, the "open and closed principle". The principle of openness and closure emphasizes that software entities (classes, modules, functions, etc.) should be open for extension and closed for modification. Here, the Spring container manages the life cycle of Bean creation, initialization, destruction, etc., but at the same time opens the extension point of BeanPostProcessor, so that developers can realize the Spring Bean life cycle without modifying the Spring source code. Custom operations, this design concept greatly improves the flexibility and scalability of Spring.

BeanPostProcessor is not part of the Spring Bean life cycle, but it is a component that plays an important role in the Spring Bean life cycle .

1.2 Documentation of BeanPostProcessor

Let's take a look at the documentation comments for this method. As you can see from the figure, the BeanPostProcessor interface defines two methods, postProcessBeforeInitialization and postProcessAfterInitialization

The postProcessBeforeInitialization method will be called before any bean initialization callbacks (such as the afterPropertiesSet method of InitializingBean or a custom init-method) . In other words, this method will be called when the properties of the bean have been set, but not yet initialized.

The postProcessAfterInitialization method is called after any bean initialization callbacks (such as afterPropertiesSet of InitializingBean or custom initialization methods) . At this point, the bean's property values ​​have been filled. The returned bean instance may be a wrapper around the original bean.

2. Use of BeanPostProcessor

2.1 Basic usage example of BeanPostProcessor

The whole code is as follows:

First define two simple beans: Lion and Elephant

Lion.java

package com.example.demo.bean;
public class Lion {
 private String name;
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
}

Elephant.java

package com.example.demo.bean;
public class Elephant {
 private String name;
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
}

Then define a simple BeanPostProcessor that just prints out the name of the processed bean:

package com.example.demo.processor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("Before initialization: " + beanName);
 return bean;
 }
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("After initialization: " + beanName);
 return bean;
 }
}

Then we define a configuration class that contains the Bean definitions for the Lion, Elephant and MyBeanPostProcessor classes:

package com.example.demo.configuration;
import com.example.demo.bean.Elephant;
import com.example.demo.bean.Lion;
import com.example.demo.processor.MyBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
 @Bean
 public Lion lion() {
 return new Lion();
 }
 @Bean
 public Elephant elephant() {
 return new Elephant();
 }
 @Bean
 public MyBeanPostProcessor myBeanPostProcessor() {
 return new MyBeanPostProcessor();
 }
}

Finally, we create the ApplicationContext object in the main program:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
 public static void main(String[] args) {
 ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
 ((AnnotationConfigApplicationContext)context).close();
 }
}

operation result:

When the above code is executed, the Lion and Elephant objects will be created first, and then the postProcessBeforeInitialization and postProcessAfterInitialization methods will be called during and after initialization to print out the name of the processed Bean.

Careful friends may observe that there is a red log
message here: Bean 'animalConfig' of type [com.example.demo.configuration.AnimalConfig$$EnhancerBySpringCGLIB$$ee4adc7e] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

In Spring, BeanPostProcessor is specially treated, they will be instantiated and initialized before other ordinary beans, the reason for this design is that the existence of BeanPostProcessor can affect the creation and initialization process of other beans.  There can be multiple BeanPostProcessors in the Spring application context, and Spring itself provides many built-in BeanPostProcessors.

However, if you need to rely on other beans during the initialization of BeanPostProcessor, these dependent beans will be initialized before the post-processor. However, since these dependent beans are initialized before the BeanPostProcessor is initialized, they will miss the processing of this BeanPostProcessor. In this example, MyBeanPostProcessor is such a BeanPostProcessor, and "animalConfig" is the Bean it depends on. So this log information means that the bean 'animalConfig' was not processed by all BeanPostProcessors when it was initialized, and it cannot be processed by MyBeanPostProcessor here.

We only need to hand over the instantiation process directly to the Spring container to manage, instead of manually instantiating in the configuration class, we can eliminate this prompt, that is, add @Component to MyBeanPostProcessor.

In the example in Section 3, @Component is used to process this MyBeanPostProcessor, and this prompt disappears.

2.2 Use BeanPostProcessor to modify the return value of the Bean initialization result

Still the above example, we only modify the method of the MyBeanPostProcessor class and run it again

package com.example.demo.processor;
import com.example.demo.bean.Elephant;
import com.example.demo.bean.Lion;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("Before initialization: " + bean);
 if (bean instanceof Lion) {
 return new Elephant();
 }
 return bean;
 }
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("After initialization: " + bean);
 return bean;
 }
}

operation result:

Both methods of BeanPostProcessor can return any Object, which means we can change the returned bean in these two methods. For example, if we make the postProcessBeforeInitialization method return a new Elephant instance when it receives a Lion instance, then we will see that the Lion instance becomes an Elephant instance.

Since the two methods of BeanPostProcessor can return any Object, what will happen if I do some damage and return null? Will it cause an exception because the initialized bean is null?

The answer is no, let's take a look:

package com.example.demo.processor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("Before initialization: " + bean);
 return null;
 }
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("After initialization: " + bean);
 return bean;
 }
}

Let's run to see the result

It turns out that the bean type is still initialized normally, and there will be no change. Let's continue debugging to see why

We see through the stack frame that the last method to call the postProcessBeforeInitialization method is applyBeanPostProcessorsBeforeInitialization, double-click to see this method

As can be seen from my debugging diagram, if postProcessBeforeInitialization returns null, Spring still uses the original bean for subsequent processing, and the same logic is the same in postProcessAfterInitialization. That's why we return null in the methods of the BeanPostProcessor class, the original bean instance still exists.

2.3 Realize dynamic modification of Bean properties through BeanPostProcessor

Let's see how to intercept bean initialization

The whole code is as follows:

First, we define a Lion class:

public class Lion {
 private String name;
 public Lion() {
 this.name = "Default Lion";
 }
 public Lion(String name) {
 this.name = name;
 }
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 @Override
 public String toString() {
 return "Lion{" + "name='" + name + '\'' + '}';
 }
}

Next, we define a BeanPostProcessor, which we call MyBeanPostProcessor:

package com.example.demo.processor;
import com.example.demo.bean.Lion;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("Bean的初始化之前:" + bean);
 return bean;
 }
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("Bean的初始化之后:" + bean);
 if (bean instanceof Lion) {
 ((Lion) bean).setName("Simba");
 }
 return bean;
 }
}

Then we define a configuration class that contains the Bean definition for the Lion class and the Bean definition for the MyBeanPostProcessor class:

package com.example.demo.configuration;
import com.example.demo.bean.Lion;
import com.example.demo.processor.MyBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
 @Bean
 public Lion lion() {
 return new Lion();
 }
 @Bean
 public MyBeanPostProcessor myBeanPostProcessor() {
 return new MyBeanPostProcessor();
 }
}

Finally, we create the ApplicationContext object in the main program and get the Lion object:

package com.example.demo;
import com.example.demo.bean.Lion;
import com.example.demo.configuration.AnimalConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
 public static void main(String[] args) {
 ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
 Lion lion = context.getBean("lion", Lion.class);
 System.out.println(lion);
 ((AnnotationConfigApplicationContext)context).close();
 }
}

operation result:

When the above code is executed, first create a Lion object, then call the postProcessBeforeInitialization and postProcessAfterInitialization methods during and after initialization, modify the Lion name to "Simba", and finally output the Lion object in the main program, displaying its name as "Simba ".

3. In-depth analysis of the execution timing of BeanPostProcessor

3.1 The role and execution timing of the post-processor in the Bean life cycle

In this example, we will create a Bean named Lion and Elephant, which will show the property assignment and the execution order of the various steps of the life cycle. At the same time, we will also create a BeanPostProcessor to print the message and display its execution time.

The whole code is as follows:

First, we define our Lion:

package com.example.demo.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
public class Lion implements InitializingBean, DisposableBean {
 private String name;
 private Elephant elephant;
 public Lion() {
 System.out.println("1. Bean Constructor Method Invoked!");
 }
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 System.out.println("2. Bean Setter Method Invoked! name: " + name);
 }
 /**
     * setter注入
     * @param elephant
     */
 @Resource
 public void setElephant(Elephant elephant) {
 this.elephant = elephant;
 System.out.println("2. Bean Setter Method Invoked! elephant: " + elephant);
 }
 @PostConstruct
 public void postConstruct() {
 System.out.println("4. @PostConstruct Method Invoked!");
 }
 @Override
 public void afterPropertiesSet() throws Exception {
 System.out.println("5. afterPropertiesSet Method Invoked!");
 }
 public void customInitMethod() {
 System.out.println("6. customInitMethod Method Invoked!");
 }
 @PreDestroy
 public void preDestroy() {
 System.out.println("8. @PreDestroy Method Invoked!");
 }
 @Override
 public void destroy() throws Exception {
 System.out.println("9. destroy Method Invoked!");
 }
 public void customDestroyMethod() {
 System.out.println("10. customDestroyMethod Method Invoked!");
 }
}

Create the Elephant that Lion depends on

package com.example.demo.bean;
import org.springframework.stereotype.Component;
@Component
public class Elephant {
 private String name;
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
}

Then, we define a simple BeanPostProcessor:

package com.example.demo.processor;
import com.example.demo.bean.Lion;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 if(bean instanceof Lion) {
 System.out.println("3. postProcessBeforeInitialization Method Invoked!");
 }
 return bean;
 }
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 if(bean instanceof Lion) {
 System.out.println("7. postProcessAfterInitialization Method Invoked!");
 }
 return bean;
 }
}

Create a configuration class AnimalConfig

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 = "customInitMethod", destroyMethod = "customDestroyMethod")
 public Lion lion() {
 Lion lion = new Lion();
 lion.setName("my lion");
 return lion;
 }
}

Main program:

package com.example.demo;
import com.example.demo.bean.Lion;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
 public static void main(String[] args) {
 System.out.println("容器初始化之前...");
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.example");
 System.out.println("容器初始化完成");
 Lion bean = context.getBean(Lion.class);
 bean.setName("oh!!! My Bean set new name");
 System.out.println("容器准备关闭...");
 context.close();
 System.out.println("容器已经关闭");
 }
}

All method calls are seen on the console in the expected order, which can better understand the Bean property assignment and life cycle and the role of BeanPostProcessor.

According to the print log, we can analyze that

  1. First, Bean Constructor Method Invoked! indicates that the Lion's constructor was invoked, creating a new Lion instance.
  2. Next, Bean Setter Method Invoked! name: my lion and Bean Setter Method Invoked! elephant: com.example.demo.bean.Elephant@7364985f illustrate Spring's dependency injection on Lion instances. In this step, Spring calls Lion's setter method, sets the value "my lion" for the name property, and injects an Elephant instance for the elephant property.
  3. Then, postProcessBeforeInitialization Method Invoked! indicates that the postProcessBeforeInitialization method of MyBeanPostProcessor is called, which is before the Lion instance is initialized.
  4. @PostConstruct Method Invoked! Indicates that the @PostConstruct annotated method was invoked after the bean has been initialized but before Spring performs any further initialization.
  5. afterPropertiesSet Method Invoked! Indicates that Spring invoked the afterPropertiesSet method of InitializingBean
  6. customInitMethod Method Invoked! Indicates that the init-method method of the Lion instance was invoked.
  7. postProcessAfterInitialization Method Invoked! Indicates that the postProcessAfterInitialization method of MyBeanPostProcessor was invoked after the Lion instance was initialized.
    Spring then completes the entire initialization process.
  8. The setter method of the Lion instance is manually called in the main program, so it can be seen in Bean Setter Method Invoked! name: oh!!! My Bean set new name, the name attribute is set to a new value "oh!!! My Bean set new name ".
    When the container is ready to shut down:
  9. @PreDestroy Method Invoked! Indicates that the @PreDestroy annotated method is invoked before the Bean is destroyed.
  10. destroy Method Invoked! Indicates that the Lion instance has started to be destroyed. In this step, Spring calls the destroy method of DisposableBean.
  11. customDestroyMethod Method Invoked! Indicates that the Lion instance starts to be destroyed, and the destroy-method method of the Lion instance is invoked.

Finally, Spring completes the entire destruction process and the container shuts down.

This log provides a complete view of the Spring Bean lifecycle, showing all steps from creation to destruction.

Note: DisposableBean's destroy method and destroy-method method call, this destruction process does not mean that the bean instance is immediately deleted from memory, Java's garbage collection mechanism determines when the object is deleted from memory. The Spring container cannot force this operation, such as disassociation between beans and cleaning the cache. This is not what Spring will do when the bean is destroyed, but what the Java garbage collector does when an object is no longer referenced. .

The execution order of BeanPostProcessor is a very important part in the life cycle of Spring Bean. For example, if a Bean implements the InitializingBean interface, the afterPropertiesSet method will be called after all BeanPostProcessor's postProcessBeforeInitialization methods to ensure that all pre-processing is completed. Likewise, the BeanPostProcessor's postProcessAfterInitialization method will be called after all initialization callback methods to ensure that the Bean has been fully initialized.

We can register multiple BeanPostProcessors. In this case, Spring will invoke these post-processors in the order specified by their Ordered interface or @Order annotation. If no order is specified, then their execution order is undefined.

3.2 Diagram: Interaction sequence between Bean life cycle and post-processor

Based on the above execution results, let's summarize. The following is a sequence diagram of the Spring Bean life cycle, which describes in detail the entire process of Spring Bean from instantiation to ready-to-use, including Bean instantiation, property assignment, and life cycle methods. execution and post-processor calls.

 

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/10085299