在spring管理Bean的初始化过程中,除了正常管理bean的实例化(初始化、参数注入等)外,还对外提供了丰富的对Bean操作的扩展。例如自定义初始化操作,自定义容器退出时Bean的销毁操作等等。这段时间看源码觉得此方面最实际的一个例子就是,我们在Bean初始化之前以及之后,框架允许我们做一些统一性的逻辑操作。BeanPostProcessor就完成了这个功能,它能够在装配Bean的过程中动态去改变Bean的行为,达到对Bean的增强。首先需要了解整个spring容器对bean初始化的流程,其流程图如下:

上图中已经完成了容器的初始化(对配置文件的加载和解析),已经进入到实例化阶段。能够看出BeanPostProcessor在整个流程中所处的位置,它正好围绕着bean自定义初始化方法前后。那么有一个问题,就是既然能够使用afterPropertySet或者init-method来自行初始化我们的bean,那么为什么还要这些BeanPostProcessor呢?因为afterPropertySet和init-method更关注自身的业务逻辑处理,而BeanPostProcessor更加通用,比如是否需要感知当前环境的上下文等更具有通用性的逻辑。

一、使用BeanPostProcessor来完成Bean实例化前、后处理

  BeanPostProcessor接口定义了两个方法,postProcessBeforeInitialization和postProcessAfterInitialization。一看名字便一目了然它们各自的作用,那就是在初始化的前后进行预处理和后处理。问题的关键在于容器启动后,BeanFactory是怎么感知到这些BeanPostProcessor的存在呢?或者说我们自定义的BeanPostProcessor怎么能够集成到框架中,让容器感知和使用我们自定义的processor?首先,我们要找到BeanFactory对BeanPostProcessor的处理逻辑代码,这段代码在AbstractAutowireCapableBeanFactory类的initializeBean方法中:

  1. Object wrappedBean = bean; //进入initializeBean方法之前,已经构造了一个Bean对象,不过未经初始化
  2. if (mbd == null || !mbd.isSynthetic()) {
  3. wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); //前处理
  4. }
  5.  
  6. try {
  7. invokeInitMethods(beanName, wrappedBean, mbd);//这里有机会调用Bean的afterPropertySet方法以及自定义的初始化方法
  8. }
  9. catch (Throwable ex) {
  10. throw new BeanCreationException(
  11. (mbd != null ? mbd.getResourceDescription() : null),
  12. beanName, "Invocation of init method failed", ex);
  13. }
  14.  
  15. if (mbd == null || !mbd.isSynthetic()) {
  16. wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);//后处理
  17. }
  18. return wrappedBean;

再进一步看applyBeanPostProcessorsBeforeInitialization方法。

  1. Object result = existingBean;
  2. // 在这里容器会遍历所有已经注册的BeanPostProcessor,无论是spring框架自身的还是我们自定义的。
  3. for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) {
  4. BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next();
  5. result = beanProcessor.postProcessBeforeInitialization(result, beanName);
  6. }
  7. return result;

可以看到,我们完全有机会在afterPropertySet方法之前就对Bean进行操作,我们只要在BeanFactory中注册我们的BeanPostProcessor(在BeanFactory中注册我们的processor),那么就会在BeanFactory加载Bean的过程中,回调我们自己的BeanPostProcessor来完成我们的动作(上面代码中循环查找所有注册的BeanPostProcessor)。不过使用BeanPostProcessor带来的一个问题就是任何Bean的加载都会调用你注册过的这个processor,所以一些个性化的业务初始化操作放在这里是不合适的,业务初始化操作应该放到afterPropertySet(需要实现InitializingBean接口,侵入性较大)或者自定义初始化方法(需要在bean配置文件中指定init-method参数,侵入性较小)中完成。所以,BeanPostProcessor的使用应该是通用的业务逻辑处理。比如我们经常使用到的自动绑定(Autowired)就是使用BeanPostProcessor来完成的。我们来简单看一下处理autowired的这个processor是如何完成自动绑定成员变量和方法参数的。Autowired的processor类是:

  1. AutowiredAnnotationBeanPostProcessor.class

autowired的过程都是在bean实例化之后做的,所以我们看postProcessAfterInitialization方法的具体实现。

  1. // 根据当前bean对象的class信息得到使用注解autowired的成员变量和方法
  2. InjectionMetadata metadata = findAutowiringMetadata(bean.getClass());
  3. try {
  4. /*
  5. // 实现了将容器中的bean注入到当前对象成员变量的过程。这里之所以能够拿到容器中的其他bean,是因为AutowiredAnnotationBeanPostProcessor实现了BeanFactoryAware接口,从而得到当前容器的BeanFactory。另外具体看进去里面的实现,autowired的实现实现方式是通过byType实现的。
  6.  
  7. */
  8. metadata.injectFields(bean, beanName);
  9. }
  10. catch (Throwable ex) {
  11. throw new BeanCreationException(beanName, "Autowiring of fields failed", ex);
  12. }
  13. return true;

spring对bean进行初始化的过程默认使用set方法注入。而且属性注入是在初始化bean之前(参照AbstractAutowireCapableBeanFactory类的doCreateBean方法),也就是说我们的autowired方式应该是在set方法注入之后,autowired是会覆盖set方法注入的属性。我们可以做以下实验来验证我们的猜测:我们在Apple中准备注入另一个Bean seed,同时使用了set方法和autowired。Apple类中代码部分如下:

  1. @Autowired
  2. @Qualifier("seed1")
  3. private Seed seed;
  4.  
  5. public Sort getSeed() {
  6. return seed;
  7. }
  8.  
  9. public void setSeed(Seed seed) {
  10. this.seed = seed;
  11. }

配置文件如下:

  1. <bean id="apple" class="com.alibaba.china.Apple" >
  2. <property name="seed">
  3. <ref bean="seed0" />
  4. </property>
  5. </bean>
  6.  
  7. <bean id="seed0" class="com.alibaba.china.Seed" >
  8. <property name="seedName">
  9. <value>苹果种子</value>
  10. </property>
  11. </bean>
  12.  
  13. <bean id="seed1" class="com.alibaba.china.Seed">
  14. <property name="seedName">
  15. <value>Apple Seed</value>
  16. </property>
  17. </bean>

  我们用set方法注入seed0,用自动绑定的方式注入seed1。由于BeanPostProcessor在初始化过程中执行,在set属性注入之后,所以预期结果seed1被注入。但是运行结果发现是seed0注入了apple对象。

  为什么会这样呢?经过调试发现,在使用set方法属性注入的过程之前,会处理一类特殊的BeanPostProcessor------InstantiationAwareBeanPostProcessor,它是BeanPostProcessor的扩展接口,但是容器会在set注入之前优先检查是否存在这类BeanPostProcessor,如果存在InstantiationAwareBeanPostProcessor先执行这类processor。而恰恰巧合的是,AutowiredAnnotationBeanPostProcessor是InstantiationAwareBeanPostProcessor的子类。所以autowired的动作在set方法前做了,set方法的属性覆盖了autowired的属性,所以在上述例子中seed0最终被成功注入到apple这个对象里面。我们用类图能更加形象地描述InstantiationAwareBeanPostProcessor与BeanPostProcessor接口:

当获取一个bean对象时,主要逻辑在AbstractAutowireCapableBeanFactory类的doCreateBean方法中。其父类中维护一个beanPostProcessors列表,我们可以在容器启动时加入我们的processor,在执行到doCreateBean中我们的processor会被回调。我们根据具体的需要去实现BeanPostProcessor接口或者InstantiationAwareBeanPostProcessor接口。前者主要是初始化前后的处理动作,而后者主要是执行实例化前后的逻辑。

ApplicationContext更加简单地使用BeanPostProcessor:

  如果我们直接使用BeanFactory,需要把我们的BeanPostProcessor注册到BeanFactory上,否则BeanFactory感知不到我们的processor。而ApplicationContext则在此方面做了增强,我们只需在配置文件里面声明我们的BeanPostProcessor,ApplicationContext能够自动做到感知。ApplicationContext是如何做到的呢?我们不妨自己先猜想一下,如果让我们自己实现这个增强功能应该怎么做?大致方向就是自己去扫描配置文件,看所有的bean中有哪些是BeanPostProcessor,然后我们不分青红皂白一股脑把所有的BeanPostProcessor都注册进去。spring对ApplicationContext也正是这么实现的,具体的描述在另一片分享中更具体的提到:

二、

spring源码:BeanPostProcessor(li)的更多相关文章

  1. 2.spring源码-BeanPostProcessor后置处理之ApplicationContextAwareProcessor,实现spring容器中某一个类的bean对象在初始化时需要得到Spring容器内容。

    需求:我们的需求是,在spring初始化完毕时,使我们自定义一个类Bird类可以得到spring容器内容. 实现步骤: 1.首先我们来看一下ApplicationContextAwareProcess ...

  2. 1.spring源码-BeanPostProcessor后置处理器

    1.BeanPostProcessor接口的介绍: BeanPostProcessor是一个接口,其中有两个方法,postProcessBeforeInitialization和postProcess ...

  3. spring源码:学习线索(li)

    一.spring xml配置(不包括AOP,主要了解在初始化及实例化过程中spring配置文件中每项内容的具体实现过程,从根本上掌握spring) <bean>的名字 &,alia ...

  4. [spring源码学习]五-BeanPostProcessor的使用

    一.接口描述 spring提供了一个接口类-BeanPostProcessor,我们叫他:bean的加工器,应该是在bean的实例化过程中对bean做一些包装处理,里边提供两个方法 public in ...

  5. Ioc容器BeanPostProcessor-Spring 源码系列(3)

    Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...

  6. spring源码分析系列 (2) spring拓展接口BeanPostProcessor

    Spring更多分析--spring源码分析系列 主要分析内容: 一.BeanPostProcessor简述与demo示例 二.BeanPostProcessor源码分析:注册时机和触发点 (源码基于 ...

  7. Spring 源码(8)Spring BeanPostProcessor的注册、国际化及事件发布机制

    上一篇文章https://www.cnblogs.com/redwinter/p/16198942.html介绍了Spring的注解的解析过程以及Spring Boot自动装配的原理,大概回顾下:Sp ...

  8. spring源码:web容器启动(li)

    web项目中可以集成spring的ApplicationContext进行bean的管理,这样使用起来bean更加便捷,能够利用到很多spring的特性.我们比较常用的web容器有jetty,tomc ...

  9. spring源码分析(一)IoC、DI

    创建日期:2016.08.06 修改日期:2016.08.07 - 2016.08.12 交流QQ:992591601 参考书籍:<spring源码深度解析>.<spring技术内幕 ...

  10. spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析

    更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...

随机推荐

  1. kali linux下的arp攻击

    这是我第一篇博客,写的不好请谅解 ____________________________(分割线)_______________________________ 在kali linux系统下自带工具 ...

  2. [PHP源码阅读]strpos、strstr和stripos、stristr函数

    我在github有对PHP源码更详细的注解.感兴趣的可以围观一下,给个star.PHP5.4源码注解.可以通过commit记录查看已添加的注解. strpos mixed strpos ( strin ...

  3. Storm构建分布式实时处理应用初探

    最近利用闲暇时间,又重新研读了一下Storm.认真对比了一下Hadoop,前者更擅长的是,实时流式数据处理,后者更擅长的是基于HDFS,通过MapReduce方式的离线数据分析计算.对于Hadoop, ...

  4. SQL Server 服务器磁盘测试之SQLIO篇(一)

    数据库调优工作中,有一部分是需要排查IO问题的,例如IO的速度或者RAID级别无法响应高并发下的快速请求.最常见的就是查看磁盘每次读写的响应速度,通过性能计数器Avg.Disk sec/Read(Wr ...

  5. Entity Framework 6 Recipes 2nd Edition(10-5)译 -> 在存储模型中使用自定义函数

    10-5. 在存储模型中使用自定义函数 问题 想在模型中使用自定义函数,而不是存储过程. 解决方案 假设我们数据库里有成员(members)和他们已经发送的信息(messages) 关系数据表,如Fi ...

  6. 通过扩展让ASP.NET Web API支持JSONP

    同源策略(Same Origin Policy)的存在导致了"源"自A的脚本只能操作"同源"页面的DOM,"跨源"操作来源于B的页面将会被拒 ...

  7. Windows 10 安装SVN 不显示状态图标--解决方法

    升级win10以后,什么都正常,就是svn版本库图标不见了,图标的显示有助于我们定位代码的修改及提交情况,该怎么办呢? 下面分享详细的解决办法亲测有用: 其实也比较简单, 在注册表中找到此项: HKE ...

  8. php排序算法

    <?php//冒泡排序(数组排序) function bubble_sort($array){ $count = count($array); if ($count <= 0) retur ...

  9. iOS 之项目中遇到的问题总结

    昨天去一家公司面试,面试官问了我在项目开发中遇到过哪些问题,是什么引起的,怎样解决的? 当时由于有点小紧张只说出了一两点,现在就来好好总结一下. 问题: 1.两表联动 所谓的两表联动就是有左右两个表格 ...

  10. 基于 getter 和 setter 撸一个简易的MVVM

    Angular 和 Vue 在对Angular的学习中,了解到AngularJS 的两个主要缺点: 对于每一次界面时间,Ajax 或者 timeout,都会进行一个脏检查,而每一次脏检查又会在内部循环 ...