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
微信公眾號:
技術交流群: