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定義。
預設情況下,會預先執行個體化所有單例。是以,重要的是(至少對于單例bean),如果有一個(父)
ApplicationContext
定義僅打算用作模闆,并且此定義指定了一個類,則必須確定将
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
執行個體執行它們的工作。(備注:在Spring容器初始化bean過程中執行相關的回調操作)
BeanPostProcessor
執行個體是按容器劃分作用域。僅僅在使用繼承容器才有意義。如果在一個容器中定義
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
可能會通路意外的Bean,是以使它們不适合進行自動代理或其他類型的Bean後處理。例如,如果你有一個用
Spring
标注的依賴項,其中字段或
@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
自定義bean)。從技術上講,可以在
BeanPostProcessor
中使用Bean執行個體(例如,通過使用
BeanFactoryPostProcessor
BeanFactory.getBean()
),這樣做導緻過早的初始化bean,違反了标準容器生命周期。這會導緻負面的影響,例如:繞過後置處理。
同樣,
執行個體是按容器劃分。(如果你使用分層的容器才會有意義) 如果你定義在容器中定義一個
BeanFactoryPostProcessor
,它僅适用于該容器中的bean定義。在一個容器中的bean定義不會被其他的容器中
BeanFactoryPostProcessor
後置處理,即使兩個容器在同一個層級。
BeanFactoryPostProcessor
Bean工廠後處理器在
ApplicationContext
中聲明時會自動執行,以便将更改應用于定義容器的配置中繼資料。Spring包括一些預定義的工廠後置處理器,例如
PropertyOverrideConfigurer
PropertySourcesPlaceholderConfigurer
。你可以使用一個自定義的
BeanFactoryPostProcessor
-例如,注冊一個自定義屬性編輯器。
ApplicationContext
自動地檢測任何部署到容器中并實作
BeanFactoryPostProcessor
接口的執行個體bean。使用這些bean作為bean工廠後置處理器,在适當的時間。你可以像其他ban一樣部署這些後置處理器。
與一樣,通常不希望配置
BeanPostProcessors
進行延遲初始。如果沒有其他bean引用
BeanFactoryPostProcessors
,這個後置處理器不會被初始化。是以,标記它為延遲初始化将被忽略并且
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時,即在非<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
ApplicationContext
階段,bean的解析将失敗。
preInstantiateSingletons()
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()
傳回一個單例傳回true否則為falseFactoryBean
-
:傳回由getObject()方法傳回的對象類型;如果類型未知,則傳回nullClass getObjectType()
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.techCSDN:
https://blog.csdn.net/liyong1028826685微信公衆号:

技術交流群: