月薪5萬,回家媳婦把我當大爺伺候!

Spring中有2個非常重要的接口:BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor,這2個接口面試中也會經常問到,本文我們一起來拿下他們倆。

先來看幾個問題

  1. BeanFactoryPostProcessor是做什麼的?

  2. BeanDefinitionRegistryPostProcessor是幹什麼的?

  3. BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor有什麼區別?

  4. 這幾個接口的執行順序是什麼樣的?

Spring容器中主要的4個階段

  • 階段1:Bean註冊階段,此階段會完成所有bean的註冊

  • 階段2:BeanFactory後置處理階段

  • 階段3:註冊BeanPostProcessor

  • 階段4:bean創建階段,此階段完成所有單例bean的註冊和裝載操作,這個階段不是我們本文關注的重點,有興趣的,可以去看之前的文章中有詳細介紹:Bean生命週期詳解

本文介紹的2個接口主要和前2個階段有關係,下面我們主要來看前2個階段。

階段1:Bean註冊階段

概述

spring中所有bean的註冊都會在此階段完成,按照規範,所有bean的註冊必須在此階段進行,其他階段不要再進行bean的註冊。

這個階段spring爲我們提供1個接口:BeanDefinitionRegistryPostProcessor,spring容器在這個階段中會獲取容器中所有類型爲BeanDefinitionRegistryPostProcessor的bean,然後會調用他們的postProcessBeanDefinitionRegistry方法,源碼如下,方法參數類型是BeanDefinitionRegistry,這個類型大家都比較熟悉,即bean定義註冊器,內部提供了一些方法可以用來向容器中註冊bean。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

這個接口還繼承了BeanFactoryPostProcessor接口,這個大家先不用關心,一會階段2中會介紹。

當容器中有多個BeanDefinitionRegistryPostProcessor的時候,可以通過下面任意一種方式來指定順序

  1. 實現org.springframework.core.PriorityOrdered接口

  2. 實現org.springframework.core.Ordered接口

執行順序:

PriorityOrdered.getOrder() asc,Ordered.getOrder() asc

下面通過案例來感受一下效果。

案例1:簡單實用

此案例演示BeanDefinitionRegistryPostProcessor的簡單使用

自定義一個BeanDefinitionRegistryPostProcessor

下面我們定義了一個類,需要實現BeanDefinitionRegistryPostProcessor接口,然後會讓我們實現2個方法,大家重點關注postProcessBeanDefinitionRegistry這個方法,另外一個方法來自於BeanFactoryPostProcessor,一會我們後面在介紹這個方法,在postProcessBeanDefinitionRegistry方法中,我們定義了一個bean,然後通過registry將其註冊到容器了,代碼很簡單

package com.javacode2018.lesson003.demo3.test0;

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //定義一個字符串類型的bean
        AbstractBeanDefinition userNameBdf = BeanDefinitionBuilder.
                genericBeanDefinition(String.class).
                addConstructorArgValue("路人").
                getBeanDefinition();
        //將userNameBdf註冊到spring容器中
        registry.registerBeanDefinition("userName", userNameBdf);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

同包中來個配置類

package com.javacode2018.lesson003.demo3.test0;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class MainConfig0 {
}

測試用例

package com.javacode2018.lesson003.demo3;

import com.javacode2018.lesson003.demo3.test0.MainConfig0;
import com.javacode2018.lesson003.demo3.test1.MainConfig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class BeanDefinitionRegistryPostProcessorTest {
    @Test
    public void test0() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig0.class);
        context.refresh();
        System.out.println(context.getBean("userName"));
    }
}

運行輸出

路人

案例2:多個指定順序

下面我們定義2個BeanDefinitionRegistryPostProcessor,都實現Ordered接口,第一個order的值爲2,第二個order的值爲1,我們來看一下具體執行的順序。

第一個

package com.javacode2018.lesson003.demo3.test1;

@Component
public class BeanDefinitionRegistryPostProcessor1 implements BeanDefinitionRegistryPostProcessor, Ordered {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        System.out.println(String.format("BeanDefinitionRegistryPostProcessor1{order=%d},註冊name bean,", this.getOrder()));
        //定義一個bean
        AbstractBeanDefinition nameBdf = BeanDefinitionBuilder.
                genericBeanDefinition(String.class).
                addConstructorArgValue("路人甲java").
                getBeanDefinition();
        //將定義的bean註冊到容器
        registry.registerBeanDefinition("name", nameBdf);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public int getOrder() {
        return 2;
    }
}

第二個

package com.javacode2018.lesson003.demo3.test1;

@Component
public class BeanDefinitionRegistryPostProcessor2 implements BeanDefinitionRegistryPostProcessor, Ordered {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        System.out.println(String.format("BeanDefinitionRegistryPostProcessor2{order=%d},註冊car bean,", this.getOrder()));
        //定義一個bean
        AbstractBeanDefinition nameBdf = BeanDefinitionBuilder.
                genericBeanDefinition(String.class).
                addConstructorArgValue("保時捷").
                getBeanDefinition();
        //將定義的bean註冊到容器
        registry.registerBeanDefinition("car", nameBdf);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public int getOrder() {
        return 1;
    }
}

上面2個類中的postProcessBeanDefinitionRegistry方法第一行都有輸出,一個可以通過運行結果看到執行的順序。

同包中添加配置類

package com.javacode2018.lesson003.demo3.test1;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class MainConfig1 {
}

測試案例

@Test
public void test1() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig1.class);
    context.refresh();
    context.getBeansOfType(String.class).forEach((beanName, bean) -> {
        System.out.println(String.format("%s->%s", beanName, bean));
    });
}

運行輸出

BeanDefinitionRegistryPostProcessor2{order=1},註冊car bean,
BeanDefinitionRegistryPostProcessor1{order=2},註冊name bean,
car->保時捷
name->路人甲java

小結

BeanDefinitionRegistryPostProcessor有個非常重要的實現類:

org.springframework.context.annotation.ConfigurationClassPostProcessor

這個類可能有些人不熟悉,下面這些註解大家應該比較熟悉吧,這些註解都是在上面這個類中實現的,通過這些註解來實現bean的批量註冊

@Configuration
@ComponentScan
@Import
@ImportResource
@PropertySource

有興趣的朋友可以去看一下ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry研究一下上面這些註解的解析過程,可以學到很多東西。

階段2:BeanFactory後置處理階段

概述

到這個階段的時候,spring容器已經完成了所有bean的註冊,這個階段中你可以對BeanFactory中的一些信息進行修改,比如修改階段1中一些bean的定義信息,修改BeanFactory的一些配置等等,此階段spring也提供了一個接口來進行擴展:BeanFactoryPostProcessor,簡稱bfpp,接口中有個方法postProcessBeanFactory,spring會獲取容器中所有BeanFactoryPostProcessor類型的bean,然後調用他們的postProcessBeanFactory,來看一下這個接口的源碼:

@FunctionalInterface
public interface BeanFactoryPostProcessor {

    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

當容器中有多個BeanFactoryPostProcessor的時候,可以通過下面任意一種方式來指定順序

  1. 實現org.springframework.core.PriorityOrdered接口

  2. 實現org.springframework.core.Ordered接口

執行順序:

PriorityOrdered.getOrder() asc,Ordered.getOrder() asc

下面通過案例來感受一下效果。

案例

這個案例中演示,在BeanFactoryPostProcessor來修改bean中已經註冊的bean定義的信息,給一個bean屬性設置一個值。

先來定義一個bean

package com.javacode2018.lesson003.demo3.test2;

import org.springframework.stereotype.Component;

@Component
public class LessonModel {
    //課程名稱
    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "LessonModel{" +
                "name='" + name + '\'' +
                '}';
    }
}

上面這個bean有個name字段,並沒有設置值,下面我們在BeanFactoryPostProcessor來對其設置值。

自定義的BeanFactoryPostProcessor

下面代碼中,我們先獲取lessonModel這個bean的定義信息,然後給其name屬性設置了一個值。

package com.javacode2018.lesson003.demo3.test2;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("準備修改lessonModel bean定義信息!");
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("lessonModel");
        beanDefinition.getPropertyValues().add("name", "spring高手系列!");
    }

}

測試用例

@Test
public void test2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig2.class);
    context.refresh();

    System.out.println(context.getBean(LessonModel.class));
}

運行輸出

準備修改lessonModel bean定義信息!
LessonModel{name='spring高手系列!'}

結果中可以看出,通過BeanFactoryPostProcessor修改了容器中已經註冊的bean定義信息。

這個接口的幾個重要實現類

PropertySourcesPlaceholderConfigurer

這個接口做什麼的,大家知道麼?來看一段代碼

<bean class="xxxxx">
    <property name="userName" value="${userName}"/>
    <property name="address" value="${address}"/>
</bean>

這個大家比較熟悉吧,spring就是在PropertySourcesPlaceholderConfigurer#postProcessBeanFactory中來處理xml中屬性中的${xxx},會對這種格式的進行解析處理爲真正的值。

CustomScopeConfigurer

向容器中註冊自定義的Scope對象,即註冊自定義的作用域實現類,關於自定義的作用域,不瞭解的朋友,建議先看一下:Spring系列第6篇:玩轉bean scope,避免跳坑裏!

這個用法比較簡單,定義一個CustomScopeConfigurer的bean就可以了,然後通過這個類來註冊自定義的bean。

EventListenerMethodProcessor

處理@EventListener註解的,即spring中事件機制,需要了解spring事件的:spring事件機制詳解

還有一些實現類,這裏就不介紹了。

使用注意

BeanFactoryPostProcessor接口的使用有一個需要注意的地方,在其postProcessBeanFactory方法中,強烈禁止去通過容器獲取其他bean,此時會導致bean的提前初始化,會出現一些意想不到的問題,因爲這個階段中BeanPostProcessor還未準備好,本文開頭4個階段中有介紹,BeanPostProcessor是在第3個階段中註冊到spring容器的,而BeanPostProcessor可以對bean的創建過程進行干預,比如spring中的aop就是在BeanPostProcessor的一些子類中實現的,@Autowired也是在BeanPostProcessor的子類中處理的,此時如果去獲取bean,此時bean不會被BeanPostProcessor處理,所以創建的bean可能是有問題的,還是通過一個案例給大家演示一下把,通透一些。

來一個簡單的類

package com.javacode2018.lesson003.demo3.test3;

import org.springframework.beans.factory.annotation.Autowired;

public class UserModel {
    @Autowired
    private String name; //@1

    @Override
    public String toString() {
        return "UserModel{" +
                "name='" + name + '\'' +
                '}';
    }
}

@1:使用了@Autowired,會指定注入

來個配置類

配置類中定義了2個UserModel類型的bean:user1、user2

並且定義了一個String類型的bean:name,這個會注入到UserModel中的name屬性中去。

package com.javacode2018.lesson003.demo3.test3;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class MainConfig3 {
    @Bean
    public UserModel user1() {
        return new UserModel();
    }

    @Bean
    public UserModel user2() {
        return new UserModel();
    }

    @Bean
    public String name() {
        return "路人甲Java,帶大家成功java高手!";
    }
}

測試用例

輸出容器中所有UserModel類型的bean

@Test
public void test3() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig3.class);
    context.refresh();

    context.getBeansOfType(UserModel.class).forEach((beanName, bean) -> {
        System.out.println(String.format("%s->%s", beanName, bean));
    });
}

運行輸出

user1->UserModel{name='路人甲Java,帶大家成功java高手!'}
user2->UserModel{name='路人甲Java,帶大家成功java高手!'}

效果不用多解釋,大家一看就懂,下面來重點。

添加一個BeanFactoryPostProcessor

postProcessBeanFactory方法中去獲取一下user1這個bean

package com.javacode2018.lesson003.demo3.test3;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.getBean("user1");
    }

}

再次運行輸出

user1->UserModel{name='null'}
user2->UserModel{name='路人甲Java,帶大家成功java高手!'}

注意,user1中的name變成null了,什麼情況?

是因爲@Autowired註解是在AutowiredAnnotationBeanPostProcessor中解析的,spring容器調用BeanFactoryPostProcessor#postProcessBeanFactory的使用,此時spring容器中還沒有AutowiredAnnotationBeanPostProcessor,所以此時去獲取user1這個bean的時候,@Autowired並不會被處理,所以name是null。

源碼

4個階段的源碼

4個階段的源碼爲位於下面這個方法中

org.springframework.context.support.AbstractApplicationContext#refresh

這個方法中截取部分代碼如下:

// 對應階段1和階段2:調用上下文中註冊爲bean的工廠處理器,即調用本文介紹的2個接口中的方法
invokeBeanFactoryPostProcessors(beanFactory);

// 對應階段3:註冊攔截bean創建的bean處理器,即註冊BeanPostProcessor
registerBeanPostProcessors(beanFactory);

// 對應階段3:實例化所有剩餘的(非延遲初始化)單例。
finishBeanFactoryInitialization(beanFactory);

階段1和階段2的源碼位於下面方法中,代碼比較簡單,強烈建議大家去看一下,幾分鐘就可以看懂了。

org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)

總結

  1. 注意spring的4個階段:bean定義階段、BeanFactory後置處理階段、BeanPostProcessor註冊階段、單例bean創建組裝階段

  2. BeanDefinitionRegistryPostProcessor會在第一個階段被調用,用來實現bean的註冊操作,這個階段會完成所有bean的註冊

  3. BeanFactoryPostProcessor會在第2個階段被調用,到這個階段時候,bean此時已經完成了所有bean的註冊操作,這個階段中你可以對BeanFactory中的一些信息進行修改,比如修改階段1中一些bean的定義信息,修改BeanFactory的一些配置等等

  4. 階段2的時候,2個禁止操作:禁止註冊bean、禁止從容器中獲取bean

  5. 本文介紹的2個接口的實現類可以通過PriorityOrdered接口或者Ordered接口來指定順序

案例源碼

https://gitee.com/javacode2018/spring-series

路人甲java所有案例代碼以後都會放到這個上面,大家watch一下,可以持續關注動態。

課後作業

2個問題,歡迎留言討論,會有紅包驚喜哦!

問題1

大家去讀一下org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors的源碼,將內部主要的執行過程通過文字描述出來,然後留言

問題2

BeanPostProcessor和BeanFactoryPostProcessors有何區別?留言

Spring系列

  1. Spring系列第1篇:爲何要學spring?

  2. Spring系列第2篇:控制反轉(IoC)與依賴注入(DI)

  3. Spring系列第3篇:Spring容器基本使用及原理

  4. Spring系列第4篇:xml中bean定義詳解(-)

  5. Spring系列第5篇:創建bean實例這些方式你們都知道?

  6. Spring系列第6篇:玩轉bean scope,避免跳坑裏!

  7. Spring系列第7篇:依賴注入之手動注入

  8. Spring系列第8篇:自動注入(autowire)詳解,高手在於堅持

  9. Spring系列第9篇:depend-on到底是幹什麼的?

  10. Spring系列第10篇:primary可以解決什麼問題?

  11. Spring系列第11篇:bean中的autowire-candidate又是幹什麼的?

  12. Spring系列第12篇:lazy-init:bean延遲初始化

  13. Spring系列第13篇:使用繼承簡化bean配置(abstract & parent)

  14. Spring系列第14篇:lookup-method和replaced-method比較陌生,怎麼玩的?

  15. Spring系列第15篇:代理詳解(Java動態代理&cglib代理)?

  16. Spring系列第16篇:深入理解java註解及spring對註解的增強(預備知識)

  17. Spring系列第17篇:@Configration和@Bean註解詳解(bean批量註冊)

  18. Spring系列第18篇:@ComponentScan、@ComponentScans詳解(bean批量註冊)

  19. Spring系列第18篇:@import詳解(bean批量註冊)

  20. Spring系列第20篇:@Conditional通過條件來控制bean的註冊

  21. Spring系列第21篇:註解實現依賴注入(@Autowired、@Resource、@Primary、@Qulifier)

  22. Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 詳解

  23. Spring系列第23篇:Bean生命週期詳解

  24. Spring系列第24篇:父子容器詳解

  25. Spring系列第25篇:@Value【用法、數據來源、動態刷新】

  26. Spring系列第26篇:國際化詳解

  27. Spring系列第27篇:spring事件機制詳解

  28. Spring系列第28篇:Bean循環依賴詳解

更多好文章

  1. Java高併發系列(共34篇)

  2. MySql高手系列(共27篇)

  3. Maven高手系列(共10篇)

  4. Mybatis系列(共12篇)

  5. 聊聊db和緩存一致性常見的實現方式

  6. 接口冪等性這麼重要,它是什麼?怎麼實現?

  7. 泛型,有點難度,會讓很多人懵逼,那是因爲你沒有看這篇文章!

世界上最好的關係是相互成就,點贊轉發 感恩開心????

路人甲java

▲長按圖片識別二維碼關注

路人甲Java:工作10年的前阿里P7,所有文章以系列的方式呈現,帶領大家成爲java高手,目前已出:java高併發系列、mysql高手系列、Maven高手系列、mybatis系列、spring系列,正在連載springcloud系列,歡迎關注!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章