一塊兒來讀官方文檔-----SpringIOC(07)

1.8。容器擴展點

一般,應用程序開發人員不須要對ApplicationContext 實現類進行子類化。相反,能夠經過插入特殊集成接口的實現來擴展Spring IoC容器。接下來的幾節描述了這些集成接口。spring

1.8.1。自定義bean實現BeanBeanPostProcessor接口

BeanPostProcessor接口定義了回調方法,您能夠實現這些回調方法來修改默認的bean實例化的邏輯,依賴關係解析邏輯等。
若是您想在Spring容器完成實例化,配置和初始化bean以後實現一些自定義邏輯,則能夠插入一個或多個自定義BeanPostProcessor。sql

您能夠配置多個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,以提供代理包裝邏輯。apache

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實例。app

示例: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:ide

<?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>

    <!--
   當上述bean (messenger)被實例化時,這個自定義的BeanPostProcessor實現將事實輸出到系統控制檯   -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

請注意實例化tracingbeanpostprocessor是如何定義的。它甚至沒有名稱,並且,由於它是一個bean,因此能夠像其餘bean同樣進行依賴注入。post

下面的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 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應用於替換數據源的某些屬性的元數據。將要替換的值指定爲形式的佔位符,該形式${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 class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
		<property name="locations" value="classpath:jdbc.properties"/>
		//自定義前綴後綴
		<property name="placeholderPrefix" value="${"/>
		<property name="placeholderSuffix" value="}"/>
	</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" class="org.springframework.example.factoryBean.MyFactoryBean"/>

getBean("myFactoryBean")  返回的是MyBean實例
getBean("&myFactoryBean")  返回的是MyFactoryBean實例
相關文章
相關標籤/搜索