天天看點

Spring 5 中文解析核心篇-IoC容器之BeanDefinition繼承與容器拓展點

1.7 Bean Definition繼承

bean

定義包含一些配置資訊,包括構造函數參數、屬性值、和容器特定資訊,例如初始化方法、靜态工廠方法名等等。子

bean

定義繼承父

bean

定義配置資料。子

bean

定義能夠覆寫一些值或者增加其他需要。使用父

bean

和子

bean

定義能夠儲存一些類型。實際上,這是一種模版模式。

如果你程式設計式使用

ApplicationContext

接口,子

bean

定義通過

ChildBeanDefinition

類描述。大多數使用者不在這個級别使用(備注:應用開發人員一般不會接觸)。相反,它們在例如

ClassPathXmlApplicationContext

之類的類中聲明性地配置

bean

定義。當你使用基于XML配置中繼資料,你可以通過使用

parent

屬性訓示一個子

bean

的定義,指定

parent

bean作為這個屬性的值。下面例子顯示怎樣做:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>
<!--parent指定繼承父類-->
<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>           

如果沒有指定,子

bean

定義将使用父定義中的

bean

類,但也可以覆寫它。在後面這種情況下,子

bean

類必須相容父

bean

定義(也就是說,必須接受父

bean

的屬性值)。

bean

定義繼承作用域、構造參數值、屬性值、和覆寫父類方法,并可以添加新值。任何作用域、初始化方法、銷毀方法或

static

工廠方法設定都會覆寫對應的父

bean

設定。

其餘設定始終從子

bean

定義中擷取:依賴、自動裝配、依賴檢查、單例和懶加載。

前面的例子通過使用

abstract

屬性顯示标記父

bean

定義作用一個抽象,如果父

bean

定義沒有指定類,顯示地标記父

bean

定義為

abstract

是需要的,像下面例子展示:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>           

bean

不能被執行個體化,因為它是不完整的,并且它被顯示的标記為

abstract

。當一個定義是

abstract

,它僅僅作為一個

bean

定義的模版且父

bean

定義為子

bean

定義服務。嘗試自己使用這樣的抽象父

bean

,通過将其引用為另一個

bean

ref

屬性或使用父

bean

ID

進行顯式的

getBean()

調用将傳回錯誤。類似地,容器的内部

preInstantiateSingletons()

方法将忽略定義為抽象的bean定義。

預設情況下,

ApplicationContext

會預先執行個體化所有單例。是以,重要的是(至少對于單例bean),如果有一個(父)

bean

定義僅打算用作模闆,并且此定義指定了一個類,則必須確定将

abstract

屬性設定為

true

,否則應用程式上下文将實際上(試圖)預先執行個體化抽象

Bean

參考執行個體:

com.liyong.ioccontainer.starter.XmlBeanDefinitionInheritanceContainer

1.8 容器拓展點

典型地,應用程式開發者不需要實作

ApplicationContext

類。相反,

Spring IoC

容器通過插件實作指定的內建接口去擴充。下面的章節描述這些接口的內建。

1.8.1 通過使用

BeanPostProcessor

自定義bean

BeanPostProcessor

接口定義回調方法,你可以實作這個接口提供你自己的(或者覆寫容器的預設設定)初始化邏輯、依賴解析邏輯等等。如果你想實作一些自定義邏輯,在

Spring

容器完成執行個體化、配置、初始化

bean

之後,你可以插入一個或多個自定義

BeanPostProcessor

實作。

你可以配置多個

BeanPostProcessor

執行個體并且你可以通過設定

order

屬性來控制這些

BeanPostProcessor

執行個體的執行順序。僅僅

BeanPostProcessor

實作

Ordered

接口是可以設定這個屬性。如果自己實作

BeanPostProcessor

,你應該考慮實作

Ordered

接口。更多詳情,檢視

BeanPostProcessor

Ordered

接口。參見

有關以程式設計方式注冊BeanPostProcessor執行個體的說明

BeanPostProcessor

接口在

bean

(對象)執行個體上操作。也就是說,

Spring IoC

容器執行個體化一個

bean

執行個體,然後

BeanPostProcessor

執行個體執行它們的工作。(備注:在Spring容器初始化bean過程中執行相關的回調操作)

BeanPostProcessor

執行個體是按容器劃分作用域。僅僅在使用繼承容器才有意義。如果在一個容器中定義

BeanPostProcessor

,僅僅在那個容器中的

bean

被後置處理。換句話說,定義在一個容器中的

bean

不會被在其他容器定義的

BeanPostProcessor

所處理,即使兩個容器都屬于同一層次結構。

改變實際

bean

定義(也就是定義

bean

的藍圖),你需要使用

BeanFactoryPostProcessor

,如使用 BeanFactoryPostProcessor自定義配置中繼資料中所述

org.springframework.beans.factory.config.BeanPostProcessor

接口恰好地由兩個回調方法組成。當一個類作為後置處理起被注冊到容器中時,對于每個被容器建立

bean

執行個體,後置處理器從容器初始化方法(例如:

InitializingBean.afterPropertiesSet()

或者任何被聲明init方法)被調用之前,并且任何

bean

初始化回調之後獲得回調。後置處理器能夠處理bean執行個體任何操作,包括忽略所有的回調。

Bean

後處理器通常檢查回調接口,或者可以用代理包裝

Bean

Spring AOP

基礎設施類中實作

bean

的後置處理去提供一個代理包裝邏輯。

ApplicationContext

自動的檢查所有

bean

,這些

bean

在配置中繼資料中實作了

BeanPostProcessor

接口。

ApplicationContext

注冊這些

bean

作為後置處理器,以便以後在建立

bean

時可以調用它們。

Bean

後處理器可以與其他

bean

相同的方式部署在容器中。

注意,當通過在類上使用

@Bean

工廠方法聲明

BeanPostProcessor

時,工廠方法傳回類型應該是實作類本身或隻是實作

org.springframework.beans.factory.config.BeanPostProcessor

接口,清晰地表明該

bean

的後處理器性質。否則,

ApplicationContext

無法在完全建立之前按類型自動檢測它。由于

BeanPostProcessor

需要及早執行個體化才能應用于上下文中其他

bean

的初始化,是以這種早期類型檢測至關重要。

程式設計式地注冊BeanPostProcessor執行個體

推薦的去注冊

BeanPostProcessor

方式是通過

ApplicationContext

自動檢測(前面描述),可以使用

addBeanPostProcessor

方法以程式設計方式針對

ConfigurableBeanFactory

注冊它們。當注冊之前你需要評估條件邏輯甚至需要跨層次結構的上下文複制後置處理器時,這是非常有用的。注意,然而,

BeanPostProcessor

執行個體程式設計式的添加不遵循

Ordered

排序接口。在這裡,注冊的順序決定了執行的順序。需要注意是程式設計式的注冊

BeanPostProcessor

執行個體總是在這些通過自動檢測的後置處理器之後被處理,而不管顯示的順序。

BeanPostProcessor執行個體和AOP自定代理

BeanPostProcessor

接口的類是特殊的,并且容器對它們的處理方式有所不同。在啟動時會執行個體化它們直接引用的所有

BeanPostProcessor

執行個體和

Bean

,這是

ApplicationContext

特殊啟動階段的一部分。接下來,以排序方式注冊所有

BeanPostProcessor

執行個體,并将其應用于容器中的所有其他

bean

。因為

AOP

自動代理是作為

BeanPostProcessor

本身實作的,是以

BeanPostProcessor

執行個體或它們直接引用的

bean

都不适合進行自動代理,是以,沒有編織的方面。

對于任何這樣的

bean

,你應該檢視日志消息:

Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)

.

如果你有通過使用自動裝配或

@Resource

注入

bean

BeanPostProcessor

中(可能回退到自動裝配),當搜尋類型比對的依賴項候選者時,

Spring

可能會通路意外的Bean,是以使它們不适合進行自動代理或其他類型的Bean後處理。例如,如果你有一個用

@Resource

标注的依賴項,其中字段或

Setter

名稱不直接與

bean

的聲明名稱相對應,并且不使用

name

屬性,那麼

Spring

将通路其他

bean

以按類型比對它們。

下面的例子展示了在

ApplicationContext

中怎樣去編寫、注冊和使用

BeanPostProcessor

例子:Hello World

第一個例子說明基礎的使用。這個例子展示一個自定義

BeanPostProcessor

實作并調用通過容器建立的每個

bean

toString()

方法并且列印結果字元串到控制台。

下面的清單展示了自定義

BeanPostProcessor

實作類定義:

package scripting;

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

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
      //列印bean資訊
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}           

下面

beans

元素使用

InstantiationTracingBeanPostProcessor

<?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:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>           

注意怎樣定義

InstantiationTracingBeanPostProcessor

。它甚至沒有名字,并且它是一個

bean

,可以像其他bean一樣被依賴注入。(前面的配置還定義了一個由

Groovy

腳本支援的

bean

Spring

動态語言支援詳情在“

動态語言支援

”一章中。)

下面的Java應用程式運作前面的代碼和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}           

前面的應用輸出結果類似下面:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961           

例子:RequiredAnnotationBeanPostProcessor

将回調接口或與自定義

BeanPostProcessor

實作結合使用是擴充

Spring IoC

容器常用方法。

Spring

RequiredAnnotationBeanPostProcessor

是一個示例,它是

Spring

發行版附帶的

BeanPostProcessor

實作,可以確定标有(任意)注解的

bean

上的

JavaBean

屬性實際上(配置為)依賴注入了一個值。說明:就是常用的依賴注入。

參考代碼:com.liyong.ioccontainer.starter.XmlBeanPostProcessorIocContainer

1.8.2 BeanFactoryPostProcessor自定義配置中繼資料

下一個拓展點是

org.springframework.beans.factory.config.BeanFactoryPostProcessor

。這個接口語義類似BeanPostProcessor,一個重要的不同點:

BeanFactoryPostProcessor

是操作在bean的配置中繼資料上。也就是說,Spring IoC容器允許除

BeanFactoryPostProcessor

執行個體外其他任何bean被

BeanFactoryPostProcessor

讀取配置中繼資料和改變它。

BeanFactoryPostProcessor

執行個體,并且你可以通過設定order屬性在這些

BeanFactoryPostProcessor

執行個體上來控制順序。然而,如果

BeanFactoryPostProcessor

Ordered

接口才能設定這個屬性。如果你寫自己的

BeanFactoryPostProcessor

Ordered

接口。更多關于

BeanFactoryPostProcessor

Ordered

接口詳細資訊。

如果想去改變實際bean執行個體(也就是,這個對象是從配置中繼資料被建立的),你需要使用

BeanPostProcessor

(前面描述通過使用

BeanPostProcessor

自定義bean)。從技術上講,可以在

BeanFactoryPostProcessor

中使用Bean執行個體(例如,通過使用

BeanFactory.getBean()

),這樣做導緻過早的初始化bean,違反了标準容器生命周期。這會導緻負面的影響,例如:繞過後置處理。

同樣,

BeanFactoryPostProcessor

執行個體是按容器劃分。(如果你使用分層的容器才會有意義) 如果你定義在容器中定義一個

BeanFactoryPostProcessor

,它僅适用于該容器中的bean定義。在一個容器中的bean定義不會被其他的容器中

BeanFactoryPostProcessor

後置處理,即使兩個容器在同一個層級。

Bean工廠後處理器在

ApplicationContext

中聲明時會自動執行,以便将更改應用于定義容器的配置中繼資料。Spring包括一些預定義的工廠後置處理器,例如

PropertyOverrideConfigurer

PropertySourcesPlaceholderConfigurer

。你可以使用一個自定義的

BeanFactoryPostProcessor

-例如,注冊一個自定義屬性編輯器。

ApplicationContext

自動地檢測任何部署到容器中并實作

BeanFactoryPostProcessor

接口的執行個體bean。使用這些bean作為bean工廠後置處理器,在适當的時間。你可以像其他ban一樣部署這些後置處理器。

BeanPostProcessors

一樣,通常不希望配置

BeanFactoryPostProcessors

進行延遲初始。如果沒有其他bean引用

Bean(Factory)PostProcessor

,這個後置處理器不會被初始化。是以,标記它為延遲初始化将被忽略并且

Bean(Factory)PostProcessor

将被提前初始化即使你的聲明

default-lazy-init

屬性為true。

參考代碼:

com.liyong.ioccontainer.starter.XmlBeanFactoryPostProcessorIocContainer

例如:類名替換

PropertySourcesPlaceholderConfigurer

可以使用

PropertySourcesPlaceholderConfigurer

通過使用标準Java屬性格式将屬性值從bean定義外部化到一個單獨的檔案中(意思是:bean配置資料可以配置到一個單獨檔案中)。這樣做使部署應用程式的人員可以自定義特定于環境的屬性,例如資料庫URL和密碼,而無需為修改容器的主要XML定義檔案而複雜或冒風險。

考慮下面基于XML配置中繼資料片段,

DataSource

使用占位符值定義:

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>           

這個例子顯示從外部化

Properties

檔案的屬性配置。在運作時,

PropertySourcesPlaceholderConfigurer

應用中繼資料替換

DataSource

的屬性值。将要替換的值指定為

$ {property-name}

格式的占位符,該格式遵循

Ant

log4j

JSPEL

樣式。

真實值來自于标準的

Properties

格式的其他檔案:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root           

是以,

${jdbc.username}

字元串在運作時被替換為

sa

值,并且同樣應用到其他的占位符在

properties

檔案中比對這些key。

PropertySourcesPlaceholderConfigurer

檢查Bean定義的大多數屬性和屬性中的占位符。此外,你可以自定義占位符的字首和字尾。

context命名空間在Spring2.5中被引入,你可以配置一個專用配置元素的占位符。在

location

屬性中你可以提供一個或多個

location

通過逗号分隔,類似下面例子:

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>           

PropertySourcesPlaceholderConfigurer

不僅僅在你指定的Properties中查找屬性。預設情況,如果在指定的屬性檔案中沒有找到屬性,它會再次檢查Spring

Environment

屬性和正常的Java System屬性。

你可以使用

PropertySourcesPlaceholderConfigurer

去替代類名稱,有時候者非常有用,當你在運作時必須選擇一個特定的實作類。下面例子展示怎樣去做:
<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
 <property name="locations">
     <value>classpath:com/something/strategy.properties</value>
 </property>
 <property name="properties">
     <value>custom.strategy.class=com.something.DefaultStrategy</value>
 </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>           
如果在運作時無法将類解析為有效的類,則在即将建立bean時,即在非

lazy-init

bean的

ApplicationContext

preInstantiateSingletons()

階段,bean的解析将失敗。

com.liyong.ioccontainer.starter.XmlPropertySourcesPlaceholderConfigurerIocContainer

例子:

PropertyOverrideConfigurer

另一個bean工廠後處理器

PropertyOverrideConfigurer

類似于

PropertySourcesPlaceholderConfigurer

,但與後者不同,原始定義可以具有bean屬性的預設值,也可以完全沒有值。如果覆寫的屬性檔案對于一些bean屬性沒有符合内容,預設的上下文定義被使用。

注意:bean定義沒有意識到被覆寫,是以從XML定義檔案中不能立即看出覆寫的配置正在被使用。由于存在覆寫機制,在多個

PropertyOverrideConfigurer

執行個體情況下對應相同bean屬性不同的值,最後一個将被使用。

屬性檔案配置格式:

beanName.property=value           

下面清單顯示格式化例子:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb           

此示例檔案可與容器定義一起使用,容器定義包含一個名為

dataSource

的bean,該bean具有

driver

url

屬性。

複合屬性名也是被支援的,隻要路徑的每個元件(被覆寫的最終屬性除外)都是非空的(可能是由構造函數初始化的)。下面的例子,bean的

tom

屬性

fred

sammy

屬性設定隻123:

tom.fred.bob.sammy=123           
指定的重寫值總是文本值。它們沒有被轉譯為bean引用。當XML bean定義中的原始值指定bean引用時,這種約定也适用。

context命名空間在Spring2.5中被引入,可以使用專用配置元素配置屬性覆寫,類似下面例子:

<context:property-override location="classpath:override.properties"/>           

com.liyong.ioccontainer.starter.XmlPropertyOverrideConfigurerIocContainer

1.8.3 FactoryBean自定義初始化邏輯

可以為本身就是工廠的對象實作

org.springframework.beans.factory.FactoryBean

這個

FactoryBean

接口是Spring IoC容器的執行個體化邏輯可插拔點。如果你有一個複雜的初始化代碼在Java中比備援XML更好的表達,你可以建立你自己的

FactoryBean

,在實作類中寫複雜的初始化邏輯,然後插入你的自定義

FactoryBean

到容器中。

FactoryBean

接口提供三個方法:

  • Object getObject()

    :傳回這個工廠建立的一個執行個體。這個執行個體可能被共享,依賴于這個工廠傳回的是單例或原型。
  • boolean isSingleton()

    :如果

    FactoryBean

    傳回一個單例傳回true否則為false
  • Class getObjectType()

    :傳回由getObject()方法傳回的對象類型;如果類型未知,則傳回null

FactoryBean

概念和接口在Spring架構中一些地方被使用到。Spring有超過50個

FactoryBean

接口的實作。

當你需要向容器擷取一個實際的

FactoryBean

執行個體本身而不是由它産生的bean時請在調用ApplicationContext的

getBean()

方法時在該bean的ID前面加上一個

符号。是以,對于id為

myBean

的給定

FactoryBean

,在容器上調用

getBean(“myBean”)

将傳回

FactoryBean

的産生的bean,而調用

getBean(“&myBean”)

FactoryBean

執行個體本身。

com.liyong.ioccontainer.service.CustomFactorBean

作者

個人從事金融行業,就職過易極付、思建科技、某網約車平台等重慶一流技術團隊,目前就職于某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大資料、資料存儲、自動化內建和部署、分布式微服務、響應式程式設計、人工智能等領域。同時也熱衷于技術分享創立公衆号和部落格站點對知識體系進行分享。

部落格位址:

http://youngitman.tech

CSDN:

https://blog.csdn.net/liyong1028826685

微信公衆号:

Spring 5 中文解析核心篇-IoC容器之BeanDefinition繼承與容器拓展點

技術交流群:

Spring 5 中文解析核心篇-IoC容器之BeanDefinition繼承與容器拓展點