SpringFramework — 后置处理器(postProcess)

小龙 856 2022-05-18

概述

官方文档

BeanPostProcessor是一个容器扩展点

BeanPostProcessor 接口定义了回调方法,您可以实现这些回调方法以提供自己的(或覆盖容器默认的)实例化逻辑、依赖处理 / 解析逻辑等。如果您想在 IOC 容器完成实例化、配置、初始化 bean 之后实现一些自定义逻辑,则可以注册一个或多个自定义的 BeanPostProcessor 实现。

这段话已经解释的非常清楚了,BeanPostProcessor 是一个回调机制的扩展点,它的核心工作点是在 bean 的初始化前后做一些额外的处理(预初始化 bean 的属性值、注入特定的依赖,甚至扩展生成代理对象等)。

BeanPostProcessor的执行可以指定先后顺序

您可以配置多个 BeanPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanPostProcessor 实例的运行顺序。仅当 BeanPostProcessor 实现 Ordered 接口时,才可以设置此属性。如果您编写自己的 BeanPostProcessor ,则也应该考虑实现 Ordered 接口。

与监听器一样,后置处理器也可以指定多个,并且可以通过实现 Ordered 接口,指定后置处理器工作的先后顺序。举一个比较简单的例子:如果有两个后置处理器,分别处理 IOC 容器中的 Service 层实现类,一个负责注入 Dao 层的接口,一个负责统一控制事务,那这个时候就需要先让注入 Dao 接口的后置处理器先工作,让控制事务的后置处理器往后稍稍。

BeanPostProcessor在IOC容器间互不影响

BeanPostProcessor 在 bean(或对象)实例上运行。也就是说,Spring 的 IOC 容器会实例化出一个 bean 的对象实例,然后 BeanPostProcessor 完成它的工作。 BeanPostProcessor 是按容器划分作用域的(仅在使用容器层次结构时,这种设定才有意义)。如果在一个容器中定义 BeanPostProcessor ,它将仅对该容器中的 bean 进行后置处理。换句话说,一个容器中定义的 bean 不会由另一个容器中定义的 BeanPostProcessor 进行后处理,即使这两个容器是同一层次结构的一部分。 要更改实际的 BeanDefinition 信息,您需要使用 BeanFactoryPostProcessor ,如使用 BeanFactoryPostProcessor 自定义配置元数据中的信息。

从这一长串文档中,提取出几个关键信息:BeanPostProcessor 作用于 bean 对象的创建后;不同 IOC 容器中的 BeanPostProcessor 不会互相起作用

javadoc

BeanPostProcessor 是一种工厂的回调钩子,它允许对 bean 实例进行自定义修改(例如检查 bean 实现的标记接口,或使用代理包装 bean )。 通常,通过标记接口等填充 bean 的后置处理器将实现 postProcessBeforeInitialization 方法,而使用代理包装 bean 的后置处理器通常将实现 postProcessAfterInitialization 方法。

javadoc 更倾向于教我们怎么用,它也说了,BeanPostProcessor 提供了两个回调时机:bean 的初始化之前bean 的初始化之后,它们分别适合做填充代理的工作。

BeanPostProcessor的设计

BeanPostProcessor 是一个接口,它只定义了两个方法

public interface BeanPostProcessor {
    // 初始化之前,会在任何 bean 的初始化回调(例如 InitializingBean 的 afterPropertiesSet 或自定义 init-method )之前执行
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    // 初始化之后,会在任何 bean 的初始化回调(例如 InitializingBean 的 afterPropertiesSet 或自定义 init-method )之后。
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
  • postProcessBeforeInitialization 方法会在任何 bean 的初始化回调(例如 InitializingBean 的 afterPropertiesSet 或自定义 init-method )之前执行;

  • postProcessAfterInitialization 方法会在任何 bean 的初始化回调(例如 InitializingBean 的 afterPropertiesSet 或自定义 init-method )之后。

此外,对于 postProcessAfterInitialization 方法,还可以对那些 FactoryBean 创建出来的真实对象进行后置处理

面试中如何概述BeanPostProcessor

BeanPostProcessor 是一个容器的扩展点,它可以在 bean 的生命周期过程中,初始化阶段前后添加自定义处理逻辑,并且不同的 IOC 容器之间的 BeanPostProcessor 不会相互干预

BeanPostProcess 在 Bean 生命周期的执行顺序

编写Bean

public class Dog implements InitializingBean {
    
    public void initMethod() {
        System.out.println("initMethod ...");
    }
    
    @PostConstruct
    public void postConstruct() {
        System.out.println("PostConstruct ...");
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean ...");
    }
}

编写后置处理器

public class ExecuteTimeBeanPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Dog) {
            System.out.println("postProcessBeforeInitialization ...");
        }
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Dog) {
            System.out.println("postProcessAfterInitialization ...");
        }
        return bean;
    }
}

编写ml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="dog" class="com.linkedbear.spring.postprocessor.c_executetime.bean.Dog" init-method="initMethod"/>

    <bean class="com.linkedbear.spring.postprocessor.c_executetime.config.ExecuteTimeBeanPostProcessor"/>

    <!-- 记得开注解配置,否则@PostConstruct不生效 -->
    <context:annotation-config/>
</beans>

运行程序

public class BeanPostProcessorExecuteTimeApplication {
    
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                "postprocessor/processor-executetime.xml");
        ctx.close();
    }
}

执行结果

postProcessBeforeInitialization ...
PostConstruct ...
InitializingBean ...
initMethod ...
postProcessAfterInitialization ...

从执行结果可以看出:
BeanPostProcessor#postProcessBeforeInitialization@PostConstructInitializingBeaninit-methodBeanPostProcessor#postProcessAfterInitialization

如图:

image-1652859327846

BeanPostProcessor的扩展及作用时机

BeanPostProcessor 有如下的接口扩展:

image-1652859700189

分析 BeanPostProcessor 子接口

InstantiationAwareBeanPostProcessor

javaDoc 解释

BeanPostProcessor 的子接口,它添加了实例化之前的回调,以及在实例化之后但在设置显式属性或自动装配发生之前的回调。

通常用于抑制特定目标 bean 的默认实例化,例如创建具有特殊 TargetSource 的代理(池目标,延迟初始化目标等),或实现其他注入策略,例如字段注入。 注意:此接口是专用接口,主要供框架内部使用。

建议尽可能实现普通的 BeanPostProcessor 接口,或从 InstantiationAwareBeanPostProcessorAdapter 派生,以免对该接口进行扩展。

从文档上看,它的作用有两个:

  • 拦截并替换 Bean 的默认实例化动作

  • 拦截 Bean 的属性注入和自动装配,并在此之前扩展

看一下 InstantiationAwareBeanPostProcessorAdapter 接口中定义的方法

接口方法定义

InstantialtionAwareBeanPostProcessor 中定义了 4 个方法,其中有一个方法已经过时

default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
    return null;
}
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
    return true;
}
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
        throws BeansException {
    return null;
}
// 已过时,被上面的方法代替
@Deprecated
default PropertyValues postProcessPropertyValues(
        PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
    return pvs;
}

解析一其中的方法,postProcessPropertyValues()已过期,不管它

  • postProcessBeforeInstantiation:在 Bean 实例化之前处理

    • 这个方法非常好理解:它可以拦截 bean 原本的实例化方法,转用这里的实例化方法
  • postProcessAfterInstantiation:在 bean 实例化之后处理

    • 这个方法的返回值是 boolean,它与下面的 postProcessProperties 方法有关,如果返回 falsepostProcessProperties 就不会执行
  • postProcessProperties:在赋值前触发

    • 根据文档中的意思,这个方法在属性赋值前触发,而 PropertyValues 又是一组 field - value 的键值对,由此可推断,postProcessProperties 方法最终会返回一组属性和值的 PropertyValues,让他参与 bean 的赋值环节

加入 InstantiationAwareBeanPostProcessor 后的 bean 的生命周期就是下面这样子:

image-1652929756776

重点知识:

postProcessBeforeInstantiation 方法执行完毕后,并不会再执行 postProcessProperties (换句话说,postProcessProperties 方法没有机会能再影响 postProcessBeforeInstantiation 方法创建出来的对象)

SmartInstantiationAwareBeanPostProcessor

相较于 InstantiationAwareBeanPostProcessor 只多了一个 smart,这个接口扩展了 3 个额外的方法

default Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
    return null;
}
default Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
    return null;
}
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
    return bean;
}
  • predictBeanType :预测 bean 的类型(不能预测时返回 null )

  • determineCandidateConstructors :根据 bean 对应 Class 中的构造器定义,决定使用哪个构造器进行对象实例化

    • 这个方法很重要,如果 bean 没有声明任何构造器,则此处会拿到默认的无参构造器;如果声明了多个构造器,则该处会根据 IOC 容器中的 bean 和指定的策略,选择最适合的构造器
  • getEarlyBeanReference :提早暴露出 bean 的对象引用(该方法与 bean 的循环依赖解决有关)

DestructionAwareBeanPostProcessor

顾名思义,它可以在 bean 的销毁前拦截处理。这个接口的方法定义也很简单:

void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;

default boolean requiresDestruction(Object bean) {
    return true;
}

很明显它就是一个回调的处理而已,没什么花里胡哨的。

关于这个接口的使用,在 SpringFramework 中有个蛮经典的:监听器的引用释放回调。由于 ApplicationContext 中会注册一些 ApplicationListener ,而这些 ApplicationListener 与 ApplicationContext 互相引用,所以在 IOC 容器销毁之前,就需要将这些引用断开,这样才可以进行对象的销毁和回收。

MergedBeanDefinitionPostProcessor

BeanDefinition 合并的过程中,在这个后置处理器中也有对应的拦截处理。

BeanDefinition 合并的意义是为了将父 bean 继承或者已经定义好的注入属性一块拿过来,这样就不用子 bean 再定义一次了,还有一种情况,它发生在基于注解的类继承上

public abstract class Animal {

    @Autowired
    private Person person;
}
public class Cat extends Animal {
    
    private String name;
}

这种情况下,向 IOC 容器注册 Cat 时,Spring 在底层也会把 person 需要注入的定义信息合并进去,并标注它需要自动注入处理。

MergedBeanDefinitionPostProcessor 的使用

MergedBeanDefinitionPostProcessor 的接口,它只定义了一个方法:

void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);

( 5.1 后又定义了一个 resetBeanDefinition 方法,仅用于清除 BeanFactory 内部缓存)

声明bean

声明的 bean 就是上面的一个 Animal ,一个 Cat ,当然还得有 Person :

public abstract class Animal {
    @Autowired
    private Person person;
    public Person getPerson() {
        return person;
    }
    public void setPerson(Person person) {
        this.person = person;
    }
}

@Component
public class Cat extends Animal {
    @Value("咪咪")
    private String name;
    @Override
    public String toString() {
        return "Cat {person: " + this.getPerson() + ", name: " + name + "}";
    }
}

@Component
public class Person {}

执行结果

Root bean: class [com.rsthe.beanPostProcessor.mergedBeanDefinitionPostProcessor.bean.Cat]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [F:\java_sound_code\spring\demo-spring\build\classes\java\main\com\rsthe\beanPostProcessor\mergedBeanDefinitionPostProcessor\bean\Cat.class]

postProcessMergedBeanDefinition 方法发生在 bean 的实例化之后,自动注入之前。而这个设计,就是为了在属性赋值和自动注入之前,把要注入的属性都收集好,这样才能顺利的向下执行注入的逻辑。

在 SpringFramework 中,一个非常重要的 MergeDefinitionPostProcessor 的实现,就是 AutowiredAnnotationBeanPostProcessor ,它负责给 bean 实现注解的自动注入,而注入的依据就是 postProcessMergedBeanDefinition 后整理的标记

BeanFactoryPostProcessor的使用

前面把 BeanPostProcessor 以及它的扩展都学习了一遍,现在学习对策是给 BeanDefinition 用的后置处理器了:BeanFactoryPostProcessor 。

官方文档

该接口的语义与 BeanPostProcessor 的语义相似,但有一个主要区别:BeanFactoryPostProcessor 对 Bean 的配置元数据进行操作。也就是说,IOC 容器允许 BeanFactoryPostProcessor 读取配置元数据,并有可能在容器实例化除 BeanFactoryPostProcessor 实例以外的任何 bean 之前更改它。

官方文档也说了,它操作的是** Bean 的配置元信息**。而且这里面还有一个非常关键的点:它可以在 bean 实例的初始化之前修改定义信息,换句话说,它可以对原有的 BeanDefinition 进行修改

由于 SpringFramework 中设计的所有 bean 在没有实例化之前都是以 BeanDefinition 的形式存在,如果提前修改了 BeanDefinition ,那么在 bean 的实例化时,最终创建出的 bean 就会受到影响。

javadoc 描述

允许自定义修改 ApplicationContext 中的 BeanDefinition ,以适应上下文基础 BeanFactory 的 Bean 属性值。 ApplicationContext 可以在其 Bean 的定义信息中自动检测 BeanFactoryPostProcessor 的 Bean,并在创建任何其他 Bean 之前应用它们。 BeanFactoryPostProcessor 可以与 BeanDefinition 进行交互并进行修改,但不能与 bean 的实例进行交互。这样做可能会导致 bean 实例化过早,从而违反了容器的规矩并造成了意外的副作用。如果需要与 bean 实例交互,请考虑实现 BeanPostProcessor 。

BeanFactoryPostProcessor 本身也属于 BeanFactory 中的 bean ,但是由于它的特殊性,所以 ApplicationContext 可以检查、获取它们,并且将其应用到 BeanFactory 中

BeanFactoryPostProcessor 的作用是在 BeanDefinition 已经注册到 BeanFactory 后,对 BeanDefinition 进行修改 / 配置。除此之外,BeanFactoryPostProcessor 与 BeanPostProcessor 没有任何关联,一个是影响 BeanDefinition ,一个是影响 bean 实例

BeanFactoryPostProcessor 中原则上不允许访问、创建任何 bean 实例(此时 IOC 容器还没初始化好,BeanPostProcessor 都没有准备好,会导致创建的 bean 实例产生残缺)。

用这样三段话,配合官方文档,基本上就可以把 BeanFactoryPostProcessor 的作用都解释到位了,小伙伴们如果还是觉得不好理解,也没有关系,马上下面就会通过实例来研究 BeanFactoryPostProcessor 的使用。

接口方法定义

BeanFactoryPostProcessor 中只定义了一个方法,就是对 BeanFactory 的后置处理:

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

注意看这里的设计,即便 ConfigurableListableBeanFactory 的最终实现类只有 DefaultListableBeanFactory ,这里的入参也是接口,可见依赖倒转的设计在 SpringFramework 中体现得淋漓尽致呀!

javadoc

在标准初始化之后,修改 ApplicationContext 内部的 BeanFactory 。此时所有 BeanDefinition 都将被加载,但尚未实例化任何 bean 。在此可以给 bean 覆盖或添加属性,甚至可以用于初始化 bean 。

image-1652944654113

面试中如何概述BeanFactoryPostProcessor

BeanFactoryPostProcessor 是容器的扩展点,它用于在 IOC 容器的生命周期中,所有的 BeanDefinition 都注册到 BeanFactory 后回调触发,用于访问 / 修改已经存在的BeanDefinition

BeanPostProcessor 相同,它们都是容器隔离的,不同的容器中 BeanFactoryPostProcessor 不会相互起作用的。

对比BeanPostProcessor与BeanFactoryPostProcessor

BeanPostProcessor BeanFactoryPostProcessor
处理目标 bean 实例 BeanDifinition
执行时机 bean 的初始化阶段前后(已经创建出 Bean 实例了) BeanDifinition 解析完毕,注册进 BeanFactory的阶段(Bean 还未实例化)
可操作空间 给 Bean 的属性赋值、创建代理对象等 给 BeanDifinition 中增删属性、移除 BeanDifinition 等

BeanDefinitionRegistryPostProcessor

javadoc

对标准 BeanFactoryPostProcessor 的 SPI 的扩展,允许在进行常规 BeanFactoryPostProcessor 检测之前注册其他 Bean 的定义信息。特别是, BeanDefinitionRegistryPostProcessor 可以注册其他 Bean 的定义,这些定义又定义了 BeanFactoryPostProcessor 实例。

注释中最关键的一句话:允许在 BeanFactoryPostProcessor 之前注册其他的 BeanDefinition ,这个才是重中之重!这句话想表达的 BeanDefinitionRegistryPostProcessor 的执行时机比 BeanFactoryPostProcessor 更早,BeanFactoryPostProcessor 一般只用来修改、扩展 BeanDefinition 中的信息,而 BeanDefinitionRegistryPostProcessor 则可以在 BeanFactoryPostProcessor 处理 BeanDefinition 之前,向 BeanFactory 注册新的 BeanDefinition ,甚至注册新的 BeanFactoryPostProcessor 用于下一个阶段的回调。

由于实现了 BeanDefinitionRegistryPostProcessor 的类同时也实现了 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法,所以在执行完所有 BeanDefinitionRegistryPostProcessor 的接口方法后,会立即执行这些类的 postProcessBeanFactory 方法,之后才是执行那些普通的只实现了 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法。(简单概括:扩展接口的优先执行机制

面试中如何概述BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor 是容器的扩展点,它用于在 IOC 容器生命周期中,所有 BeanDefinition 都准备好,即将加载到 BeanFactory 时触发回调,用于给 BeanFactory中添加新的 BeanDefinitionBeanDefinitionRegistryPostProcessor 也是容器隔离的,不同的容器中的 BeanDefinitionRegistryPostProcessor 不会互相起作用

三种后置处理器的对比

BeanPostProcessor BeanFactoryPostProcessor BeanDefinitionRegistryPostProcessor
处理目标 bean 实例 BeanDefinition BeanDefinition、.class文件等
执行时机 bean 的初始化阶段前后(已经创建出 Bean 对象) BeanDefinition 解析完毕注册进 BeanFactory 之后(此时 bean 未实例化) 配置文件、配置类已经解析完毕并注册进 BeanFactory,但还没有被 BeanFactoryPostProcessor处理
可操作空间 给 bean 的属性赋值、创建代理对象等 给 BeanDefinition 中增删属性、移除 BeanDefinition 等 向 BeanFactory 中注册新的 BeanDefinition

# IOC