Spring BeanPostProsser最佳实践

BeanPostProcessor接口的作用是在Spring bean 实例化完成后(执行完initializeBean)之后,初始化之前(执行bean的初始化方法)对Bean添加一些自定义的处理逻辑。也就是说执行beanProcessor方法是在bean实例化之后 此时bean的属性值都已经被赋值好了。另外需要注意的是 beanProcessor的实现类一定要被Spring托管才能生效。

一、BeanPostProcessor示例

  • 实体类

      @Component
      @ConfigurationProperties(prefix = "spring.datasource")
      public class DataSourceProperty {
      
      
          private String userName;
      
          private String password;
      
          /**
           * 通过@PostConstruct来标记这个init方法为bean的初始化方法
           **/
          @PostConstruct
          private void init() {
              //这条语句会在 beanprocessor的postProcessBeforeInitialization方法执行之后执行 2
              System.out.println("执行初始化方法");
          }
      
          public String getUserName() {
              return userName;
          }
      
          public void setUserName(String userName) {
              this.userName = userName;
          }
      
          public String getPassword() {
              return password;
          }
      
          public void setPassword(String password) {
              this.password = password;
          }
      }
    
  • 配置

    spring.datasource.username=test
    spring.datasource.password=12345678
    
  • 自定义的BeanPostProcessor实现类

      //这个类一定要被spring容器托管(也就是要能够被spring识别为一个bean) postProcessBeforeInitialization 和 
      //postProcessAfterInitialization才会生效
      @Component
      public class MyBeanPostProcessor implements BeanPostProcessor {
      
          /**
           * bean实例化前处理
           * @param bean
           * @param beanName
           * @return
           * @throws BeansException
           */
          @Override
          public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              if (bean instanceof DataSourceProperty) {
                          //这条语句是最先打印的 1  DataSourceProperty 此时它的两个属性是已经被赋值了的
                  System.out.println("dataSourceProperty 初始化前执行 dataSourceProperty" );
              }
              return bean;
          }
      
          /**
           * bean实例化后处理
           * @param bean
           * @param beanName
           * @return
           * @throws BeansException
           */
          @Override
          public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              if (bean instanceof DataSourceProperty) {
                  //这条语句是最后打印的
                  System.out.println("dataSourceProperty 初始后执行" );
              }
              return bean;
          }
      }
    
  • 结果

二、为什么BeanPostProcessor必须被Spring托管才能生效
关于这一点可以查看源码

  • 第一步
    当Spring容器启动时 会先执行AbstractApplicationContext类的refresh方法 通过该方法去初始化我们的Spring容器 源码如下
      public abstract class AbstractApplicationContext extends DefaultResourceLoader
              implements ConfigurableApplicationContext {
          @Override
          public void refresh() throws BeansException, IllegalStateException {
              synchronized (this.startupShutdownMonitor) {
                  //省略前面若干方法
                  prepareBeanFactory(beanFactory);
      
                  try {
              //此处省略若干方法
                      // 向Spring容器注册beanProstProcessor
                      registerBeanPostProcessors(beanFactory);
                      beanPostProcess.end();
                      //此处省略若干方法 完成Spring容器刷新工作
                      finishRefresh();
                  }catch (BeansException ex) {
                      //此处省略
                  }finally {
                      //此处生路
                  }
              }
          }
      }
    
  • 第二步
    从第一步 我们知道了当Spring容器在执行refresh方法的时候 会向容器中注册beanProcessor 下面接着看registerBeanPostProcessors(beanFactory)方法的逻辑 该方法的真正实现是在PostProcessorRegistrationDelegate类中
      final class PostProcessorRegistrationDelegate {
        public static void registerBeanPostProcessors(
                  ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
              //核心方法 从beanFactory中获取容器中所有实现了BeanPostProcessor接口的bean的名称
              String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
          //此后省略若干方法
          }
      }
    
  • 第三步
    beanFactory.getBeanNamesForType() 这个方法真正的实现是在DefaultListableBeanFactory中
      public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
              implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
          
        @Override
          public String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
              if (!isConfigurationFrozen() || type == null || !allowEagerInit) {
                  //真正获取beanPostProcessor的名称的方法
            return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);
              }
              //此处省略若干方法
              return resolvedBeanNames;
          }
      }
    
  • 第四步
      public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
              implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
        
        private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
              List<String> result = new ArrayList<>();
      
              // 它会遍历Spring容器的beanDefinitionNames 
              for (String beanName : this.beanDefinitionNames) {
                    //此处省略若干方法
              //如果类型是指定的类型 则将这个bean的名称加入到list中
              //对于BeanPostProcessor来说 此时type就是BeanPostProcessor
                      boolean matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
                      //此处省略若干方法      
                      if (matchFound) {
                          result.add(beanName);
                      }
              //此处省略若干方法
              }
              return StringUtils.toStringArray(result);
          }
      }   
    

可以发现它最终还是需要通过遍历Spring容器中所有的beanName才能够将这个bean注册为BeanProcessor 如果这个BeanPostProcessor的实现类都没有被Spring托管 也就不可能被注册为beanProcessor 这也就是为什么beanProcessor的实现类必须被spring托管才能生效。

三、BeanPostProcessor执行的流程

//第一步 容器刷新
1、AbstractApplicationContext.refresh()
//第二步 完成beanFactory初始化
2、AbstractApplicationContext.finishBeanFactoryInitialization(beanFactory);  
//第三步 默认的beanFactory开始预实例化单例bean
3、DefaultListableBeanFactory.preInstantiateSingletons();
//第四步 开始根据bean的名称获取bean
4、AbstractBeanFactory.getBean(String name);
//第五步 执行doCreateBean方法 开始创建bean
5、AbstractAutowireCapableBeanFactory.doCreateBean()
//第六步 对bean进行初始化
AbstractAutowireCapableBeanFactory.initializeBean()  
//第七步 开始遍历beanProcessor 执行beanProcessor的postProcessBeforeInitialization方法
AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization();
//第八步 执行beanPostProcessor的后置方法
AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization();

initializeBean源码

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
        //此处省略若干方法
        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {
      //执行beanPostProcessor的前置方法
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }

        try {
      //执行bean的初始化方法
            invokeInitMethods(beanName, wrappedBean, mbd);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
        }
  
        if (mbd == null || !mbd.isSynthetic()) {
      //执行beanPostProcessor的后置方法
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }

        return wrappedBean;
    }

applyBeanPostProcessorsBeforeInitialization源码

@Override
    public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
            throws BeansException {

        Object result = existingBean;
    //遍历beanPostProcessor 执行前置方法
        for (BeanPostProcessor processor : getBeanPostProcessors()) {
            Object current = processor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

applyBeanPostProcessorsAfterInitialization源码

@Override
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException {

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

推荐阅读更多精彩内容

  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,055评论 0 4
  • 公元:2019年11月28日19时42分农历:二零一九年 十一月 初三日 戌时干支:己亥乙亥己巳甲戌当月节气:立冬...
    石放阅读 6,885评论 0 2
  • 今天上午陪老妈看病,下午健身房跑步,晚上想想今天还没有断舍离,马上做,衣架和旁边的的布衣架,一看乱乱,又想想自己是...
    影子3623253阅读 2,914评论 1 8