Spring之BeanPostProcessor

作用

spring具有很好的擴(kuò)展性凌受,但是這個(gè)擴(kuò)展它的這個(gè)擴(kuò)展性體現(xiàn)在哪里呢旅赢?而我們要說的BeanPostProcessor就是對(duì)Spring擴(kuò)展性優(yōu)秀的表現(xiàn)之一。

簡單的說就是BeanPostProcessor提供了初始化前后回調(diào)的方法放椰,我們所說的擴(kuò)展就是在實(shí)例化前后對(duì)Bean進(jìn)行擴(kuò)展缔刹。

接口定義

public interface BeanPostProcessor {
  /**
  * 初始前調(diào)用
  */
   @Nullable
   default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }
   /**
   * 初始化后調(diào)用
   */
   @Nullable
   default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }
}

上面就是BeanPostProcessor接口的定義,從方法名字也能看出這兩個(gè)方法一個(gè)在初始化前調(diào)用一個(gè)在初始化后調(diào)用杀迹。需要注意的是,方法的返回值為原始實(shí)例或者包裝后的實(shí)例押搪。如果返回null會(huì)導(dǎo)致后續(xù)的BeanPostProcessor不生效(BeanPostProcessor是可以注冊(cè)多個(gè)的)。

如何使用

BeanPostProcessorDemo代碼如下:

public class BeanPostProcessorDemo {
    public static void main(String[] args) {
        //創(chuàng)建基礎(chǔ)容器
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //加載xml配置文件
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions("spring-bean-post-processor.xml");
        //添加BeanPostProcessor
        beanFactory.addBeanPostProcessor(new UserBeanPostProcessor());
        User user = beanFactory.getBean(User.class);
        System.out.println(user);
    }
}
@Data
class User{
    private String userName;
    private Integer age;
    private String beforeMessage;
    private String afterMessage;
    public void initMethod(){
        System.out.println("初始化:"+this);
        this.setUserName("小明");
        this.setAge(18);
    }
}
class UserBeanPostProcessor implements BeanPostProcessor{
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof User){
            System.out.println("初始化前:"+bean);
            ((User) bean).setBeforeMessage("初始化前信息");
        }
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof User){
            System.out.println("初始化后:"+bean);
            ((User) bean).setAfterMessage("初始化后信息");
        }
        return bean;
    }
}

spring-bean-post-processor.xml配置文件如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="user" class="com.buydeem.beanpostprocessor.User" init-method="initMethod"/>
    
</beans>

運(yùn)行之后打印結(jié)果如下:

初始化前:User(userName=null, age=null, beforeMessage=null, afterMessage=null)
初始化:User(userName=null, age=null, beforeMessage=初始化前信息, afterMessage=null)
初始化后:User(userName=小明, age=18, beforeMessage=初始化前信息, afterMessage=null)
User(userName=小明, age=18, beforeMessage=初始化前信息, afterMessage=初始化后信息)

上面的代碼很簡單就是創(chuàng)建基礎(chǔ)的容器,因?yàn)槲疫@個(gè)里面用的是BeanFactory影暴,BeanFactory作為基礎(chǔ)容器是需要手動(dòng)將BeanPostProcessor注冊(cè)到容器中去的襟己。下面分析打印結(jié)果:

初始化前:User(userName=null, age=null, beforeMessage=null, afterMessage=null)

該結(jié)果是postProcessBeforeInitialization方法中輸出的內(nèi)容,這個(gè)時(shí)候User實(shí)例還只是進(jìn)行了實(shí)例化厦画,還未進(jìn)行到初始化步驟疮茄,所以所有的屬性都為null,說明該方法確實(shí)是初始化執(zhí)行的。

初始化:User(userName=null, age=null, beforeMessage=初始化前信息, afterMessage=null)

該結(jié)果為自定義的初始化方法initMethod方法中輸出的內(nèi)容根暑,這個(gè)時(shí)候User實(shí)例真正初始化力试,而beforeMessage中中的值正是我們?cè)?strong>postProcessBeforeInitialization設(shè)置的。

初始化后:User(userName=小明, age=18, beforeMessage=初始化前信息, afterMessage=null)

該結(jié)果是postProcessAfterInitialization中輸出內(nèi)容排嫌,從打印結(jié)果可以看出它的確是在自定義initMethod后畸裳。

Spring相關(guān)源碼解讀

如果之前看過了解過Bean的生命周期一定知道,Spring中Bean總體上來說可以分為四個(gè)周期:實(shí)例化淳地、屬性賦值怖糊、初始化、銷毀颇象。而BeanPostProcessor則是在初始化階段的前后執(zhí)行伍伤,下面我通過源碼來說明。

首先看AbstractAutowireCapableBeanFactory中doCreateBean方法遣钳,該方法實(shí)際就是創(chuàng)建指定Bean的方法扰魂。源碼太長這里就不展示了,我們只需要找到其中三個(gè)重要的方法調(diào)用如下:createBeanInstance耍贾、populateBean阅爽、initializeBean。這三個(gè)方法分別代表了Spring Bean中的實(shí)例化荐开、屬性賦值和初始化三個(gè)生命周期付翁。而BeanPostProcessor是在初始化前后調(diào)用,所以我們查看initializeBean中的方法詳情即可晃听。該方法詳情如下:

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
   //處理BeanNameAware百侧、BeanClassLoaderAware砰识、BeanFactoryAware
   if (System.getSecurityManager() != null) {
      AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
         invokeAwareMethods(beanName, bean);
         return null;
      }, getAccessControlContext());
   }
   else {
      invokeAwareMethods(beanName, bean);
   }
   //處理BeanPostProcessor
   Object wrappedBean = bean;
   if (mbd == null || !mbd.isSynthetic()) {
      //回調(diào)postProcessBeforeInitialization方法
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
   }
   try {
      //處理InitializingBean和BeanDefinition中指定的initMethod
      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()) {
      //回調(diào)postProcessAfterInitialization方法
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
   }
   return wrappedBean;
}

從上面的源碼可以看出首先是處理部分Aware相關(guān)接口,然后接著就是處理BeanPostProcessor中的postProcessBeforeInitialization方法佣渴,該方法詳情如下:

public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
      throws BeansException {
   Object result = existingBean;
   //依次處理BeanPostProcessor
   for (BeanPostProcessor processor : getBeanPostProcessors()) {
      Object current = processor.postProcessBeforeInitialization(result, beanName);
      //如果放回null,則直接返回后續(xù)BeanPostProcessor中的postProcessBeforeInitialization不再執(zhí)行
      if (current == null) {
         return result;
      }
      result = current;
   }
   return result;
}

該方法就是執(zhí)行postProcessBeforeInitialization回調(diào)的詳情內(nèi)容辫狼,從該實(shí)現(xiàn)可以知道,BeanPostProcessor可以有多個(gè)辛润,而且會(huì)按照順序依次處理膨处。如果只要其中的任意一個(gè)返回null,則后續(xù)的BeanPostProcessor的postProcessBeforeInitialization將不會(huì)再處理了砂竖。接著就是執(zhí)行初始化方法真椿,即invokeInitMethods方法被調(diào)用。

protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
      throws Throwable {
   boolean isInitializingBean = (bean instanceof InitializingBean);
   if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
      if (logger.isTraceEnabled()) {
         logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
      }
      //如果當(dāng)前Bean實(shí)現(xiàn)了InitializingBean接口則會(huì)執(zhí)行它的afterPropertiesSet()方法
      if (System.getSecurityManager() != null) {
         try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
               ((InitializingBean) bean).afterPropertiesSet();
               return null;
            }, getAccessControlContext());
         }
         catch (PrivilegedActionException pae) {
            throw pae.getException();
         }
      }
      else {
         ((InitializingBean) bean).afterPropertiesSet();
      }
   }
   //如果在BeanDefinition中定義了initMethod則執(zhí)行初始化方法
   if (mbd != null && bean.getClass() != NullBean.class) {
      String initMethodName = mbd.getInitMethodName();
      if (StringUtils.hasLength(initMethodName) &&
            !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
         invokeCustomInitMethod(beanName, bean, mbd);
      }
   }
}

從上面代碼也進(jìn)一步驗(yàn)證了BeanPostProcessor中的postProcessBeforeInitialization方法的確是在初始化前調(diào)用乎澄。當(dāng)invokeInitMethods方法執(zhí)行完之后接著就執(zhí)行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;
}

該方法與applyBeanPostProcessorsBeforeInitialization幾乎就是相同的,不同的在于它執(zhí)行的是postProcessAfterInitialization置济。至此Spring Bean的初始化也就完成了解恰。


Spring Bean初始化過程.jpg

Spring對(duì)@PostConstruct的支持

通過上面源碼解讀了解了Spring Bean生命周期中初始化的過程,但是實(shí)際上Spring對(duì)于JSR250也支持浙于,例如對(duì)@PostConstruct注解的支持护盈,但是在之前的源碼中并沒有發(fā)現(xiàn)Spring Bean的初始化過程中有所體現(xiàn)。這里面的秘密就是我們的BeanPostProcessor了路媚。在Spring中有一個(gè)CommonAnnotationBeanPostProcessor類黄琼,這個(gè)類的注釋中有說到這個(gè)類就是用來對(duì)JSR250及其他一些規(guī)范的支持。下面我就通過這個(gè)類的源碼來說明Spring是如何通過BeanPostProcessor來實(shí)現(xiàn)對(duì)@PostContruct的支持整慎。

CommonAnnotationBeanPostProcessor.jpg

從上圖中我們可以看出脏款,CommonAnnotationBeanPostProcessor并沒有直接對(duì)BeanPostProcessor有所實(shí)現(xiàn),它繼承InitDestroyAnnotationBeanPostProcessor該類裤园,而對(duì)@PostConstruct的實(shí)現(xiàn)主要在該類中撤师。

InitDestroyAnnotationBeanPostProcessor.jpg

而對(duì)BeanPostProcessor的實(shí)現(xiàn)代碼如下:

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
   //生命周期元數(shù)據(jù)封裝
   LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
   try {
      //執(zhí)行InitMethods
      metadata.invokeInitMethods(bean, beanName);
   }
   catch (InvocationTargetException ex) {
      throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
   }
   catch (Throwable ex) {
      throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
   }
   return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
   return bean;
}

對(duì)BeanPostProcessor的實(shí)現(xiàn)主要在before方法中,該方法主要就是兩部分內(nèi)容拧揽,第一部分主要是信息封裝到LifecycleMetadata中剃盾,便于后面第二步的執(zhí)行相關(guān)初始化方法。
通過上面的方法實(shí)現(xiàn)我們知道了淤袜,Spring對(duì)JSR250的實(shí)現(xiàn)就是借助于BeanPostProcessor來實(shí)現(xiàn)的痒谴。下面我直接用代碼來驗(yàn)證看看我的猜想是否正確。

public class BeanPostProcessorDemo2 {
    public static void main(String[] args) {
        //創(chuàng)建基礎(chǔ)容器
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //構(gòu)建BeanDefinition并注冊(cè)
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Person.class)
                .getBeanDefinition();
        beanFactory.registerBeanDefinition("person",beanDefinition);
        //注冊(cè)CommonAnnotationBeanPostProcessor
        CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor = new CommonAnnotationBeanPostProcessor();
        beanFactory.addBeanPostProcessor(commonAnnotationBeanPostProcessor);
        //獲取Bean
        Person person = beanFactory.getBean(Person.class);
        System.out.println(person);
    }
}
class Person{
    @PostConstruct
    public void annotationInitMethod(){
        System.out.println("@PostConstruct");
    }
}

上面的代碼比較簡單铡羡,我們定義一個(gè)Person并使用@PostConstruct標(biāo)記出它的初始化方法积蔚,然后我們創(chuàng)建BeanFactory,并創(chuàng)建Person的BeanDefinition將其注冊(cè)到BeanFactory(與讀取配置文件一樣)烦周,然后我們創(chuàng)建CommonAnnotationBeanPostProcessor并將其添加到BeanFactory中尽爆。最后打印結(jié)果打印出@PostConstruct怎顾。如果我們將下面這句代碼注釋。

beanFactory.addBeanPostProcessor(commonAnnotationBeanPostProcessor);

再次執(zhí)行可以發(fā)現(xiàn)漱贱,@PostConstruct將會(huì)失效槐雾,且最后不會(huì)打印出結(jié)果。

使用中的注意事項(xiàng)

如何使用以及實(shí)現(xiàn)原理上面已經(jīng)說了很多幅狮,但是在使用時(shí)有些地方是有限制的募强,對(duì)于這些限制還是要做了解,避免到時(shí)候使用時(shí)出現(xiàn)問題而不知道是什么原因?qū)е碌摹?/p>

順序性

BeanPostProcessor是可以注冊(cè)多個(gè)的彪笼,在AbstractBeanFactory內(nèi)部通過List變量beanPostProcessors來存儲(chǔ)BeanPostProcessor钻注。而在執(zhí)行時(shí)是按照List中BeanPostProcessor的順序一個(gè)個(gè)執(zhí)行的,所以我們?cè)谙肴萜髦刑砑覤eanPostProcessor時(shí)需要注意順序配猫。如果我們不是通過手動(dòng)添加(大多數(shù)時(shí)候不是)時(shí),而是在代碼或者配置文件中定義多個(gè)BeanPostProcessor時(shí)杏死,我們可以通過實(shí)現(xiàn)Ordered接口來控制它的順序泵肄。

BeanPostProcessor依賴的Bean不會(huì)執(zhí)行BeanPostProcessor

BeanPostProcessor依賴的Bean是不會(huì)執(zhí)行BeanPostProcessor的,這是因?yàn)樵趧?chuàng)建BeanPOstProcessor之所依賴的Bean就需要完成初始化淑翼,而這個(gè)時(shí)候BeanPostProcessor都還未完初始化完成腐巢。實(shí)例代碼如下:

public class App3 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.scan("com.buydeem.beanpostprocessor");
        context.register(App3.class);
        context.refresh();
    }
}
@Component
class ClassA{
}
@Component
class ClassB{
}
@Component
class MyBeanPostProcessor implements BeanPostProcessor{
    @Autowired
    private ClassA classA;
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor"+bean);
        return bean;
    }
}

最后ClassA是不會(huì)打印出來的,而ClassB是會(huì)被打印出來玄括。因?yàn)镸yBeanPostProcessor依賴ClassA實(shí)例冯丙。

注冊(cè)方式

注冊(cè)BeanPostProcessor方式主要有兩種,一種是在Java或則xml文件配置和聲明作為普通Bean遭京。這樣在ApplicationContext加載時(shí)會(huì)對(duì)這類型的Bean特殊處理胃惜,將其注冊(cè)到容器中來。另外一種則是通過API手動(dòng)注冊(cè)到容器哪雕,就如我們之前的示例一樣船殉,在還未注冊(cè)到容器之前是不會(huì)生效的。

總結(jié)

在Spring中BeanPostProcessor的子接口或?qū)崿F(xiàn)類有很多種斯嚎,例如:InstantiationAwareBeanPostProcessor利虫、MergedBeanDefinitionPostProcessor、DestructionAwareBeanPostProcessor等等堡僻。這些接口分別處在Spring Bean生命周期的不同階段糠惫,而他們的功能與BeanPostProcessor都類似,都是為了給Spring Bean各個(gè)聲明周期提供擴(kuò)展點(diǎn)钉疫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末硼讽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子陌选,更是在濱河造成了極大的恐慌理郑,老刑警劉巖蹄溉,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異您炉,居然都是意外死亡柒爵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門赚爵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棉胀,“玉大人,你說我怎么就攤上這事冀膝⊙渖荩” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵窝剖,是天一觀的道長麻掸。 經(jīng)常有香客問我,道長赐纱,這世上最難降的妖魔是什么脊奋? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮疙描,結(jié)果婚禮上诚隙,老公的妹妹穿的比我還像新娘。我一直安慰自己起胰,他們只是感情好久又,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著效五,像睡著了一般地消。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上火俄,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天犯建,我揣著相機(jī)與錄音,去河邊找鬼瓜客。 笑死适瓦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谱仪。 我是一名探鬼主播玻熙,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼疯攒!你這毒婦竟也來了嗦随?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枚尼,沒想到半個(gè)月后贴浙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡署恍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年崎溃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盯质。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡袁串,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呼巷,到底是詐尸還是另有隱情囱修,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布王悍,位于F島的核電站破镰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏压储。R本人自食惡果不足惜啤咽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渠脉。 院中可真熱鬧,春花似錦瓶佳、人聲如沸芋膘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽为朋。三九已至,卻和暖如春厚脉,著一層夾襖步出監(jiān)牢的瞬間习寸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工傻工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留霞溪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓中捆,卻偏偏與公主長得像鸯匹,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泄伪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容