写点什么

Bean 生命周期的扩展点:Bean Post Processor

  • 2023-06-28
    广东
  • 本文字数:7784 字

    阅读完需:约 26 分钟

Bean生命周期的扩展点:Bean Post Processor

本文分享自华为云社区《Spring 高手之路 6——Bean 生命周期的扩展点:BeanPostProcessor》,作者:砖业洋__ 。

1. 探索 Spring 的后置处理器(BeanPostProcessor)

1.1 BeanPostProcessor 的设计理念


BeanPostProcessor 的设计目标主要是提供一种扩展机制,让开发者可以在 Spring Bean 的初始化阶段进行自定义操作。这种设计理念主要体现了 Spring 的一种重要原则,即 “开放封闭原则”。开放封闭原则强调软件实体(类、模块、函数等等)应该对于扩展是开放的,对于修改是封闭的。在这里,Spring 容器对于 Bean 的创建、初始化、销毁等生命周期进行了管理,但同时开放了 BeanPostProcessor 这种扩展点,让开发者可以在不修改 Spring 源码的情况下,实现对 Spring Bean 生命周期的自定义操作,这种设计理念大大提升了 Spring 的灵活性和可扩展性。


BeanPostProcessor 不是 Spring Bean 生命周期的一部分,但它是在 Spring Bean 生命周期中起重要作用的组件

1.2 BeanPostProcessor 的文档说明


我们来看看这个方法的文档注释,从图中可以看到,BeanPostProcessor 接口定义了两个方法,postProcessBeforeInitialization 和 postProcessAfterInitialization



postProcessBeforeInitialization 方法会在任何 bean 初始化回调(如 InitializingBean 的 afterPropertiesSet 方法或者自定义的 init-method)之前被调用。也就是说,这个方法会在 bean 的属性已经设置完毕,但还未进行初始化时被调用。


postProcessAfterInitialization 方法在任何 bean 初始化回调(比如 InitializingBean 的 afterPropertiesSet 或者自定义的初始化方法)之后被调用。这个时候,bean 的属性值已经被填充完毕。返回的 bean 实例可能是原始 bean 的一个包装。


2. BeanPostProcessor 的使用

2.1 BeanPostProcessor 的基础使用示例


全部代码如下:


首先定义两个简单的 Bean:Lion 和 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; }}
复制代码


然后定义一个简单的 BeanPostProcessor,它只是打印出被处理的 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; }}
复制代码


接着我们定义一个配置类,其中包含对 Lion、Elephant 类和 MyBeanPostProcessor 类的 Bean 定义:


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;@Configurationpublic class AnimalConfig { @Bean public Lion lion() { return new Lion(); } @Bean public Elephant elephant() { return new Elephant(); } @Bean public MyBeanPostProcessor myBeanPostProcessor() { return new MyBeanPostProcessor(); }}
复制代码


最后,我们在主程序中创建 ApplicationContext 对象:


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(); }}
复制代码


运行结果:



以上代码在执行时,将先创建 Lion 和 Elephant 对象,然后在初始化过程中和初始化后调用 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法,打印出被处理的 Bean 的名字。


2.2 利用 BeanPostProcessor 修改 Bean 的初始化结果的返回值


还是上面的例子,我们只修改一下 MyBeanPostProcessor 类的方法后再次运行


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; }}
复制代码


运行结果:



BeanPostProcessor 的两个方法都可以返回任意的 Object,这意味着我们可以在这两个方法中更改返回的 bean。例如,如果我们让 postProcessBeforeInitialization 方法在接收到 Lion 实例时返回一个新的 Elephant 实例,那么我们将会看到 Lion 实例变成了 Elephant 实例。


那既然 BeanPostProcessor 的两个方法都可以返回任意的 Object,那我搞点破坏返回 null 会怎么样,会不会因为初始化 bean 为 null 而导致异常呢?


答案是不会的,我们来看一下:


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; }}
复制代码


我们运行看结果



结果发现还是正常初始化的 bean 类型,不会有任何改变,我们继续调试看看是为什么



我们通过堆栈帧看到调用 postProcessBeforeInitialization 方法的上一个方法是 applyBeanPostProcessorsBeforeInitialization,双击点开看一看这个方法



从我这个调试图中可以看到,如果 postProcessBeforeInitialization 返回 null,Spring 仍然用原始的 bean 进行后续的处理,同样的逻辑在 postProcessAfterInitialization 也是一样。这就是为什么我们在 BeanPostProcessor 类的方法中返回 null,原始 bean 实例还是存在的原因。

2.3 通过 BeanPostProcessor 实现 Bean 属性的动态修改


来看看是怎么拦截 bean 的初始化的


全部代码如下:


首先,我们定义一个 Lion 类:


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 + '\'' + '}'; }}
复制代码


接下来,我们定义一个 BeanPostProcessor,我们称之为 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; }}
复制代码


然后我们定义一个配置类,其中包含对 Lion 类的 Bean 定义和对 MyBeanPostProcessor 类的 Bean 定义:


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;@Configurationpublic class AnimalConfig { @Bean public Lion lion() { return new Lion(); } @Bean public MyBeanPostProcessor myBeanPostProcessor() { return new MyBeanPostProcessor(); }}
复制代码


最后,我们在主程序中创建 ApplicationContext 对象,并获取 Lion 对象:


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(); }}
复制代码


运行结果:



上面代码在执行时,先创建一个 Lion 对象,然后在初始化过程中和初始化后调用 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法,修改 Lion 的名字为 "Simba",最后在主程序中输出 Lion 对象,显示其名字为 "Simba"。

3. 深度剖析 BeanPostProcessor 的执行时机

3.1 后置处理器在 Bean 生命周期中的作用及执行时机


在这个例子中,我们将创建一个名为 Lion 和 Elephant 的 Bean,它会展示属性赋值和生命周期的各个步骤的执行顺序。同时,我们还将创建一个 BeanPostProcessor 来打印消息并显示它的执行时机。


全部代码如下:


首先,我们定义我们的 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!"); }}
复制代码


创建 Lion 所依赖的 Elephant


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


然后,我们定义一个简单的 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;@Componentpublic 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; }}
复制代码


创建一个配置类 AnimalConfig


package com.example.demo.configuration;import com.example.demo.bean.Lion;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class AnimalConfig { @Bean(initMethod = "customInitMethod", destroyMethod = "customDestroyMethod") public Lion lion() { Lion lion = new Lion(); lion.setName("my lion"); return lion; }}
复制代码


主程序:


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("容器已经关闭"); }}
复制代码


控制台上看到所有的方法调用都按照预期的顺序进行,这可以更好地理解 Bean 属性赋值和生命周期以及 BeanPostProcessor 的作用。



根据打印日志我们可以分析出


  1. 首先,Bean Constructor Method Invoked! 表明 Lion 的构造器被调用,创建了一个新的 Lion 实例。

  2. 接着,Bean Setter Method Invoked! name: my lion 和 Bean Setter Method Invoked! elephant: com.example.demo.bean.Elephant@7364985f 说明 Spring 对 Lion 实例的依赖注入。在这一步,Spring 调用了 Lion 的 setter 方法,为 name 属性设置了值 “my lion”,同时为 elephant 属性注入了一个 Elephant 实例。

  3. 然后,postProcessBeforeInitialization Method Invoked! 说明 MyBeanPostProcessor 的 postProcessBeforeInitialization 方法被调用,这是在初始化 Lion 实例之前。

  4. @PostConstruct Method Invoked! 说明 @PostConstruct 注解的方法被调用,这是在 Bean 初始化之后,但是在 Spring 执行任何进一步初始化之前。

  5. afterPropertiesSet Method Invoked! 说明 Spring 调用了 InitializingBean 的 afterPropertiesSet 方法

  6. customInitMethod Method Invoked! 表示调用了 Lion 实例的 init-method 方法。

  7. postProcessAfterInitialization Method Invoked! 说明 MyBeanPostProcessor 的 postProcessAfterInitialization 方法被调用,这是在初始化 Lion 实例之后。


    然后 Spring 完成了整个初始化过程。

  8. 主程序中手动调用了 Lion 实例的 setter 方法,因此在 Bean Setter Method Invoked! name: oh!!! My Bean set new name 可见,name 属性被设置了新的值 "oh!!! My Bean set new name"。


    当容器准备关闭时:

  9. @PreDestroy Method Invoked! 说明 @PreDestroy 注解的方法被调用,这是在 Bean 销毁之前。

  10. destroy Method Invoked! 表示 Lion 实例开始销毁。在这一步,Spring 调用了 DisposableBean 的 destroy 方法。

  11. customDestroyMethod Method Invoked! 表示 Lion 实例开始销毁,调用了 Lion 实例的 destroy-method 方法。


最后,Spring 完成了整个销毁过程,容器关闭。


这个日志提供了 Spring Bean 生命周期的完整视图,显示了从创建到销毁过程中的所有步骤。


注意:DisposableBean 的 destroy 方法和 destroy-method 方法调用,这个销毁过程不意味着 bean 实例就被立即从内存中删除了,Java 的垃圾收集机制决定了对象什么时候被从内存中删除。Spring 容器无法强制进行这个操作,比如解除 bean 之间的关联和清理缓存,这并不是 Spring 在销毁 bean 时会做的,而是由 Java 的垃圾回收器在一个对象不再被引用时做的事情。


BeanPostProcessor 的执行顺序是在 Spring Bean 的生命周期中非常重要的一部分。例如,如果一个 Bean 实现了 InitializingBean 接口,那么 afterPropertiesSet 方法会在所有的 BeanPostProcessor 的 postProcessBeforeInitialization 方法之后调用,以确保所有的前置处理都完成了。同样,BeanPostProcessor 的 postProcessAfterInitialization 方法会在所有的初始化回调方法之后调用,以确保 Bean 已经完全初始化了。


我们可以注册多个 BeanPostProcessor。在这种情况下,Spring 会按照它们的 Ordered 接口或者 @Order 注解指定的顺序来调用这些后置处理器。如果没有指定顺序,那么它们的执行顺序是不确定的。

3.2 图解:Bean 生命周期与后置处理器的交互时序


综合上面的执行结果,我们来总结一下,下面是 Spring Bean 生命周期的时序图,它详细地描绘了 Spring Bean 从实例化到准备使用的整个过程,包括 Bean 的实例化、属性赋值、生命周期方法的执行和后置处理器的调用。



点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 2
用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

生于云,长于云,让开发者成为决定性力量

评论

发布
暂无评论
Bean生命周期的扩展点:Bean Post Processor_后端_华为云开发者联盟_InfoQ写作社区