Spring高手之路6——Bean生命周期的扩展点:BeanPostProcessor

122 阅读10分钟

推荐课程

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 接口定义了两个方法, postProcessBeforeInitializationpostProcessAfterInitialization

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

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

; 2. BeanPostProcessor的使用

2.1 BeanPostProcessor的基础使用示例

全部代码如下:

首先定义两个简单的 BeanLionElephant

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;
    }
}

接着我们定义一个配置类,其中包含对 LionElephant类和 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;

@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();
    }
}

最后,我们在主程序中创建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();
    }
}

运行结果:

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

细心的小伙伴可能观察到这里有红色日志
信息: 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)

Spring 中, BeanPostProcessor 是被特殊处理的,它们会在其他普通 Bean 之前被实例化和初始化,这样设计的原因是 BeanPostProcessor 的存在可以影响其他 Bean 的创建和初始化过程。 Spring应用上下文中可以存在多个 BeanPostProcessorSpring本身就提供了很多内置的 BeanPostProcessor

但是,如果在初始化 BeanPostProcessor的过程中需要依赖其他的 Bean,那么这些被依赖的 Bean会先于后置处理器进行初始化。然而,由于这些被依赖的 Bean是在该 BeanPostProcessor初始化完成之前就已经进行了初始化,它们就会错过这个 BeanPostProcessor的处理。在这个例子中, MyBeanPostProcessor就是这样的一个 BeanPostProcessor,而 "animalConfig"是它所依赖的 Bean。所以这个日志信息就是说, 'animalConfig'这个 Bean在初始化的时候,没有被所有的 BeanPostProcessor处理,这里它无法得到 MyBeanPostProcessor的处理。

我们只需要把实例化过程直接交给 Spring容器来管理,而不是在配置类中手动进行实例化,就可以消除这个提示信息,也就是在 MyBeanPostProcessor上加 @Component即可。

在第 3节的例子中就使用了 @Component处理这个 MyBeanPostProcessor,这个提示就消失了。

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会怎么样,会不会因为初始化 beannull而导致异常呢?

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

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返回 nullSpring仍然用原始的 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;

@Configuration
public 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对象,然后在初始化过程中和初始化后调用 postProcessBeforeInitializationpostProcessAfterInitialization方法,修改 Lion的名字为" Simba",最后在主程序中输出 Lion对象,显示其名字为 "Simba"

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

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

在这个例子中,我们将创建一个名为 LionElephantBean,它会展示属性赋值和生命周期的各个步骤的执行顺序。同时,我们还将创建一个 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);
    }

    @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;

@Component
public 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;

@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;
    }
}

创建一个配置类 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;
    }
}

主程序:

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 lionBean Setter Method Invoked! elephant: com.example.demo.bean.Elephant@7364985f 说明 SpringLion 实例的依赖注入。在这一步, Spring 调用了 Lionsetter 方法,为 name 属性设置了值 " my lion",同时为 elephant 属性注入了一个 Elephant 实例。
  3. 然后, postProcessBeforeInitialization Method Invoked! 说明 MyBeanPostProcessorpostProcessBeforeInitialization 方法被调用,这是在初始化 Lion 实例之前。
  4. @PostConstruct Method Invoked! 说明 @PostConstruct 注解的方法被调用,这是在 Bean 初始化之后,但是在 Spring 执行任何进一步初始化之前。
  5. afterPropertiesSet Method Invoked! 说明 Spring 调用了 InitializingBeanafterPropertiesSet 方法
  6. customInitMethod Method Invoked! 表示调用了 Lion 实例的 init-method 方法。
  7. postProcessAfterInitialization Method Invoked! 说明 MyBeanPostProcessorpostProcessAfterInitialization 方法被调用,这是在初始化 Lion 实例之后。

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

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

当容器准备关闭时:

  1. @PreDestroy Method Invoked! 说明 @PreDestroy 注解的方法被调用,这是在 Bean 销毁之前。
  2. destroy Method Invoked! 表示 Lion 实例开始销毁。在这一步, Spring 调用了 DisposableBeandestroy 方法。
  3. customDestroyMethod Method Invoked! 表示 Lion 实例开始销毁,调用了 Lion 实例的 destroy-method 方法。

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

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

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

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

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

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

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

欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------