1.8,容器擴展點
通常,應用程式開發人員不需要對ApplicationContext 實作類進行子類化,相反,可以通過插入特殊集成介面的實作來擴展Spring IoC容器,接下來的幾節描述了這些集成介面,
1.8.1,自定義bean實作BeanBeanPostProcessor介面
BeanPostProcessor介面定義了回呼方法,您可以實作這些回呼方法來修改默認的bean實體化的邏輯,依賴關系決議邏輯等,
如果您想在Spring容器完成實體化,配置和初始化bean之后實作一些自定義邏輯,則可以插入一個或多個自定義BeanPostProcessor,
您可以配置多個BeanPostProcessor實體,并且可以BeanPostProcessor通過實作Ordered 介面設定order屬性來控制這些實體的運行順序,
@Component
public class MyBeanPostProcessor implements BeanPostProcessor, Ordered {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
BeanPostProcessor實體操作的是bean的實體,
也就是說,Spring IoC容器實體化一個bean實體,
然后使用BeanPostProcessor對這些實體進行處理加工,
BeanPostProcessor實體是按容器劃分作用域的,
僅在使用容器層次結構時,這才有意義,
如果BeanPostProcessor在一個容器中定義一個,它將僅對該容器中的bean進行后處理,
換句話說,一個容器中定義的bean不會被BeanPostProcessor另一個容器中的定義進行后處理,
即使這兩個容器是同一層次結構的一部分也是如此,
BeanPostProcessor修改的是bean實體化之后的內容,
如果要更改實際的bean定義(即bean definition)
您需要使用 BeanFactoryPostProcessor介面.
org.springframework.beans.factory.config.BeanPostProcessor介面恰好由兩個回呼方法組成,
當此類被注冊為容器的post-processor時,對于容器創建的每個bean實體,post-processor都會在任何bean實體化之后并且在容器初始化方法(例如InitializingBean.afterPropertiesSet()或任何宣告的init方法)被使用之前呼叫,
post-processor可以對bean實體執行任何操作,也可以完全忽略回呼,
post-processor通常檢查回呼介面,或者可以用代理包裝Bean,
一些Spring AOP基礎結構類被實作為post-processor,以提供代理包裝邏輯,
ApplicationContext自動檢測實作BeanPostProcessor介面所有bean,注意是要注冊成bean,僅僅實作介面是不可以的,
請注意,通過使用@Bean工廠方法宣告BeanPostProcessor時,工廠方法的回傳型別應該是實作類本身或至少是org.springframework.beans.factory.config.BeanPostProcessor 介面,以清楚地表明該bean的post-processor性質,
否則,ApplicationContext無法在完全創建之前按型別自動檢測它,
由于BeanPostProcessor需要提前實體化以便應用于背景關系中其他bean的初始化,因此這種早期型別檢測至關重要,
@Bean
public BeanPostProcessor myBeanPostProcessor(){
return new MyBeanPostProcessor();
}
以編程方式注冊BeanPostProcessor實體
雖然推薦的BeanPostProcessor注冊方法是通過ApplicationContext自動檢測,
但是您可以ConfigurableBeanFactory使用addBeanPostProcessor方法通過編程方式對它們進行注冊,
當您需要在注冊之前評估條件邏輯(比如應用場景是xxx條件才注冊,xxx條件不注冊時),
甚至需要跨層次結構的背景關系復制Bean post-processor時,這將非常有用,
但是請注意,以BeanPostProcessor編程方式添加的實體不遵守該Ordered介面,
在這里,注冊的順序決定了執行的順序,
還要注意,以BeanPostProcessor編程方式注冊的實體總是在通過自動檢測注冊的實體之前進行處理,
而不考慮任何明確的順序,
BeanPostProcessor 實體和AOP自動代理
實作BeanPostProcessor介面的類是特殊的,并且容器對它們的處理方式有所不同,
BeanPostProcessor它們直接參考的所有實體和bean在啟動時都會實體化,
作為ApplicationContext的特殊啟動階段的一部分,
接下來,BeanPostProcessor以排序方式注冊所有實體,并將其應用于容器中的所有其他bean,
但是因為AOP自動代理的實作是通過BeanPostProcessor介面,
所以在AOP的BeanPostProcessor介面實體化之前的
BeanPostProcessor實體或BeanPostProcessor實體直接參考的bean都沒有資格進行自動代理,
并且對于任何此類bean都沒有任何處理切面的BeanPostProcessor指向他們,
您應該看到一條參考性日志訊息:
Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying),
這條訊息的意思大概就是說這個bean沒有得到所有BeanPostProcessor的處理
下面分析一下這條日志的邏輯:我們不用AOP的BeanPostProcessor用AutowiredAnnotationBeanPostProcessor來看這個情況
首先這條日志是在BeanPostProcessorChecker類中列印的,
這個類本身就實作了BeanPostProcessor,
Spring容器增加這個processor的代碼如下:
//獲取所有的BeanPostProcessor型別的bean
//第一個true表示包括非單例的bean
//第二個false表示僅查找已經實體化完成的bean,如果是factory-bean則不算入內
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
//當前beanFactory內的所有post-processor數 + 1 + postBeanNames的數量
//這個數量在后續有個判斷
//beanFactory.getBeanPostProcessorCount() 系統內置processor
//1 就是BeanPostProcessorChecker
//postProcessorNames.length 就是能掃描到的processor
//這個數量之和就是目前系統能看到的所有processor
//還有的就可能是決議完了某些bean又新增了processor那個不算在內
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
//add BeanPostProcessorChecker 進入beanPostProcessor鏈
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
BeanPostProcessorChecker中判斷并列印上邊那條日志的方法如下:
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
//如果當前bean不是postProcessor的實體
//并且不是內部使用的bean
//并且this.beanFactory.getBeanPostProcessorCount()小于剛才相加的值
//三個都滿足才會列印那行日志
if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) &&
this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {
if (logger.isInfoEnabled()) {
logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() +
"] is not eligible for getting processed by all BeanPostProcessors " +
"(for example: not eligible for auto-proxying)");
}
}
return bean;
}
//當前beanName不為空,并且對應的bean是容器內部使用的bean則回傳true
private boolean isInfrastructureBean(@Nullable String beanName) {
if (beanName != null && this.beanFactory.containsBeanDefinition(beanName)) {
BeanDefinition bd = this.beanFactory.getBeanDefinition(beanName);
return (bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE);
}
return false;
}
在看Spring createBean時遍歷postProcessor的代碼
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
就是通過這么一個回圈來執行后置方法applyBeanPostProcessorsAfterInitialization,前置方法也是這樣的
現在假設我們有一個自定義的beanPostProcessor里面需要注入一個我們自定義的beanA,
那么在beanPostProcessor被實體化的時候肯定會要求注入我們自定義的beanA,
那么現在就有多種情況了:
1.我們用的set或者構造器注入那beanA會被實體化并注入
2.如果我們用的@Autowired,當我們自定義的beanPostProcessor實體化
在AutowiredAnnotationBeanPostProcessor實體化之前,那么beanA都無法被注入值
如果在之后,則還是可以被注入值
但是這兩種情況都會列印這行日志
Bean 'beanA' of type [org.springframework.beanA] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
以下示例顯示了如何在ApplicationContext中撰寫,注冊和使用BeanPostProcessor實體,
示例:Hello World,BeanPostProcessor-style
第一個示例演示了基本用法,示例展示了一個自定義BeanPostProcessor實作,它在容器創建每個bean時呼叫該bean的toString()方法,并將結果字串列印到系統控制臺,
下面的清單顯示了自定義的BeanPostProcessor實作類定義:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// 只需按原樣回傳實體化的bean
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // 我們可以回傳任何物件參考
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
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="https://www.cnblogs.com/dabaieyangzhijidi/p/Fiona Apple Is Just So Dreamy."/>
</lang:groovy>
<!--
當上述bean (messenger)被實體化時,這個自定義的BeanPostProcessor實作將事實輸出到系統控制臺 -->
<bean />
</beans>
請注意實體化tracingbeanpostprocessor是如何定義的,它甚至沒有名稱,而且,因為它是一個bean,所以可以像其他bean一樣進行依賴注入,
下面的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的AutowiredAnnotationBeanPostProcessor——一個隨Spring發行版附帶的BeanPostProcessor實作,它確保被注解(@Autowired,@Value, @Inject等注解)注釋的屬性會被注入一個bean實體,
1.8.2,自定義配置元資料BeanFactoryPostProcessor
我們要看的下一個擴展點是 org.springframework.beans.factory.config.BeanFactoryPostProcessor,
該介面與BeanPostProcessor主要區別在于:BeanFactoryPostProcessor對Bean配置元資料進行操作,
也就是說,Spring IoC容器允許BeanFactoryPostProcessor讀取配置元資料,并有可能在容器實體化實體任何bean之前更改元資料,
您可以配置多個BeanFactoryPostProcessor實體,并且可以BeanFactoryPostProcessor通過設定order屬性來控制這些實體的運行順序,但是,僅當BeanFactoryPostProcessor實作 Ordered介面時才能設定此屬性,
如果希望更改實際bean實體(從配置元資料創建的物件),則需要使用BeanPostProcessor,
盡管在BeanFactoryPostProcessor中使用bean實體在技術上是可行的(例如,通過使用BeanFactory.getBean()),
但是這樣做會導致過早的bean實體化,違反標準的容器生命周期,
這可能會導致負面的副作用,比如繞過bean的后處理,
另外,BeanFactoryPostProcessor實體的作用域為每個容器,
這只有在使用容器層次結構時才有用,
如果您在一個容器中定義了BeanFactoryPostProcessor,那么它只應用于該容器中的bean定義,
一個容器中的Bean定義不會被另一個容器中的BeanFactoryPostProcessor實體進行后處理,即使這兩個容器屬于同一層次結構,
當BeanFactoryPostProcessor在ApplicationContext中宣告時,它將自動運行,以便對定義容器的配置元資料應用更改,
Spring包括許多預定義的bean工廠后處理器,如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer,
您還可以使用自定義BeanFactoryPostProcessor例如,用于注冊自定義屬性編輯器,
ApplicationContext自動檢測部署其中實作BeanFactoryPostProcessor介面的任何bean,在適當的時候,這些bean會被bean factory post-processors來使用,
你也可以像部署任何其他bean一樣部署這些自定義的bean factory post-processors,
示例:PropertySourcesPlaceholderConfigurer
您可以使用PropertySourcesPlaceholderConfigurer使用標準的Java屬性格式將bean定義中的屬性值外部化到單獨的檔案中,這樣,部署應用程式的人員就可以自定義特定于環境的屬性,比如資料庫url和密碼,而無需修改主XML定義檔案或容器檔案的復雜性或風險,
考慮以下基于xml的配置元資料片段,其中定義了具有占位符值的資料源:
<bean >
<property name="locations" value="https://www.cnblogs.com/dabaieyangzhijidi/p/classpath:com/something/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
>
<property name="driverClassName" value="https://www.cnblogs.com/dabaieyangzhijidi/p/${jdbc.driverClassName}"/>
<property name="url" value="https://www.cnblogs.com/dabaieyangzhijidi/p/${jdbc.url}"/>
<property name="username" value="https://www.cnblogs.com/dabaieyangzhijidi/p/${jdbc.username}"/>
<property name="password" value="https://www.cnblogs.com/dabaieyangzhijidi/p/${jdbc.password}"/>
</bean>
該示例顯示了從外部Properties檔案配置的屬性,
在運行時,將 PropertySourcesPlaceholderConfigurer應用于替換資料源的某些屬性的元資料,將要替換的值指定為形式的占位符,該形式${property-name}遵循Ant和log4j和JSP EL樣式,
實際值來自標準Java Properties格式的另一個檔案:
jdbc.driverClassName = org.hsqldb.jdbcDriver
jdbc.url = jdbc:hsqldb:hsql://production:9002
jdbc.username = sa
jdbc.password = root
因此,${jdbc.username}在運行時將字串替換為值“sa”,并且其他與屬性檔案中的鍵匹配的占位符值也適用,
在PropertySourcesPlaceholderConfigurer為大多數屬性和bean定義的屬性占位符檢查,此外,您可以自定義占位符前綴和后綴,
<bean >
<property name="locations" value="https://www.cnblogs.com/dabaieyangzhijidi/p/classpath:jdbc.properties"/>
//自定義前綴后綴
<property name="placeholderPrefix" value="https://www.cnblogs.com/dabaieyangzhijidi/p/${"/>
<property name="placeholderSuffix" value="https://www.cnblogs.com/dabaieyangzhijidi/p/}"/>
</bean>
1.8.3,自定義實體化邏輯FactoryBean
您可以org.springframework.beans.factory.FactoryBean為本身就是工廠的物件實作介面,
該FactoryBean介面是可插入Spring IoC容器的實體化邏輯的一點,
如果您有復雜的初始化代碼,而不是(可能)冗長的XML,可以用Java更好地表達,則以創建自己的代碼 FactoryBean,
在該類中撰寫復雜的初始化,然后將自定義FactoryBean插入容器,
該FactoryBean界面提供了三種方法:
- Object getObject():回傳此工廠創建的物件的實體,實體可以共享,具體取決于該工廠是否回傳單例或原型,
- boolean isSingleton():true如果FactoryBean回傳單例或false其他則回傳 ,
- Class getObjectType():回傳getObject()方法回傳的物件型別,或者null如果型別未知,則回傳該物件型別,
FactoryBeanSpring框架中的許多地方都使用了該概念和介面,Spring附帶了50多種FactoryBean介面實作,Spring中的了解的少,但是Mybatis的MybatisSqlSessionFactoryBean很出名,
當您需要向容器詢問FactoryBean本身而不是由它產生的bean的實際實體時,請在呼叫的方法時在該bean的id前面加上“&”符號(&),
因此,對于給定id為myBean的一個FactoryBean ,呼叫getBean("myBean")回傳的是FactoryBean生成的實體,getBean("&myBean")回傳的是FactoryBean本身,
public class MyFactoryBean implements FactoryBean<MyBean> {
@Override
public MyBean getObject() throws Exception {
return new MyBean();
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
}
<bean id="myFactoryBean" />
getBean("myFactoryBean") 回傳的是MyBean實體
getBean("&myFactoryBean") 回傳的是MyFactoryBean實體
本文由博客一文多發平臺 OpenWrite 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/139678.html
標籤:Java
