再谈Spring BeanPostProcessor

之前已经就spring中的BeanPostProcessor使用方法以及其实现细节谈论过,现在从更加宏观、抽象的角度去理解spring的bpp具体是如何工作的,现在spring自身有多少bpp,如果我们有自定义的bpp需求,应该如何实现。

其中如下demo的代码位置 GitHub simple-spring

目录

再谈Spring BeanPostProcessor
1、BeanPostProcessor 种类
1.1、BeanPostProcessor 接口
1.2、InstantiationAwareBeanPostProcessor 接口
1.3、MergedBeanDefinitionPostProcessor 接口
2、源码学习
2.1、实例化前的提前处理
2.2、实例化后的合并
2.3、实例化后的数据填充
2.4、初始化init方法
3、Demo

1、BeanPostProcessor 种类

image

如上图,是在IDEA中使用control+H命令显示出来的BeanPostProcessor的继承实现类关系图,重点介绍BeanPostProcessorInstantiationAwareBeanPostProcessorMergedBeanDefinitionPostProcessor三个接口,后面再继续结合实际的实现类,分析spring是如何操作BeanPostProcessor对象的

阅读这里面的源码需要对Spring 源码有一定的理解,具体的可看【目录】Spring 源码学习

1.1、BeanPostProcessor 接口

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    // 在调用bean的init方法(非构造方法)前调用
    // beanName 是在Spring IOC 容器的bean 名称,返回的对象需要会被直接使用
    // 切记!!!默认返回bean即可,不要无缘无故返回null,会出现各种NPE的情况
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
    // 在调用bean的init方法(非构造方法)后调用
}

1.2、InstantiationAwareBeanPostProcessor 接口

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;
    // 在对象实例化之前被调用,传入的参数是类型以及bean的那么
    // 如果有返回非空的对象,则意味着不需要调用doCreate操作完成对象实例化等
    // 同时返回非空后会调用postProcessAfterInitialization方法
    // 注意看,不是postProcessAfterInstantiation而是postProcessAfterInitialization方法
    boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;
     // 返回的是boolean类型,并不是和postProcessBeforeInstantiation配套使用的,在正常的实例化之后
     // 主要功能是判断是否需要完成对象填充
     // 如果返回false,则意味着不会完成对象属性的填充,例如@Resource导入的对象还是为null
    PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException;
    // 提供给已经实例化的对象一种可以自定义完成对象pv属性的修改操作
    // 注意不要随意返回null,一旦返回null,不会进行对象填充、对象依赖等操作
}

1.3、MergedBeanDefinitionPostProcessor 接口

public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {
    void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
    // 在完成bean的实例化之后,填充数据(populateBean)之前,可自定义的修改beanDefinition内容
    // 名字成为merge,合并BPP对象和对于的beanDefinition的内容
    // 实际上可以完成任何想任何实现的功能
}

以上的三个接口基本上能够覆盖所有的BPP特定的功能点,从上面图也可以看出来,spring、springboot很多模块都有相应的对象实现,完成特定的功能。

2、源码学习

2.1、实例化前的提前处理

直接定位到AbstractAutowireCapableBeanFactory类的createBean方法,主要介绍的是InstantiationAwareBeanPostProcessor

try {
    Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
    if (bean != null) {
          // 如果bean有用,则会直接返回,不会再继续执行doCreateBean方法
        return bean;
    }
}
catch (Throwable ex) {
    throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
            "BeanPostProcessor before instantiation of bean failed", ex);
    // bean创建失败,在初始化之前错误了
}

protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
    Object bean = null;
    if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
        // Make sure bean class is actually resolved at this point.
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
              // 如果存在InstantiationAwareBeanPostProcessor对象
            Class<?> targetType = determineTargetType(beanName, mbd);
            if (targetType != null) {
                bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
                // 调用的是postProcessBeforeInstantiation方法
                if (bean != null) {
                      // 如果返回的数据不为null,则调用的是postProcessAfterInitialization方法
                      // 注意别看错了,一个是实例化Instantiation,一个是初始化Initialization
                    bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
                }
            }
        }
        mbd.beforeInstantiationResolved = (bean != null);
        // 记录下是否通过提前批次的处理(如果bean不为null,则肯定是)
    }
    // 最后返回结果
    return bean;
}

其意思就是利用在实例化之前检查是否存在合适的InstantiationAwareBeanPostProcessor对象,去拦截某些需要被处理的bean提前完成bean的实例化过程,不会去调用init方法,也没有数据的填充,@Resource对象的引入等操作。

2.2、实例化后的合并

实例化后,仅仅是完成了对象最基础的实例化工作,还未涉及到填充数据,init方法执行等操作,主要介绍的是MergedBeanDefinitionPostProcessor

synchronized (mbd.postProcessingLock) {
    if (!mbd.postProcessed) {
        try {
              // 合并管理bean
            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                    "Post-processing of merged bean definition failed", ex);
        }
        mbd.postProcessed = true;
    }
}

protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof MergedBeanDefinitionPostProcessor) {
              // 遍历所有的BeanPostProcessor
              // 筛选出属于MergedBeanDefinitionPostProcessor的对象,调用postProcessMergedBeanDefinition
            MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
            bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
        }
    }
}

2.3、实例化后的数据填充

主要的操作在populateBean方法中,涉及到的也是InstantiationAwareBeanPostProcessor

再次强调InstantiationAwareBeanPostProcessor接口不仅仅是用在实例化前,在实例化之后也同样有用

boolean continueWithPropertyPopulation = true;
// 设置可以继续设置pv值的boolean对象为true
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof InstantiationAwareBeanPostProcessor) {
              // 如果是InstantiationAwareBeanPostProcessor对象
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                  // 调用postProcessAfterInstantiation
                  // 如果返回false意味着不需要填充pv操作了,直接break
                continueWithPropertyPopulation = false;
                break;
            }
        }
    }
}

if (!continueWithPropertyPopulation) {
     // 上文设置为false后,直接返回,完全忽略后续的applyPropertyValues操作
    return;
}
if (hasInstAwareBpps || needsDepCheck) {
    PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
    if (hasInstAwareBpps) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                // 对bean对象的pv值进行处理操作
                if (pvs == null) {
                      // 如果返回为null,则直接返回
                      // 所以在自定义设置BPP的时候必须注意该方法的返回值
                      // 通过ide自动生成的对象,其默认返回值是null
                    return;
                }
            }
        }
    }
    if (needsDepCheck) {
         // 检查对象的依赖问题
        checkDependencies(beanName, mbd, filteredPds, pvs);
    }
}

2.4、初始化init方法

在spring中是可以设置对象的init方法,在对象实现之后,在initializeBean方法中,如下的代码片段

Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    // 调用postProcessBeforeInitialization
}

try {
    invokeInitMethods(beanName, wrappedBean, mbd);
    // 动态代理反射invoke调用初始化方法
    // 就是在这里获取到bean的init方法调用信息,invoke调用
}
catch (Throwable ex) {
    throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
}

if (mbd == null || !mbd.isSynthetic()) {
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    // 唯一一个被调用了2次的地方的方法postProcessAfterInitialization
}
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
        throws BeansException {
    Object result = existingBean;
    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
        result = beanProcessor.postProcessBeforeInitialization(result, beanName);
        if (result == null) {
            return result;
        }
    }
    return result;
}

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
        throws BeansException {
    Object result = existingBean;
    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
        result = beanProcessor.postProcessAfterInitialization(result, beanName);
        if (result == null) {
              // 都有个这样的操作,返回为null,则退出
            return result;
        }
    }
    return result;
}

3、Demo

public class CustomInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (beanName.equals("student")) {
            Student student = new Student("bpp", 20);
            return student;
        }

        if (beanName.equals("superStudent")) {
            SuperStudent studentAndTeacher = new SuperStudent();
            studentAndTeacher.setName("zhangsan");
            return studentAndTeacher;
        }

        return null;
    }

    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return true;
    }

    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        //return pvs;
        return null;
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
public class SuperStudent {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Resource
    private Teacher teacher;

    public void doInit() {
        System.out.println("superstudent init");
    }

    public void doSet() {
        if (teacher != null) {
            teacher.setAge(19);
            teacher.setName("superStudent");
            System.out.println(teacher.toString());
        } else {
            System.out.println("teacher is null");
        }
    }

    @Override
    public String toString() {
        return "SuperStudent{" +
                "name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}
public static void runBPP() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
            new String[]{"context-bpp.xml"});
    SuperStudent student = (SuperStudent) applicationContext.getBean("superStudent");
    Teacher teacher = (Teacher) applicationContext.getBean("teacher");
    System.out.println(teacher.toString());
    System.out.println(student.toString());
    student.doSet();
    System.out.println(teacher.toString());
}

如下图几种样例的代码

  • 返回pv为null
    image

很清楚在SuperStudent的对象是null,并没有完成填充操作,但是调用了init方法

  • 返回pv为原值,不作任何改变
    image

完成了填充操作,而且这个SuperStudent的teacher对象是和Spring IOC 容器内的保持一致

  • 设置postProcessBeforeInstantiation
    image

SuperStudent返回输出的对象,teacher没有值,而且也没有调用其init的方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 1.1 spring IoC容器和beans的简介 Spring 框架的最核心基础的功能是IoC(控制反转)容器,...
    simoscode阅读 6,707评论 2 22
  • 本来是准备看一看Spring源码的。然后在知乎上看到来一个帖子,说有一群**自己连Spring官方文档都没有完全读...
    此鱼不得水阅读 6,928评论 4 21
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,369评论 1 92
  • 几年之前我家也曾经养过一只狗,出生没几天就来我家,刚来的时候,还不是很会爬高,因为我家客厅是有歩级的,它还不能爬上...
    惠芝阅读 340评论 2 2