詳解Spring IOC

一、Spring IOC其實(shí)很簡(jiǎn)單

?有部分Java開(kāi)發(fā)者對(duì)IOC(Inversion Of Control)和DI(Dependency Injection)的概念有些混淆,認(rèn)為二者是對(duì)等對(duì),實(shí)際上我在之前對(duì)著作中已經(jīng)說(shuō)過(guò)了,IOC其實(shí)有兩種方式,一種就是DI,而另一種是DL,即Dependency Lookup(依賴查找),前者是當(dāng)前軟件實(shí)體被動(dòng)接受其依賴對(duì)其他組件被IOC容器注入,而后者則是當(dāng)前軟件實(shí)體主動(dòng)去某個(gè)服務(wù)注冊(cè)地查找其依賴對(duì)那些服務(wù),概念之間對(duì)關(guān)系圖如下圖所示可能更貼切些。
IOC相關(guān)概念示意圖.png

?我們通常提到的Spring IOC,實(shí)際上是指Spring框架提供的IOC容器實(shí)現(xiàn)(IOC Container),而使用Spring IOC容器的一個(gè)典型的代碼片段就是:

public class App{
    public static void main(String [] args){
        Application context = new FileSystemXmlApplicationContext(". . .");
        // ...
        MockService service = context.getBean(MockService.class);
        service.doSomething();
    }
}

?任何一個(gè)使用Spring框架構(gòu)建的獨(dú)立的Java應(yīng)用(Standalone Java Application),通常都會(huì)存在一行類似于“context.getBean(...);”的代碼,實(shí)際上,這行代碼做的就是DL的工作,而構(gòu)建的任何一種IOC容器背后(比如BeanFactory或者ApplicationContext)發(fā)生的事情,則更多是DI的過(guò)程(也可能有部分DL的邏輯用于對(duì)接遺留系統(tǒng))。

?Spring的IOC容器中發(fā)生的事情其實(shí)也很簡(jiǎn)單,總結(jié)下來(lái)即兩個(gè)階段:

  1. 采摘和收集“咖啡豆”(bean)
  2. 研磨和烹飪咖啡

?我們還是回過(guò)頭來(lái)繼續(xù)說(shuō)Spring IOC容器的依賴注入流程吧!Spring IOC容器的依賴注入工作可以分為兩個(gè)階段:

?階段一:收集和注冊(cè)

?第一階段可以認(rèn)為是構(gòu)建和收集bean定義的階段,在這個(gè)階段中,我們可以通過(guò)XML或者Java代碼的方式定義一些bean,然后通過(guò)手動(dòng)組裝或者讓容器基于某些機(jī)制自動(dòng)掃描的形式,將這些bean定義收集到IOC容器中。

?假設(shè)我們以XML配置的形式來(lái)收集并注冊(cè)單一bean,一般形式如下:

<bean id="mockService" class="..MockServiceImpl">
    ...
</bean>

?如果嫌逐個(gè)收集bean定義麻煩,想批量地收集并注冊(cè)到IOC容器中,我們也可以通過(guò)XML Schema形式的配置進(jìn)行批量掃描并采集和注冊(cè):

<context:component-scan base-package="com.keevol">

注意基于JavaConfig形式的收集和注冊(cè),不管是單一還是批量,后面我們都會(huì)單獨(dú)提及。

?階段二:分析和組裝

?當(dāng)?shù)谝浑A段工作完成后,我們可以先暫且認(rèn)為IOC容器中充斥著一個(gè)個(gè)獨(dú)立的bean,它們之間沒(méi)有任何關(guān)系。但實(shí)際上,它們之間是有依賴關(guān)系的,所以,IOC容器在第二階段要干的事情就是分析這些已經(jīng)在IOC容器之中的bean,然后根據(jù)它們之間的依賴關(guān)系先后組裝它們。如果IOC容器發(fā)現(xiàn)某個(gè)bean依賴另一個(gè)bean,它就會(huì)將這另一個(gè)bean注入給依賴它的那個(gè)bean,直到所有到bean的依賴都注入完成,所有bean都“整裝待發(fā)”,整個(gè)IOC容器都工作即算完成。

?至于分析和組裝的依據(jù),Spring框架最早是通過(guò)XML配置文件的形式來(lái)描述bean與bean之間的關(guān)系,隨著Java業(yè)界研發(fā)技術(shù)和理念都轉(zhuǎn)變,基于Java代碼和Annotation元信息的描述方式也日漸興盛(比如@Autowired和@Inject),但不管使用哪種方式,都只是為了簡(jiǎn)化綁定邏輯描述的各種“表象”,最終都是為本階段都最終目的服務(wù)。

注意:很多Java開(kāi)發(fā)者一定認(rèn)為spring的XML配置文件是一種配置(configuration),但本質(zhì)上,這些配置文件更應(yīng)該是一種代碼形式,XML在這里其實(shí)可以看作一種DSL,它用來(lái)表述的是bean與bean之間的依賴綁定關(guān)系,大家還記得沒(méi)有IOC容器的年代要自己寫代碼新建(new)對(duì)象并配置(set)依賴的吧?

二、了解一點(diǎn)JavaConfig

?Java 5的推出,加上當(dāng)年基于純Java Annotation的依賴注入框架Guice的出現(xiàn),使得Spring框架及其社區(qū)也“順應(yīng)民意”,推出并持續(xù)完善了基于Java代碼和Annotation元信息的依賴關(guān)系綁定描述方式,即JavaConfig項(xiàng)目。

?基于JavaCOnfig方式的依賴關(guān)系綁定描述基本上映射了最早的基于XML的配置方式,比如:

?(1)表達(dá)形式層面

?基于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"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/shcema/beans/spring-beans.xsd
        http://www.springframework.org/shcema/context
        http://www.springframework.org/shcema/context/spring-context.xsd">
    <!-- bean定義 -->
</bean>

?而基于JavaConfig的配置方式是這樣的:

@Configuration
public class MockConfiguration{
    //bean定義
}

?任何一個(gè)標(biāo)注了@Configuration的Java類定義都是一個(gè)JavaConfig配置類。

?(2)注冊(cè)bean定義層面

?基于XML的配置形式是這樣的:

<bean id="mockService" class="..MockServiceImpl">
    ...
</bean>

?而基于JavaConfig的配置形式是這樣的:

@Configuration
public class MockConfiguration{
    @Bean
    public MockService mockService(){
        return new MockServiceImpl();
    }
}

?任何一個(gè)標(biāo)注了@Bean的方法,其返回值將作為一個(gè)bean定義注冊(cè)到Spring的IOC容器,方法名將默認(rèn)成為該bean定義的id。

?(3)表達(dá)依賴關(guān)系注入關(guān)系層面

?為了表達(dá)bean與bean之間的依賴關(guān)系,在XML形式中一般是這樣的:

<bean id="mockService" class="..MockServiceImpl">
    <property name="dependencyService" ref="dependencyService"/>
</bean>

<bean id="dependencyService" class="DependencyServiceImpl"/>

?而在JavaConfig中則是這樣的:

@Configuration
public class MockConfiguration {
    
    @Bean
    public MockService mockService(){
        return new MockServiceImpl(denpendcyService());
    }
    
    @Bean
    public DependencyService dependencyService(){
        return new DependencyServiceImpl();
    }
}

?如果一個(gè)bean的定義依賴其他bean,則直接調(diào)用對(duì)應(yīng)JavaConfig類中依賴bean的創(chuàng)建方法就可以了。

注意:在JavaConfig形式的依賴注入過(guò)程中,我們使用方法調(diào)用的形式注入依賴,如果這個(gè)方法返回的對(duì)象實(shí)例只被一個(gè)bean依賴注入,那也還好,如果多于一個(gè)bean需要依賴這個(gè)方法調(diào)用返回的對(duì)象實(shí)例,那是不是意味著我們就會(huì)創(chuàng)建多個(gè)同一類型的對(duì)象實(shí)例?
從代碼表述的邏輯來(lái)看,直覺(jué)上應(yīng)該是會(huì)創(chuàng)建多個(gè)同一類型的對(duì)象實(shí)例,但實(shí)際上最終結(jié)果卻不是這樣,依賴注入的都是同一Singleton的對(duì)象實(shí)例,那這是如何做到的?
筆者一開(kāi)始以為Spring框架會(huì)通過(guò)解析JavaConfig的代碼結(jié)構(gòu),然后通過(guò)解析器轉(zhuǎn)換加上反射等方式完成這一目的,但實(shí)際上Spring的參考文檔對(duì)象的問(wèn)題,一旦擁有攔截邏輯的子類發(fā)現(xiàn)當(dāng)前方法沒(méi)有對(duì)應(yīng)的類型實(shí)例時(shí)才會(huì)去請(qǐng)求父類的同一方法來(lái)初始化對(duì)象實(shí)例,否則直接返回之前的對(duì)象實(shí)例。

?所以,原來(lái)Spring IOC容器中有的特性(features)在JavaConfig中都可以表達(dá),只是換了一種形式而已,而且,通過(guò)聲明相應(yīng)的Java Annotation反而“內(nèi)聚”一處,變得更加簡(jiǎn)潔明了了。

三、高曝光率的Annotation

?至于@Configuration,我想前面已經(jīng)提及了,這里不再贅述,下面我們看幾個(gè)其他比較常見(jiàn)的Annotation,便于為后面更好地理解SpringBoot框架的奧秘做準(zhǔn)備。
?1.@ComponentScan
?@ComponentScan對(duì)應(yīng)XML配置形式中的<context:component-scan>元素,用于配合一些元信息Java Annotation的bean定義類批量采集到Spring的IOC容器中。
?我們可以通過(guò)basePackages等屬性來(lái)細(xì)粒度地定制@ComponentScan自動(dòng)掃描的范圍,如果不指定,則默認(rèn)Spring框架實(shí)現(xiàn)從聲明@ComponentScan所在類的package進(jìn)行掃描。
?2.@PropertySource與@PropertySources
?@PropertySource用于某些地方加載*.properties文件內(nèi)容,并將其中的屬性加載到IOC容器中,便于填充一些bean定義屬性到占位符(placeholder),當(dāng)然,這需要PropertySourcesPlaceholderConfigurer的配合。
?如果我們使用Java 8或者更高版本開(kāi)發(fā),那么,我們可以并行聲明多個(gè)@PropertySource:

@Configuration
@PropertySource("classpath:1.properties")
@PropertySource("classpath:2.properties")
@PropertySource("...")
public class XConfiguration{
    ...
}

?如果我們使用低于Java 8版本到Java開(kāi)發(fā)Spring應(yīng)用,又想聲明多個(gè)@PropertySource,則需要借助@PropertySources的幫助了:

@PropertySources({
    @PropertySource("classpath:1.properties")
    @PropertySource("classpath:2.properties")
    ...
})
public class XConfiguration{
    ...
}

?3.@Import與@ImportResource
?在XML形式的配置中,我們通過(guò)<import resource="XXX.xml"/>的形式將多個(gè)分開(kāi)的容器配置合到一個(gè)配置中,在JavaConfig形式的配置中,我們則使用@Import這個(gè)Annotation完成同樣目的:

@Configuration
@Import(MockConfiguration.class)
public class XConfiguration {
    ...
}

?@Import只負(fù)責(zé)引入JavaConfig形式定義的IOC容器配置,如果有一些遺留的配置或者遺留系統(tǒng)需要以XML形式來(lái)配置(比如dubbo框架),我們依然可以通過(guò)@ImportResource將它們一起合并到當(dāng)前JavaConfig配置的容器中:

@Configuration
@Import(MockConfiguration.class)
@ImportResource("...")
public class XConfiguration {
    ...
}

四、、bean作用域:理解Bean生命周期

?在bean準(zhǔn)備就緒給我們使用之前,容器默默的執(zhí)行了很多啟動(dòng)步驟。Spring提供了BeanFactory作為Spring容器,平常開(kāi)發(fā)中我們常用其派生類ApplicationContext。盡管BeanFactory和ApplicationContext容器的注冊(cè)方式不太一樣(使用BeanFactory要顯式地調(diào)用其addBeanPostProcessor()方法進(jìn)行注冊(cè))以及加載bean的時(shí)機(jī)不一樣(BeanFactory默認(rèn)采用延遲加載)等,但對(duì)于bean的生命周期管控基本一致。

?Bean的完整生命周期可以認(rèn)為是從容器創(chuàng)建初始化Bean開(kāi)始,直到Spring容器關(guān)閉。事實(shí)上,在Bean被初始化創(chuàng)建之前,容器級(jí)別的接口方法也會(huì)對(duì)Bean產(chǎn)生影響,因此這里把兩個(gè)事件也加進(jìn)來(lái)了。
spring ioc之bean周期.png

接口分類

在以上接口方法中,可以分為幾類:

  1. 容器級(jí)生命周期接口方法:BeanFactoryPostProcessor、BeanPostProcessor和InstantiationAwareBeanPostProcessor。它們都是容器初始化bean時(shí)對(duì)暴露的擴(kuò)展點(diǎn)(容器擴(kuò)展點(diǎn)),一般稱它們的實(shí)現(xiàn)類為“后置處理器”。InstantiationAwareBeanPostProcessor是BeanPostProcessor的子接口,可以在Bean生命周期的另外三個(gè)時(shí)期提供擴(kuò)展的回調(diào)接口,即實(shí)例化之前(調(diào)用postProcessBeforeInstantiation方法)、給bean注入對(duì)應(yīng)屬性后(調(diào)用postProcessPropertyValues方法)和實(shí)例化Bean之后(調(diào)用postProcessAfterInstantiantion方法)。
  2. Bean級(jí)生命周期接口方法:BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean。bean繼承接口然后在bean內(nèi)部實(shí)現(xiàn)方法,因此配置文件中的init-method和destory-method指定的方法。
  3. Bean自身的調(diào)用方法:通過(guò)配置文件中init-method和destory-method指定的方法。

流程概述

  1. [容器級(jí)別]BeanFactoryPostProcessor接口:可以注冊(cè)bean,如果spring配置文件注冊(cè)了該接口的實(shí)現(xiàn)類,可以在spring的bean創(chuàng)建之前,修改bean的定義屬性。例如可以把bean的scope從singleton改為prototype。
  2. [容器級(jí)別]InstantiationAwareBeanPost接口:初始化Bean之前的預(yù)處理。
  3. 初始化Bean
  4. [容器級(jí)別]InstantiationAwareBeanPost接口:如果spring配置文件注冊(cè)了該接口的實(shí)現(xiàn)類,Spring將調(diào)用它們的postProcessPropertyValues方法;
  5. 注入:Spring根據(jù)定義信息(beanDifinition)將值和引用bean注入到bean對(duì)應(yīng)的屬性中。
  6. [Bean級(jí)別]感知類名:如果bean實(shí)現(xiàn)了BeanNameAware接口,Spring將bean的ID傳遞給setBeanName()方法;
  7. [Bean級(jí)別]感知容器:如果bean實(shí)現(xiàn)了ApplicationContextAware接口,Spring將調(diào)用setApplicationContext()方法,將bean所在的應(yīng)用上下文的引用傳入進(jìn)來(lái)。注:BeanFactory容器查找實(shí)現(xiàn)了BeanFactoryAware接口,Spring將調(diào)用setBeanFactory()方法,將BeanFactory容器實(shí)例傳入。
  8. [容器級(jí)]BeanPostProcessor:如果bean實(shí)現(xiàn)了BeanPostProcessor接口,Spring將調(diào)用它們的postProcessBeforeInitialization()方法;
  9. [容器級(jí)別]InitializingBean:如果bean實(shí)現(xiàn)了InitializingBean接口,Spring將調(diào)用它們的postprocessAfterInitialialization()方法;
  10. [Bean自身方法]init-method:如果bean定義中使用指定了init-method初始化方法,該方法也會(huì)被調(diào)用;
  11. [容器級(jí)]BeanPostProcessor:如果bean實(shí)現(xiàn)了BeanPostProcessor接口,Spring將會(huì)調(diào)用它們的postProcessBeforeInitialization()方法;
  12. [容器級(jí)別/后處理器]InstantiationAwareBeanPostProcessor:如果bean實(shí)現(xiàn)了InstantiationAwareBeanPostProcessor接口,Spring將調(diào)用它們的postProcessAfterInitialization()方法;
    此時(shí),bean已經(jīng)準(zhǔn)備就緒,可以被應(yīng)用程序使用了,它們將一直駐留在應(yīng)用上下文,直到該應(yīng)用上下文被銷毀。
  13. [Bean級(jí)]DisposableBean:如果bean實(shí)現(xiàn)了DisposableBean接口,Spring將調(diào)用它的destory()接口方法。
  14. [Bean自身方法]destory-method:如果bean定義中使用destory-method聲明了銷毀方法,該方法也會(huì)被調(diào)用。

五、容器擴(kuò)展點(diǎn):FactoryBean

?FactoryBean接口是Spring IOC容器實(shí)例邏輯的擴(kuò)展點(diǎn)。什么叫擴(kuò)展點(diǎn)?還記得前面說(shuō)得bean生命周期嗎?Spring處理Bean生命周期的事件就是擴(kuò)展點(diǎn)。

5.1 區(qū)分FactoryBean和BeanFactory

?BeanFactory直譯是生產(chǎn)Bean的工廠,在Spring中就是容器,常用的ApplicationContext就是它的一個(gè)繼承類。我們也可以直接使用BeanFactory示例:

BeanFactory factory = new XmlBeanFactory(new FileSystemResource("c:/piratebean.xml"));

?而FactoryBean顧名思義就是一個(gè)bean,但這個(gè)bean和普通bean有點(diǎn)不一樣。

5.2 區(qū)分FactoryBean和普通Bean

?Spring中有兩種類型的Bean,一種是普通Bean,另一種是工廠Bean即FactoryBean,這兩種Bean都被容器管理。工廠Bean是實(shí)現(xiàn)了org.springframework.beans.factory.FactoryBean<T>接口的Bean,從ApplicationContext的getBean()方法獲取的對(duì)象不是該類的一個(gè)實(shí)例,而是該類的getObject()方法所返回的對(duì)象。

?當(dāng)我們需要獲取FactoryBean實(shí)例本身而不是它所產(chǎn)生的bean,則要使用&符號(hào)。

?比如,現(xiàn)有FactoryBean,id為“playerBean”,在容器上調(diào)用getBean("playerBean")將返回FactoryBean產(chǎn)生的bean。調(diào)用getBean("&playerBean")將返回FactoryBean它本身的實(shí)例。

5.3 使用場(chǎng)景

?當(dāng)需要在類中撰寫復(fù)雜的初始化程序,此時(shí)使用java編碼比使用XML配置更容易表達(dá)。我們常接觸的代理類就可以用這個(gè)接口實(shí)現(xiàn)。在Spring框架內(nèi)部,有很多地方有FactoryBean的實(shí)現(xiàn)類,它們?cè)诤芏鄳?yīng)用如(Spring的AOP、ORM、事務(wù)管理)及與其它第三框架(ehCache)集成時(shí)都有體現(xiàn)。

5.4 示例

package twm.spring.LifecycleTest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;

public class PlayerFactory implements FactoryBean<PlayerActionInterface>, InitializingBean,
        DisposableBean {

    /*被代理的PlayerActionInterface實(shí)現(xiàn)對(duì)象,通過(guò)XML注入*/
    private PlayerActionInterface target;
    public PlayerActionInterface getTarget() {
        return target;
    }
    public void setTarget(PlayerActionInterface target) {
        this.target = target;
    }

    /*本類生成的代理對(duì)象*/
    private PlayerActionInterface proxyObj;


    /*在容器設(shè)置bean必須的屬性之后執(zhí)行初始化工作*/
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet");
        proxyObj = (PlayerActionInterface)Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[] { Class.forName("twm.spring.LifecycleTest.PlayerActionInterface") },
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        System.out.println("method:" + method.getName());
                        System.out.println("觀察進(jìn)攻及防守隊(duì)員跑位");
                        Object result = method.invoke(target, args);
                        System.out.println("無(wú)球跑動(dòng)");
                        return result;
                    }
                });
    }
    public void destroy() throws Exception {
        System.out.println("distory");
    }

    @Override
    public PlayerActionInterface getObject() throws Exception {
        System.out.println("getObject");
        return proxyObj;
    }
    @Override
    public Class<?> getObjectType() {
        return proxyObj == null ? Object.class : proxyObj.getClass();
    }
    @Override
    public boolean isSingleton() {
        return true;
    }
}

?可以看到在PlayerFactory這個(gè)類,我們實(shí)現(xiàn)了FactoryBean接口的三個(gè)方法。其中g(shù)etObject()方法返回的是代理對(duì)象proxyObj。這個(gè)對(duì)象是在bean初始化回調(diào)接口InitializingBean的實(shí)現(xiàn)方法afterPropertiesSet()里創(chuàng)建的。使用了Proxy.newProxyInstance給原來(lái)的類創(chuàng)建了一個(gè)代理類,該代理類在方法調(diào)用的前后都加上了動(dòng)作。

六、容器擴(kuò)展點(diǎn):后置處理器BeanPostProcessor

6.1 綜述

?先回顧bean生命周期的這張圖,看看BeanPostProcessor調(diào)用位置。
spring ioc之bean周期.png

?通過(guò)上圖看到BeanPostProcessor(Bean后置處理器)兩個(gè)方法在bean生命周期的位置,即:在Spring容器完成Bean實(shí)例化和屬性設(shè)置后,并且在bean調(diào)用初始化方法之前或之后。因此BeanPostProcessor(Bean后置處理器)常用在:對(duì)bean內(nèi)部的值進(jìn)行修改;實(shí)現(xiàn)Bean的動(dòng)態(tài)代理等。

?可以定義一個(gè)或者多個(gè)BeanPostProcessor接口的實(shí)現(xiàn),然后注冊(cè)到容器中。那么該容器里管控的所有Bean在調(diào)用初始化方法之前或之后,都會(huì)調(diào)用BeanPostProcessor接口中對(duì)應(yīng)的方法。

?InstantiationAwareBeanPostProcessor是BeanPostProcessor的子接口。從最上面的的生命周期圖,我們知道它在Bean生命周期的另外三個(gè)時(shí)期提供擴(kuò)展的回調(diào)接口。其使用方法與BeanPostProcessor接口類似,只是回調(diào)時(shí)機(jī)不同。

?BeanPostProcessor接口有兩個(gè)方法:

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

?容器調(diào)用接口定義的方法時(shí)會(huì)將該受管Bean的實(shí)例和名字通過(guò)參數(shù)傳入方法,經(jīng)過(guò)處理后通過(guò)方法的返回值返回給容器。注意,不能返回null,如果返回的是null那么我們通過(guò)getBean方法將得不到目標(biāo)。

?BeanPostProcessor不允許標(biāo)記為延遲加載。因?yàn)槿绻@樣做,Spring容器將不會(huì)注冊(cè)它們,自定義邏輯也就無(wú)法得到應(yīng)用。假如你在<beans/>元素的定義中使用'default-lazy-init'屬性,那就必須將每個(gè)BeanPostProcessor顯式標(biāo)記為'lazy-init="false"'。

6.2 示例

package twm.spring.LifecycleTest;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class beanPostProcessorImpl implements BeanPostProcessor{
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        if(bean instanceof footballPlayer){
            ((footballPlayer) bean).setName("Messi");
        }
        return bean;
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }
}

?配置文件中beans.xml加上,如果定義了多個(gè)BeanPostProcessor,可以在xml通過(guò)order來(lái)通過(guò)order屬性來(lái)指定執(zhí)行的順序。

<bean id="cluo" class="twm.spring.LifecycleTest.footballPlayer">
    <property name="name" value="C.羅納爾多"></property>
</bean>

七、容器擴(kuò)展點(diǎn):后置處理器BeanFactoryPostProcessor

?BeanPostProcessor(Bean后置處理器)常用在對(duì)bean內(nèi)部的值進(jìn)行修改;實(shí)現(xiàn)Bean的動(dòng)態(tài)代理等。BeanFactoryPostProcessor和BeanPostProcessor都是spring初始化bean時(shí)對(duì)外暴露的擴(kuò)展點(diǎn)。但它們有什么區(qū)別呢?

?由Bean生命周期的圖可知:BeanFactoryPostProcessor是生命周期中最早被調(diào)用的,遠(yuǎn)遠(yuǎn)早于BeanPostProcessor。它在spring容器加載了bean的定義文件之后,在bean實(shí)例化之前執(zhí)行的。也就是說(shuō),Spring允許BeanFactoryPostProcessor在容器創(chuàng)建bean之前讀取bean配置元數(shù)據(jù),并可進(jìn)行修改。例如增加bean的屬性和值,重新設(shè)置bean是否作為自動(dòng)裝配的后選者,重設(shè)bean的依賴項(xiàng)等等。

?在spring配置文件中可以同時(shí)配置多個(gè)BeanFactoryPostProcessor,并通過(guò)在xml中注冊(cè)時(shí)設(shè)置'order'屬性來(lái)控制各個(gè)BeanFactoryPostProcessor的執(zhí)行次序。

@FunctionalInterface
public interface BeanFactoryPostProcessor {

    /**
     * Modify the application context's internal bean factory after its standard
     * initialization. All bean definitions will have been loaded, but no beans
     * will have been instantiated yet. This allows for overriding or adding
     * properties even to eager-initializing beans.
     * @param beanFactory the bean factory used by the application context
     * @throws org.springframework.beans.BeansException in case of errors
     */
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

?接口只有一個(gè)方法postProcessBeanFactory。該方法的參數(shù)是ConfigurableListableBeanFactory類型,實(shí)際開(kāi)發(fā)中,我們常使用它的getBeanDefinition()方法獲取某個(gè)bean的元數(shù)據(jù)定義:BeanDefinition。它有這些方法:
BeanDefinition

示例一

?配置文件中定義了一個(gè)bean:

<bean id="messi" class="twm.spring.LifecycleTest.footballPlayer">
    <property name="name" value="Messi"></property>
    <property name="team" value="Barcelona"></property>
</bean>

?創(chuàng)建類BeanFactoryPostProcessorImpl,實(shí)現(xiàn)接口FactoryPostProcessor:

public class beanFactoryPostProcessorImpl implements BeanFactoryPostProcessor{

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("beanFactoryPostProcessorImpl");
        BeanDefinition bdefine=beanFactory.getBeanDefinition("messi");
        System.out.println(bdefine.getPropertyValues().toString());
        MutablePropertyValues pv =  bdefine.getPropertyValues();  
            if (pv.contains("team")) {
                PropertyValue ppv= pv.getPropertyValue("name");
                TypedStringValue obj=(TypedStringValue)ppv.getValue();
                if(obj.getValue().equals("Messi")){
                    pv.addPropertyValue("team", "阿根延");  
                }
        }  
            bdefine.setScope(BeanDefinition.SCOPE_PROTOTYPE);
    }
}

八、容器擴(kuò)展點(diǎn):動(dòng)態(tài)注冊(cè)BeanDefinitionRegistryPostProcessor

?BeanDefinitionRegistryPostProcessor繼承自BeanFactoryPostProcessor,是一種比較特殊的BeanFactoryPostProcessor。BeanDefinitionRegistryPostProcessor中定義的postProcessBeanDefinitionRegistry(BeanDeinitionRegistry registry)方法可以讓我們實(shí)現(xiàn)自定義的注冊(cè)bean定義的邏輯。下面的示例中就新定義了一個(gè)名為hello,類型為Hello的bean定義。

public class CustomBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {

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

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        RootBeanDefinition helloBean = new RootBeanDefinition(Hello.class);
        //新增Bean定義
        registry.registerBeanDefinition("hello", helloBean);
    }

}

?下面配置可以讓CustomBeanDefinitionRegistry起效:

@Import(CustomBeanDefinitionRegistry.class)
public class Application{
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

如果需要給我修改意見(jiàn)的發(fā)送郵箱:erghjmncq6643981@163.com

資料參考:《SpringBoot揭秘-快速構(gòu)建微服務(wù)體系》
轉(zhuǎn)發(fā)博客,請(qǐng)注明,謝謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容