Spring IoC深刻理解

本文相關代碼(來自官方源碼spring-test模塊)請參見spring-demysify org.springframework.mylearntest包下。

三種注入方式

1.構造方法注入java

public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) 10{
	this.newsListener = newsListner;
	this.newPersistener = newsPersister;
}

構造方法注入。 這種注入方式的優勢就是,對象在構造完成以後,即已進入就緒狀態,能夠立刻使用。缺點就是,當依賴對象比較多的時候,構造方法的參數列表會比較長。而經過反射構造對象的時候,對相同類型的參數的處理會比較困難,維護和使用上也比較麻煩。並且在Java中,構造方法沒法被繼承,沒法設置默認值。對於非必須的依賴處理,可能須要引入多個構造方法,而參數數量的變更可能形成維護上的不便。mysql

2.setter 方法注入git

public class FXNewsProvider {
	private IFXNewsListener newsListener;
	private IFXNewsPersister newPersistener;
	
	public IFXNewsListener getNewsListener() {
	return newsListener;
	}
	public void setNewsListener(IFXNewsListener newsListener) {
	this.newsListener = newsListener;
	}
	
	public IFXNewsPersister getNewPersistener() {
	return newPersistener;
	}
	public void setNewPersistener(IFXNewsPersister newPersistener) {
	this.newPersistener = newPersistener;
	}
}

setter方法注入。由於方法能夠命名, 因此setter方法注入在描述性上要比構造方法注入好一些。 另外, setter方法能夠被繼承,容許設置默認值,並且有良好的IDE支持。缺點固然就是對象沒法在構造完成後立刻進入就緒狀態。github

  1. 接口注入
    FXNewsProvider爲了讓IoC Service Provider爲其注入所依賴的IFXNewsListener,首先須要實現IFXNewsListenerCallable接口,這個接口會聲明一個injectNewsListner方法(方法名隨意),該方法的參數,就是所依賴對象的類型。這樣, InjectionServiceContainer對象,即對應的IoCService Provider就能夠經過這個接口方法將依賴對象注入到被注入對象FXNewsProvider當中。

接口注入。從注入方式的使用上來講,接口注入是如今不甚提倡的一種方式,基本處於「退役狀態」。由於它強制被注入對象實現沒必要要的接口,帶有侵入性。而構造方法注入和setter方法注入則不須要如此。spring

IoC Service Provider的職責

業務對象的構建管理:
在IoC場景中,業務對象無需關心所依賴對象如何構建如何得到,但這部分工做始終須要有人來作。因此,IoC Service Provider須要將對象的構建邏輯從客戶端那裏剝離出來,以避免這部分邏輯污染業務對象的實現。
業務對象間的依賴綁定:
對於IoC Service Provider來講,這個職責是最艱鉅也是最重要的,這是它的最終使命之所在。若是不能完成這個職責,那麼,不管業務對象如何的「呼喊」,也不會獲得依賴對象的任何反應(最多見的卻是會收到一個NullPointerException)。IoC Service Provider 經過結合以前構建和管理的全部業務對象,以及各個業務對象間能夠識別依賴關係,將這些對象所依賴的對象注綁定,從而保證每一個業務對象在使用的時候,能夠處於就緒狀態。sql

IoC Service Provider 如何管理對象間的依賴關係

如何記錄對象之間的依賴關係:apache

  • 它能夠經過最基本的文本文件來記錄被注入對象和其依賴對象之間的對應關係;
  • 它也能夠經過描述性較強的XML文件格式來記錄對應信息;
  • 它還能夠經過編寫代碼的方式來註冊這些對應信息;
  • 甚至,若是願意,它也能夠經過語音方式來記錄對象間的依賴注入關係(「嗨,它要一個這種類型的對象,拿這個給它」)
  1. 直接編碼式
    當前大部分的IoC容器都應該支持直接編碼方式,好比PicoContainer、 Spring、 Avalon等。
IoContainer container = ...;
container.register(FXNewsProvider.class,new FXNewsProvider());
container.register(IFXNewsListener.class,new DowJonesNewsListener());
...
container.bind(IFXNewsListenerCallable.class, container.get(IFXNewsListener.class));
...
FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
newProvider.getAndPersistNews();

經過bind方法將「被注入對象」(由IFXNewsListenerCallable接口添加標誌)所依賴的對象,綁定爲容器中註冊過的IFXNewsListener類型的對象實例。容器在返回FXNewsProvider對象實例以前,會根據這個綁定信息,將IFXNewsListener註冊到容器中的對象實例注入到「被注入對象」——FXNewsProvider中,並最終返回已經組裝完畢的FXNewsProvider對象。數組

  1. 配置文件方式
<bean id="newsProvider" class="..FXNewsProvider">
<property name="newsListener">
<ref bean="djNewsListener"/>
</property>
<property name="newPersistener">
<ref bean="djNewsPersister"/>
</property>
</bean>

<bean id="djNewsListener"
class="..impl.DowJonesNewsListener">
</bean>

<bean id="djNewsPersister"
class="..impl.DowJonesNewsPersister">
</bean>

3.元數據方式(使用Guice)安全

public class FXNewsProvider { 
	private IFXNewsListener newsListener;
	private IFXNewsPersister newPersistener;
	
	@Inject
	public FXNewsProvider(IFXNewsListener listener,IFXNewsPersister persister) {
	this.newsListener = listener;
	this.newPersistener = persister;
	} 
	...
}

經過@Inject,咱們指明須要IoC Service Provider經過構造方法注入方式,爲FXNewsProvider注入其所依賴的對象。至於餘下的依賴相關信息,在Guice中是由相應的Module來提供的,代碼清單3-7給出了FXNewsProvider所使用的Module實現。服務器

public class NewsBindingModule extends AbstractModule {
	@Override
	protected void configure() {
	bind(IFXNewsListener.class).to(DowJonesNewsListener.class).in(Scopes.SINGLETON);
	bind(IFXNewsPersister.class).to(DowJonesNewsPersister.class).in(Scopes.SINGLETON);
	} 
}

經過Module指定進一步的依賴注入相關信息以後,咱們就能夠直接從Guice那裏取得最終已經注入完畢,並直接可用的對象了。

Injector injector = Guice.createInjector(new NewsBindingModule());
FXNewsProvider newsProvider = injector.getInstance(FXNewsProvider.class);
newsProvider.getAndPersistNews();

Spring IoC容器 和 IoC Service Provider之間的關係

Spring的IoC容器是一個IoC Service Provider,可是,這只是它被冠以IoC之名的部分緣由,咱們不能忽略的是「容器」。 Spring的IoC容器是一個提供IoC支持的輕量級容器,除了基本的IoC支持,它做爲輕量級容器還提供了IoC以外的支持。如在Spring的IoC容器之上, Spring還提供了相應的AOP框架支持、企業級服務集成等服務。


IoC容器和Provider的 關係

Spring提供了BeanFactory 和 ApplicationContext

BeanFactory

  • 基礎類型IoC容器,提供完整的IoC服務支持。若是沒有特殊指定,默認採用延遲初始化策略( lazy-load)。只有當客戶端對象須要訪問容器中的某個受管對象的時候,纔對該受管對象進行初始化以及依賴注入操做。因此,相對來講,容器啓動初期速度較快,所須要的資源有限。對於資源有限,而且功能要求不是很嚴格的場景, BeanFactory是比較合適的IoC容器選擇。

ApplicationContext

  • ApplicationContext在BeanFactory的基礎上構建,是相對比較高級的容器實現,除了擁有BeanFactory的全部支持, ApplicationContext還提供了其餘高級特性,好比事件發佈、國際化信息支持等,這些會在後面詳述。 ApplicationContext所管理的對象,在該類型容器啓動以後,默認所有初始化並綁定完成。因此,相對於BeanFactory來講, ApplicationContext要求更多的系統資源,同時,由於在啓動時就完成全部初始化,容器啓動時間較之BeanFactory也會長一些。在那些系統資源充足,而且要求更多功能的場景中,ApplicationContext類型的容器是比較合適的選擇。
    關係圖

做爲Spring提供的基本的IoC容器,BeanFactory能夠完成做爲IoC Service Provider的全部職責,包括業務對象的註冊和對象間依賴關係的綁定。

public interface BeanFactory {

	String FACTORY_BEAN_PREFIX = "&";

	Object getBean(String name) throws BeansException;

	<T> T getBean(String name, Class<T> requiredType) throws BeansException;

	Object getBean(String name, Object... args) throws BeansException;

	<T> T getBean(Class<T> requiredType) throws BeansException;

	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

	<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

	<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

	boolean containsBean(String name);

	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
	
	@Nullable
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;

	@Nullable
	Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;

	String[] getAliases(String name);

}

BeanFactory的對象註冊與依賴綁定方式

// 1-設計FXNewsProvider類用於廣泛的新聞處理
public class FXNewsProvider
{
...
}
// 2-設計IFXNewsListener接口抽象各個新聞社不一樣的新聞獲取方式,並給出相應實現類
public interface IFXNewsListener
{
...
}
// 以及
public class DowJonesNewsListener implements IFXNewsListener {
...
}
// 3-設計IFXNewsPersister接口抽象不一樣數據訪問方式,並實現相應的實現類 2
public interface IFXNewsPersister {
...
} 
// 以及
public class DowJonesNewsPersister implements IFXNewsPersister { 
...
}
  1. 直接編碼方式
    BeanFactoryFX
package org.springframework.mylearntest.beanf;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.mylearntest.before.FXNewsProvider;

/**
 * 經過編碼方式使用BeanFactory實現FX新聞相關類的註冊及綁定
 * 本內容來自書籍Spring揭密
 * 代碼搬運於此
 */
public class BeanFactoryFX {
	public static void main(String[] args) {
		DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
		BeanFactory container = bindViaCode(beanRegistry);
		FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
		newsProvider.getAndPersistNews();
	}

	// 由於傳入的DefaultListableBeanFactory同
	// 時實現了BeanFactory和BeanDefinitionRegistry接口,因此,這樣作強制類型轉換不會出
	// 現問題。但須要注意的是,單純的BeanDefinitionRegistry是沒法強制轉換到BeanFactory
	// 類型的!
	public static BeanFactory bindViaCode(BeanDefinitionRegistry registry) {
		AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class, 0, true);
		AbstractBeanDefinition newsListener = new RootBeanDefinition(DowJonesNewsListener.class,0, true);
		AbstractBeanDefinition newsPersister = new RootBeanDefinition(DowJonesNewsPersister.class, 0,true);
		// 將bean定義註冊到容器中
		registry.registerBeanDefinition("djNewsProvider", newsProvider);
		registry.registerBeanDefinition("djListener", newsListener);
		registry.registerBeanDefinition("djPersister", newsPersister);
		// 指定依賴關係
		// 1. 能夠經過構造方法注入方式
		/*ConstructorArgumentValues argValues = new ConstructorArgumentValues();
		argValues.addIndexedArgumentValue(0, newsListener);
		argValues.addIndexedArgumentValue(1, newsPersister);
		newsProvider.setConstructorArgumentValues(argValues);*/
		// 2. 或者經過setter方法注入方式
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.addPropertyValue(new PropertyValue("newsListener",newsListener));
		propertyValues.addPropertyValue(new PropertyValue("newPersistener",newsPersister));
		newsProvider.setPropertyValues(propertyValues);
		// 綁定完成 2
		return (BeanFactory)registry;
	}
}

DowJonesNewsListener

package org.springframework.mylearntest.beanf;

import org.springframework.mylearntest.before.FXNewsBean;
import org.springframework.mylearntest.before.IFXNewsListener;

public class DowJonesNewsListener implements IFXNewsListener {
	@Override
	public String[] getAvailableNewsIds() {
		return new String[0];
	}

	@Override
	public FXNewsBean getNewsByPK(String newsId) {
		return null;
	}

	@Override
	public void postProcessIfNecessary(String newsId) {

	}
}

DowJonesNewsPersister

package org.springframework.mylearntest.beanf;

import org.springframework.mylearntest.before.FXNewsBean;
import org.springframework.mylearntest.before.IFXNewsPersister;

public class DowJonesNewsPersister implements IFXNewsPersister {
	@Override
	public void persistNews(FXNewsBean newsBean) {

	}
}

IFXNewsListener

package org.springframework.mylearntest.before;

public interface IFXNewsListener {
	String[] getAvailableNewsIds();

	FXNewsBean getNewsByPK(String newsId);

	void postProcessIfNecessary(String newsId);
}

IFXNewsPersister

package org.springframework.mylearntest.before;

public interface IFXNewsPersister {
	void persistNews(FXNewsBean newsBean);
}

FXNewsProvider

package org.springframework.mylearntest.before;

import org.apache.commons.lang3.ArrayUtils;


public class FXNewsProvider {
	private IFXNewsListener newsListener;
	private IFXNewsPersister newPersistener;
	public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) {
		this.newsListener = newsListner;
		this.newPersistener = newsPersister;
	}

	public IFXNewsListener getNewsListener() {
		return newsListener;
	}

	public void setNewsListener(IFXNewsListener newsListener) {
		this.newsListener = newsListener;
	}

	public IFXNewsPersister getNewPersistener() {
		return newPersistener;
	}

	public void setNewPersistener(IFXNewsPersister newPersistener) {
		this.newPersistener = newPersistener;
	}

	public FXNewsProvider() {
	}

	public void getAndPersistNews() {
		String[] newsIds = newsListener.getAvailableNewsIds();
		if (ArrayUtils.isEmpty(newsIds)) {
			return;
		}
		for (String newsId : newsIds) {
			FXNewsBean newsBean = newsListener.getNewsByPK(newsId);
			newPersistener.persistNews(newsBean);
			newsListener.postProcessIfNecessary(newsId);
		}
	}
}

FXNewsBean

package org.springframework.mylearntest.before;

public class FXNewsBean {
}
  1. 外部配置文件方式
    一般狀況下,須要根據不一樣的外部配置文件格式,給出相應的BeanDefinitionReader實現類,由BeanDefinitionReader的相應實現類負責將相應的配置文件內容讀取並映射到BeanDefinition,而後將映射後的BeanDefinition註冊到一個BeanDefinitionRegistry,以後, BeanDefinitionRegistry即完成Bean的註冊和加載。
    大部分工做,包括解析文件格式、裝配BeanDefinition之類的工做,都是由BeanDefinitionReader的相應實現類來作的, BeanDefinitionRegistry只不過負責保管而已。
    1. properties
djNewsProvider.(class)=..FXNewsProvider
# ----------經過構造方法注入的時候-------------
djNewsProvider.$0(ref)=djListener
djNewsProvider.$1(ref)=djPersister
# ----------經過setter方法注入的時候---------
# djNewsProvider.newsListener(ref)=djListener
# djNewsProvider.newPersistener(ref)=djPersister
djListener.(class)=..impl.DowJonesNewsListener
djPersister.(class)=..impl.DowJonesNewsPersister
package org.springframework.mylearntest.directcode;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;

public class PropConfigTest {
	public static void main(String[] args) {
		// todo Caused by: java.lang.IllegalStateException: No bean class specified on bean definition
		DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
		BeanFactory container = bindViaPropertiesFile(beanRegistry);
		FXNewsProvider newsProvider =
				(FXNewsProvider)container.getBean("djNewsProvider");
		newsProvider.getAndPersistNews();
	}

	public static BeanFactory bindViaPropertiesFile(BeanDefinitionRegistry registry) {
		PropertiesBeanDefinitionReader reader =
				new PropertiesBeanDefinitionReader(registry);
		reader.loadBeanDefinitions("classpath:binding-config.properties");
		return (BeanFactory)registry;
	}
}
  1. xml

local parent 和 bean的區別

local、 parent和bean的區別在於:

  • local只能指定與當前配置的對象在同一個配置文件的對象定義的名稱(能夠得到XML解析器的id約束驗證支持);

  • parent則只能指定位於當前容器的父容器中定義的對象引用;

  • bean則基本上通吃,因此,一般狀況下,直接使用bean來指定對象引用就能夠了

  • 但這種場合下,使用idref纔是最爲合適的。由於使用idref,容器在解析配置的時候就能夠幫 你檢查這個beanName究竟是否存在,而不用等到運行時才發現這個beanName對應的對象實例不存在。畢竟,輸錯名字的問題很常見。如下代碼演示了idref的使用:

<property name="newsListenerBeanName">
<idref bean="djNewsListener"/>
</property>

byType byName No Autowired Autodetect

autodetect

  • 這種模式是byType和constructor模式的結合體,若是對象擁有默認無參數的構造方法,容器會優先考慮byType的自動綁定模式。不然,會使用constructor模式。固然,若是經過構造方法注入綁定後還有其餘屬性沒有綁定,容器也會使用byType對剩餘的對象屬性進行自動綁定。

自動綁定和手動綁定的區別

自動綁定和手動明確綁定各有利弊。自動綁定的優勢有以下兩點。

  • (1) 某種程度上能夠有效減小手動敲入配置信息的工做量。
  • (2) 某些狀況下,即便爲當前對象增長了新的依賴關係,但只要容器中存在相應的依賴對象,就不須要更改任何配置信息。
    自動綁定的缺點有以下幾點。
  • (1) 自動綁定不如明確依賴關係一目瞭然。咱們能夠根據明確的依賴關係對整個系統有一個明確的認識,但使用自動綁定的話,就可能須要在類定義以及配置文件之間,甚至各個配置文件之間來回轉換以取得相應的信息。
  • (2) 某些狀況下,自動綁定沒法知足系統須要,甚至致使系統行爲異常或者不可預知。根據類型( byType)匹配進行的自動綁定,若是系統中增長了另外一個相同類型的bean定義,那麼整個系統就會崩潰;根據名字( byName)匹配進行的自動綁定,若是把原來系統中相同名稱的bean定義類型給換掉,就會形成問題,而這些可能都是在不經意間發生的。
  • (3) 使用自動綁定,咱們可能沒法得到某些工具的良好支持,好比Spring IDE。與BeanFactory不一樣, ApplicationContext在容器啓動的時候,就會立刻對全部的「 singleton的bean定義」 進行實例化操做

懶加載配置了是否必定會生效?

僅指定lazy-init-bean的lazy-init爲true,並不意味着容器就必定會延遲初始化該bean的實例。若是某個非延遲初始化的bean定義依賴於lazy-init-bean,那麼毫無疑問,按照依賴決計的順序,容器仍是會首先實例化lazy-init-bean,而後再實例化後者,以下代碼演示了這種相互牽連致使延遲初始化失敗的狀況:

<bean id="lazy-init-bean" class="..." lazy-init="true"/> 
<bean id="not-lazy-init-bean" class="...">
<property name="propName">
<ref bean="lazy-init-bean"/> 
</property>
</bean>

如何簡化xml配置中property屬性的編寫

代碼清單4-27 使用模板化配置形式配置FXNewsProvider和SpecificFXNewsProvider

<bean id="newsProviderTemplate" abstract="true">
<property name="newPersistener">
<ref bean="djNewsPersister"/> 
</property>
</bean>
<bean id="superNewsProvider" parent="newsProviderTemplate" 
class="..FXNewsProvider">
<property name="newsListener">
</property> <ref bean="djNewsListener"/> 7
</bean>
<bean id="subNewsProvider" parent="newsProviderTemplate" 
class="..SpecificFXNewsProvider">
<property name="newsListener">
<ref bean="specificNewsListener"/>
</property> 
</bean>

abstract屬性的使用

若是你不想容器在初始化的時候實例化某些對象,那麼能夠將其abstract屬性賦值true,以免容器將其實例化。對於ApplicationContext容器尤爲如此,由於默認狀況下, ApplicationContext會在容器啓動的時候就對其管理的全部bean進行實例化,只有標誌爲abstract的bean除外。

scope 定義以及 scope 的幾種類型

scope用來聲明容器中的對象所應該處的限定場景或者說該對象的存活時間,即容器在對象進入其相應的scope以前,生成並裝配這些對象,在該對象再也不處於這些scope的限定以後,容器一般會銷燬這些對象。

Spring容器最初提供了兩種bean的scope類型: singleton和prototype,但發佈2.0以後,又引入了另外三種scope類型,即request、 session和global session類型。不過這三種類型有所限制,只能在Web應用中使用。也就是說,只有在支持Web應用的ApplicationContext中使用這三個scope纔是合理的。

global session只有應用在基於portlet的Web應用程序中才有意義,它映射到portlet的global範圍的 session。若是在普通的基於servlet的Web應用中使用了這個類scope,容器會將其做爲普通的session類型的scope對待。

方法注入

Spring容器提出了一種叫作方法注入( Method Injection)的方式,能夠幫助咱們解決上述問題。咱們所要作的很簡單,只要讓getNewsBean方法聲明符合規定的格式,並在配置文件中通知容器,當該方法被調用的時候,每次返回指定類型的對象實例便可。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean id="newsBean" class="org.springframework.mylearntest.directcode.FXNewsBean" scope="prototype">
	</bean>
	<bean id="mockPersister" class="org.springframework.mylearntest.mthdinject.MockNewsPersister">
		<property name="newsBean">
			<ref bean="newsBean"/>
		</property>
	</bean>
</beans>
package org.springframework.mylearntest.mthdinject;

import org.springframework.mylearntest.directcode.FXNewsBean;
import org.springframework.mylearntest.directcode.IFXNewsPersister;

public class MockNewsPersister implements IFXNewsPersister {
	private FXNewsBean newsBean;
	public void persistNews(FXNewsBean bean) {
		persistNews();
	}
	public void persistNews() {
		System.out.println("persist bean:"+getNewsBean());
	}
	public FXNewsBean getNewsBean() {
		return newsBean;
	}

	public void setNewsBean(FXNewsBean newsBean) {
		this.newsBean = newsBean;
	}
}
package org.springframework.mylearntest.mthdinject;


import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test4MockNewsPersister {
	public static void main(String[] args) {
		BeanFactory container = new ClassPathXmlApplicationContext("mthdinject.xml");
		MockNewsPersister persister = (MockNewsPersister)container.getBean("mockPersister");
		persister.persistNews();
		persister.persistNews();
	}
}

輸出結果爲

persist bean:org.springframework.mylearntest.directcode.FXNewsBean@5be6e01c
persist bean:org.springframework.mylearntest.directcode.FXNewsBean@5be6e01c

使用方法注入後

<bean id="newsBean" class="..domain.FXNewsBean" singleton="prototype">
</bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
<lookup-method name="getNewsBean" bean="newsBean"/>
</bean>

經過 的name屬性指定須要注入的方法名, bean屬性指定須要注入的對象,當getNewsBean方法被調用的時候,容器能夠每次返回一個新的FXNewsBean類型的實例。

經過實現BeanFactoryAware

即便沒有方法注入, 只要在實現getNewsBean()方法的時候,可以保證每次調用BeanFactory的getBean("newsBean"),就一樣能夠每次都取得新的FXNewsBean對象實例

package org.springframework.mylearntest.beanfactorywareinject;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.mylearntest.directcode.FXNewsBean;
import org.springframework.mylearntest.directcode.IFXNewsPersister;

public class MockNewsPersister1 implements IFXNewsPersister, BeanFactoryAware {
	private BeanFactory beanFactory;

	public void setBeanFactory(BeanFactory bf) throws BeansException {
		this.beanFactory = bf;
	}

	public void persistNews(FXNewsBean bean) {
		persistNews();
	}

	public void persistNews() {
		System.out.println("persist bean:" + getNewsBean());
	}

	public FXNewsBean getNewsBean() {
		return (FXNewsBean) beanFactory.getBean("newsBean");
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="newsBean" class="org.springframework.mylearntest.directcode.FXNewsBean" scope="prototype">
	</bean>
	<bean id="mockPersister1" class="org.springframework.mylearntest.beanfactorywareinject.MockNewsPersister1">
	</bean>

</beans>
package org.springframework.mylearntest.beanfactorywareinject;


import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mylearntest.mthdinject.MockNewsPersister;

public class Test4MockNewsPersister1 {
	public static void main(String[] args) {
		BeanFactory container = new ClassPathXmlApplicationContext("beanfactoryawareinject.xml");
		MockNewsPersister1 persister = (MockNewsPersister1)container.getBean("mockPersister1");
		persister.persistNews();
		persister.persistNews();
	}
}

經過ObjectFactory

ObjectFactoryCreatingFactoryBean是 Spring 提 供 的 一 個 FactoryBean實 現 ,它 返 回 一 個ObjectFactory實例。ObjectFactoryCreatingFactoryBean返回的這個ObjectFactory實例能夠爲咱們返回容器管理的相關對象。實際上,ObjectFactoryCreatingFactoryBean實現BeanFactoryAware接口,它返回ObjectFactory實例只是特定於與Spring容器進行交互的一個實現而已。使用它的好處就是,隔離了客戶端對象對BeanFactory的直接引用。

package org.springframework.mylearntest.objectfactoryinj;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.mylearntest.directcode.FXNewsBean;
import org.springframework.mylearntest.directcode.IFXNewsPersister;

@SuppressWarnings({"rawtypes" })
public class MockNewsPersister2 implements IFXNewsPersister {
	private ObjectFactory newsBeanFactory;
	public void persistNews(FXNewsBean bean) {
		persistNews();
	}
	public void persistNews() {
		System.out.println("persist bean:"+getNewsBean());
	}
	public FXNewsBean getNewsBean() {
		return (FXNewsBean) newsBeanFactory.getObject();
	}
	public void setNewsBeanFactory(ObjectFactory newsBeanFactory) {
		this.newsBeanFactory = newsBeanFactory;
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="newsBean" class="org.springframework.mylearntest.directcode.FXNewsBean" scope="prototype">
	</bean>
	<bean id="newsBeanFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
		<property name="targetBeanName">
			<idref bean="newsBean"/>
		</property>
	</bean>

	<bean id="mockPersister2" class="org.springframework.mylearntest.objectfactoryinj.MockNewsPersister2">
		<property name="newsBeanFactory">
			<ref bean="newsBeanFactory"/>
		</property>
	</bean>
</beans>
package org.springframework.mylearntest.objectfactoryinj;


import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test4MockNewsPersister2 {
	public static void main(String[] args) {
		BeanFactory container = new ClassPathXmlApplicationContext("objectfactoryinj.xml");
		MockNewsPersister2 persister = (MockNewsPersister2)container.getBean("mockPersister2");
		persister.persistNews();
		persister.persistNews();
	}
}

方法替換

使用FXNewsProviderMethodReplacer替換FXNewsProvider中的getAndPersistNews()方法

package org.springframework.mylearntest.methodreplacer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.support.MethodReplacer;

import java.lang.reflect.Method;

public class FXNewsProviderMethodReplacer implements MethodReplacer {

	private static final transient Log logger =
			LogFactory.getLog(FXNewsProviderMethodReplacer.class);

	public Object reimplement(Object target, Method method, Object[] args)
			throws Throwable {
		logger.info("before executing method["+method.getName()+
				"] on Object["+target.getClass().getName()+"].");
		System.out.println("sorry,We will do nothing this time.");
		logger.info("end of executing method["+method.getName()+
				"] on Object["+target.getClass().getName()+"].");
		return null;
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="FXNewsProvider" class="org.springframework.mylearntest.propconfig.FXNewsProvider">
		<constructor-arg index="0">
			<ref bean="djNewsListener"/>
		</constructor-arg>
		<constructor-arg index="1">
			<ref bean="djNewsPersister"/>
		</constructor-arg>
		<replaced-method name="getAndPersistNews" replacer="providerReplacer">
		</replaced-method>
	</bean>

	<bean id="djNewsListener" class="org.springframework.mylearntest.propconfig.DjNewsListener"/>
	<bean id="djNewsPersister" class="org.springframework.mylearntest.propconfig.DjNewsPersister"/>
	<bean id="providerReplacer" class="org.springframework.mylearntest.methodreplacer.FXNewsProviderMethodReplacer">
	</bean>
</beans>
package org.springframework.mylearntest.methodreplacer;


import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mylearntest.propconfig.FXNewsProvider;

public class Test4FXNewsProviderMethodReplacer {
	public static void main(String[] args) {
		BeanFactory container = new ClassPathXmlApplicationContext("methodreplacer.xml");
		FXNewsProvider fxNewsProvider = (FXNewsProvider)container.getBean("FXNewsProvider");
		fxNewsProvider.getAndPersistNews();
	}
}

IoC容器背後的祕密

  1. 容器啓動階段
    容器啓動開始,首先會經過某種途徑加載Configuration MetaData。除了代碼方式比較直接,在大部分狀況下,容器須要依賴某些工具類(BeanDefinitionReader)對加載的Configuration MetaData進行解析和分析,並將分析後的信息編組爲相應的BeanDefinition,最後把這些保存了bean定義必要信息的BeanDefinition,註冊到相應的BeanDefinitionRegistry,這樣容器啓動工做就完成了。

  2. Bean實例化階段
    通過第一階段,如今全部的bean定義信息都經過BeanDefinition的方式註冊到了BeanDefinitionRegistry中。當某個請求方經過容器的getBean方法明確地請求某個對象時,或者因依賴關係容器須要隱式地調用getBean方法時,就會觸發第二階段的活動。

該階段,容器會首先檢查所請求的對象以前是否已經初始化。若是沒有,則會根據註冊的BeanDefinition所提供的信息實例化被請求對象,併爲其注入依賴。若是該對象實現了某些回調接口,也會根據回調接口的要求來裝配它。當該對象裝配完畢以後,容器會當即將其返回請求方使用。若是說第一階段只是根據圖紙裝配生產線的話,那麼第二階段就是使用裝配好的生產線來生產具體的產品了。

BeanFactoryPostProcessor

Spring提供了一種叫作BeanFactoryPostProcessor的容器擴展機制。該機制容許咱們在容器實例化相應對象以前,對註冊到容器的BeanDefinition所保存的信息作相應的修改。這就至關於在容器實現的第一階段最後加入一道工序,讓咱們對最終BeanDefinition作一些額外的操做,好比修改其中bean定義的某些屬性,爲bean定義增長其餘信息等。

若是要自定義實現BeanFactoryPostProcessor,一般咱們須要實現org.springframework.beans.factory.config.BeanFactoryPostProcessor接口。這個時候可能須要實現類同時實現Spring的org.springframework.core.Ordered接口,以保證各個BeanFactoryPostProcessor能夠按照預先設定的順序執行(若是順序緊要的話)。

其中,org.springframework.beans.factory.config.PropertyPlaceholderConfigurer和org.springframework.beans.factory.config.Property OverrideConfigurer是兩個比較經常使用的BeanFactoryPostProcessor。

  1. PropertyPlaceholderConfigurer
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!--	使用的BeanFactoryPostProcessor-->
	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>conf/jdbc.properties</value>
				<value>conf/mail.properties</value>
			</list>
		</property>
	</bean>

	<!--	使用佔位符的數據源配置-->
	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
		<property name="url">
			<value>${jdbc.url}</value>
		</property>
		<property name="driverClassName">
			<value>${jdbc.driver}</value>
		</property>
		<property name="username">
			<value>${jdbc.username}</value>
		</property>
		<property name="password">
			<value>${jdbc.password}</value>
		</property>
		<property name="testOnBorrow">
			<value>true</value>
		</property>
		<property name="testOnReturn">
			<value>true</value>
		</property>
		<property name="testWhileIdle">
			<value>true</value>
		</property>
		<property name="minEvictableIdleTimeMillis">
			<value>180000</value>
		</property>
		<property name="timeBetweenEvictionRunsMillis">
			<value>360000</value>
		</property>
		<property name="validationQuery">
			<value>SELECT 1</value>
		</property>
		<property name="maxOpenPreparedStatements">
			<value>100</value>
		</property>
	</bean>
</beans>
jdbc.url=jdbc:mysql://server/MAIN?useUnicode=true&characterEncoding=ms932&failOverReadOnly=false&roundRobinLoadBalance=true
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=root

若是org.apache.commons.dbcp2.BasicDataSource報錯,請加入依賴
compile(group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.1.1')

基本機制就是以前所說的那樣。當BeanFactory在第一階段加載完成全部配置信息時, BeanFactory中保存的對象的屬性信息還只是以佔位符的形式存在,如${jdbc.url}、 ${jdbc.driver}。當PropertyPlaceholderConfigurer做爲BeanFactoryPostProcessor被應用時,它會使用properties配置文件中的配置信息來替換相應BeanDefinition中佔位符所表示的屬性值。這樣,當進入容器實現的第二階段實例化bean時, bean定義中的屬性值就是最終替換完成的了。

PropertyPlaceholderConfigurer不單會從其配置的properties文件中加載配置項,同時還會檢查Java的System類中的Properties,能夠經過setSystemPropertiesMode()或者setSystemPropertiesModeName()來控制是否加載或者覆蓋System相應Properties的行爲。
PropertyPlaceholderConfigurer提供了SYSTEM_PROPERTIES_MODE_FALLBACK、 SYSTEM_PROPERTIES_MODE_NEVER和SYSTEM_PROPERTIES_MODE_OVERRIDE三種模式。默認採用的是SYSTEM_PROPERTIES_ MODE_FALLBACK,果properties文件中找不到相應配置項,則到System的Properties中查找,咱們還能夠選擇不檢查System的Properties或者覆蓋它。

  1. PropertyOverrideConfigurer
    配置在properties文件中的信息一般都以明文表示,PropertyOverrideConfigurer的父類PropertyResourceConfigurer提供了一個protected類型的方法convertPropertyValue,容許子類覆蓋這個方法對相應的置項進行轉換,如對加密後的字符串解密以後再覆蓋到相應的bean定義中。固然,既然PropertyPlaceholderConfigurer也一樣繼承了PropertyResourceConfigurer,咱們也能夠針對PropertyPlaceholderConfigurer應用相似的功能。
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="pool-adjustment.properties"/>
</bean>
dataSource.minEvictableIdleTimeMillis=1000
dataSource.maxOpenPreparedStatements=50
  1. CustomEditorConfigurer
    CustomEditorConfigurer是另外一種類型的BeanFactoryPostProcessor實現,它只是輔助性地將後期會用到的信息註冊到容器,對BeanDefinition沒有作任何變更。

Spring提供的部分PropertyEditor:
StringArrayPropertyEditor。該PropertyEditor會將符合CSV格式的字符串轉換成String[]數組的形式,默認是以逗號(,)分隔的字符串,但能夠指定自定義的字符串分隔符。ByteArrayPropertyEditor、CharArrayPropertyEditor等都屬於相似功能的PropertyEditor,參照Javadoc能夠取得相應的詳細信息。

  • ClassEditor。根據String類型的class名稱,直接將其轉換成相應的Class對象,至關於經過Class.forName(String)完成的功效。能夠經過String[]數組的形式傳入需轉換的值,以達到與提供ClassArrayEditor一樣的目的。

  • FileEditor。 Spring提供的對應java.io.File類型的PropertyEditor。同屬於對資源進行定位的PropertyEditor還有InputStreamEditor、 URLEditor等。

  • LocaleEditor。針對java.util.Locale類型的PropertyEditor,格式能夠參照LocaleEditor和Locale的Javadoc說明。

  • PatternEditor。針對Java SE 1.4以後才引入的java.util.regex.Pattern的PropertyEditor,格式能夠參照java.util.regex.Pattern類的Javadoc。

以上這些PropertyEditor,容器一般會默認加載使用,因此,即便咱們不告訴容器應該如何對這些類型進行轉換,容器一樣能夠正確地完成工做。但當咱們須要指定的類型沒有包含在以上所提到PropertyEditor之列的時候,就須要給出針對這種類型的PropertyEditor實現,並經過CustomEditorConfigurer告知容器,以便容器在適當的時機使用到適當的PropertyEditor。

自定義PropertyEditor
對於Date類型,不一樣的Locale、不一樣的系統在表現形式上存在不一樣的需求。如系統這個部分須要以yyyy-MM-dd的形式表現日期,系統那個部分可能又須要以yyyyMMdd的形式對日期進行轉換。

package org.springframework.mylearntest.beanfactorypostprocessor;

import java.beans.PropertyEditorSupport;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class DatePropertyEditor extends PropertyEditorSupport {
	private String datePattern;

	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(getDatePattern());
		LocalDate dateValue = LocalDate.parse(text,dateTimeFormatter);
		setValue(dateValue);
	}

	@Override
	public String getAsText() {
		return super.getAsText();
	}

	public String getDatePattern() {
		return datePattern;
	}

	public void setDatePattern(String datePattern) {
		this.datePattern = datePattern;
	}
}

若是僅僅是支持單向的從String到相應對象類型的轉換,只要覆寫方法setAsText(String)便可。若是想支持雙向轉換,須要同時考慮getAsText()方法的覆寫。

package org.springframework.mylearntest.beanfactorypostprocessor;

import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;

import java.beans.PropertyEditor;

public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar {
	private PropertyEditor propertyEditor;

	public PropertyEditor getPropertyEditor() {
		return propertyEditor;
	}

	public void setPropertyEditor(PropertyEditor propertyEditor) {
		this.propertyEditor = propertyEditor;
	}

	@Override
	public void registerCustomEditors(PropertyEditorRegistry registry) {
		registry.registerCustomEditor(java.util.Date.class,getPropertyEditor());
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
	   xmlns:aop="http://www.springframework.org/schema/aop">

	<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
		<property name="propertyEditorRegistrars">
			<list>
				<ref bean="datePropertyEditorRegistrar"/>
			</list>
		</property>
	</bean>

	<bean id="datePropertyEditorRegistrar" class="org.springframework.mylearntest.beanfactorypostprocessor.DatePropertyEditorRegistrar">
		<property name="propertyEditor">
			<ref bean="datePropertyEditor"/>
		</property>
	</bean>

	<bean id="datePropertyEditor" class="org.springframework.mylearntest.beanfactorypostprocessor.DatePropertyEditor">
		<property name="datePattern">
			<value>yyyy/MM/dd</value>
		</property>
	</bean>
</beans>
package org.springframework.mylearntest.beanfactorypostprocessor;

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

public class Test4DateProp {
	public static void main(String[] args) {
		// applicationContext
		ApplicationContext context = new ClassPathXmlApplicationContext("datepropertyeditor2.xml");
		DatePropertyEditor datePropertyEditor = (DatePropertyEditor) context.getBean("datePropertyEditor");
		datePropertyEditor.setAsText("2020/06/21");
	}
}

bean的構造

容器啓動以後,並不會立刻就實例化相應的bean定義。咱們知道,容器如今僅僅擁有全部對象的BeanDefinition來保存實例化階段將要用的必要信息。只有當請求方經過BeanFactory的getBean()方法來請求某個對象實例的時候,纔有可能觸發Bean實例化階段的活動。 BeanFactory的getBe法能夠被客戶端對象顯式調用,也能夠在容器內部隱式地被調用。隱式調用有以下兩種狀況。

  1. 對於BeanFactory來講,對象實例化默認採用延遲初始化。一般狀況下,當對象A被請求而須要第一次實例化的時候,若是它所依賴的對象B以前一樣沒有被實例化,那麼容器會先實例化對象A所依賴的對象。這時容器內部就會首先實例化對象B,以及對象A依賴的其餘尚未實例化的對象。這種狀況是容器內部調用getBean(),對於本次請求的請求方是隱式的。
  2. ApplicationContext啓動以後會實例化全部的bean定義,這個特性在本書中已經屢次提到。但ApplicationContext在實現的過程當中依然遵循Spring容器實現流程的兩個階段,只不過它會在啓動階段的活動完成以後,緊接着調用註冊到該容器的全部bean定義的實例化方法getBean()。這就是爲何當你獲得ApplicationContext類型的容器引用時,容器內全部對象已經被所有實例化完成。不信你查一下類org.AbstractApplicationContext的refresh()方法。

bean實例化過程

  1. Bean的實例化與BeanWrapper
    第一步:獲取BeanWrapper
    • 容器在內部實現的時候,採用「策略模式(Strategy Pattern)」來決定採用何種方式初始化bean實例。一般,能夠經過反射或者CGLIB動態字節碼生成來初始化相應的bean實例或者動態生成其子類。org.springframework.beans.factory.support.InstantiationStrategy定義是實例化策略的抽象接口,其直接子類SimpleInstantiationStrategy實現了簡單的對象實例化功能,能夠經過反射來實例化對象實例,但不支持方法注入方式的對象實例化。 CglibSubclassingInstantiationStrategy繼承了SimpleInstantiationStrategy的以反射方式實例化對象的功能,而且經過CGLIB的動態字節碼生成功能,該策略實現類能夠動態生成某個類的子類,進而知足了方法注入所需的對象實例化求。默認狀況下,容器內部採用的是CglibSubclassingInstantiationStrategy。

    • 容器只要根據相應bean定義的BeanDefintion取得實例化信息,結合CglibSubclassingInstantiationStrategy以及不一樣的bean定義類型,就能夠返回實例化完成的對象實例。可是,返回方式上有些「點綴」。不是直接返回構造完成的對象實例,而是以BeanWrapper對構造完成的對象實例進行包裹,返回相應的BeanWrapper實例。

第二部:設置Bean的相應屬性

  • BeanWrapper接口一般在Spring框架內部使用,它有一個實現類org.springframework.beans.BeanWrapperImpl。其做用是對某個bean進行「包裹」,而後對這個「包裹」的bean進行操做,好比設置或者獲取bean的相應屬性值。而在第一步結束後返回BeanWrapper實例而不是原先的對象實例,就是爲了第二步「設置對象屬性」。

  • BeanWrapper定義繼承了org.springframework.beans.PropertyAccessor接口,能夠以統一的方式對對象屬性進行訪問; BeanWrapper定義同時又直接或者間接繼承了PropertyEditorRegistry和TypeConverter接口。不知你是否還記得CustomEditorConfigurer?當把各類PropertyEditor註冊給容器時,知道後面誰用到這些PropertyEditor嗎?對,就是BeanWrapper!在第一步構造完成對象以後, Spring會根據對象實例構造一個BeanWrapperImpl實例,而後將以前CustomEditorConfigurer註冊的PropertyEditor複製一份給BeanWrapperImpl例(這就是BeanWrapper同時又是PropertyEditorRegistry的緣由)。這樣,當BeanWrapper轉換類型、設置對象屬性值時,就不會無從下手了。

// 使用BeanWrapper操做對象
Object provider = Class.forName("package.name.FXNewsProvider").newInstance(); 
Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance();
Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance();
BeanWrapper newsProvider = new BeanWrapperImpl(provider); 
newsProvider.setPropertyValue("newsListener", listener);
newsProvider.setPropertyValue("newPersistener", persister);

assertTrue(newsProvider.getWrappedInstance() instanceof FXNewsProvider);
assertSame(provider, newsProvider.getWrappedInstance());
assertSame(listener, newsProvider.getPropertyValue("newsListener"));
assertSame(persister, newsProvider.getPropertyValue("newPersistener"));
// 使用Java反射API操做對象
Object provider = Class.forName("package.name.FXNewsProvider").newInstance();
Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance();
Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance();

Class providerClazz = provider.getClass();
Field listenerField = providerClazz.getField("newsListener");
listenerField.set(provider, listener);
Field persisterField = providerClazz.getField("newsListener");
persisterField.set(provider, persister);
assertSame(listener, listenerField.get(provider));
assertSame(persister, persisterField.get(provider));
  1. 各類Aware接口
    當對象實例化完成而且相關屬性以及依賴設置完成以後, Spring容器會檢查當前對象實例是否實現了一系列的以Aware命名結尾的接口定義。若是是,則將這些Aware接口定義中規定的依賴注入給當前對象實例。這些Aware接口爲以下幾個。
    針對BeanFactory容器而言
    • org.springframework.beans.factory.BeanNameAware。若是Spring容器檢測到當前對象實例實現了該接口,會將該對象實例的bean定義對應的beanName設置到當前對象實例。

    • org.springframework.beans.factory.BeanClassLoaderAware。若是容器檢測到當前對象實例實現了該接口,會將對應加載當前bean的Classloader注入當前對象實例。默認會使用加載org.springframework.util.ClassUtils類的Classloader。

    • org.springframework.beans.factory.BeanFactoryAware。在介紹方法注入的時候,咱們提到過使用該接口以便每次獲取prototype類型bean的不一樣實例。若是對象聲明實現了BeanFactoryAware接口, BeanFactory容器會將自身設置到當前對象實例。這樣,當前對象實例就擁有了一個BeanFactory容器的引用,而且能夠對這個容器內容許訪問的對象按照須要進行訪問。

對於ApplicationContext類型容器,使用BeanPostProcessor處理

  • org.springframework.context.ResourceLoaderAware 。 ApplicationContext 實現了Spring的ResourceLoader接口(後面會說起詳細信息)。當容器檢測到當前對象實例實現了ResourceLoaderAware接口以後,會將當前ApplicationContext自身設置到對象實例,這樣當前對象實例就擁有了其所在ApplicationContext容器的一個引用。

  • org.springframework.context.ApplicationEventPublisherAware。 ApplicationContext做爲一個容器,同時還實現了ApplicationEventPublisher接口,這樣,它就能夠做爲ApplicationEventPublisher來使用。因此,當前ApplicationContext容器若是檢測到當前實例化的對象實例聲明瞭ApplicationEventPublisherAware接口,則會將自身注入當前對象。

  • org.springframework.context.MessageSourceAware。 ApplicationContext經過MessageSource接口提供國際化的信息支持,即I18n( Internationalization)。它自身就實現了MessageSource接口,因此當檢測到當前對象實例實現了MessageSourceAware接口,則會將自身注入當前對象實例。

  • org.springframework.context.ApplicationContextAware。 若是ApplicationContext容器檢測到當前對象實現了ApplicationContextAware接口,則會將自身注入當前對象實例。

BeanPostProcessor

只要記住BeanPostProcessor是存在於對象實例化階段,而BeanFactoryPostProcessor則是存在於容器啓動階段。

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;

public interface BeanPostProcessor {

	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

自定義BeanPostProcessor須要實現BeanPostProcessor

假設系統中全部的IFXNewsListener實現類須要從某個位置取得相應的服務器鏈接密碼,並且系統中保存的密碼是加密的,那麼在IFXNewsListener發送這個密碼給新聞服務器進行鏈接驗證的時候,首先須要對系統中取得的密碼進行解密,而後才能發送。

  • (1) 標註須要進行解密的實現類
    爲了可以識別那些須要對服務器鏈接密碼進行解密的IFXNewsListener實現,咱們聲明瞭接口PasswordDecodable,並要求相關IFXNewsListener實現類實現該接口。
package org.springframework.mylearntest.beanpostprocessor;


public interface PasswordDecodable {
	String getEncodedPassword();
	void setDecodedPassword(String password);
}
package org.springframework.mylearntest.beanpostprocessor;

import org.springframework.mylearntest.directcode.FXNewsBean;
import org.springframework.mylearntest.directcode.IFXNewsListener;

public class DowJonesNewsListener implements IFXNewsListener,PasswordDecodable {
	private String password;
	public String[] getAvailableNewsIds() {
		// 省略
		return new String[0];
	}
	public FXNewsBean getNewsByPK(String newsId) {
		// 省略
		return null;
	}
	public void postProcessIfNecessary(String newsId) {
		// 省略
	}
	public String getEncodedPassword() {
		return this.password;
	}
	public void setDecodedPassword(String password) {
		this.password = password;
	}
}
  • (2) 實現相應的BeanPostProcessor對符合條件的Bean實例進行處理
    咱們經過PasswordDecodable接口聲明來區分將要處理的對象實例,當檢查到當前對象實例實現了該接口以後,就會從當前對象實例取得加密後的密碼,並對其解密。而後將解密後的密碼設置回當前對象實例。
package org.springframework.mylearntest.beanpostprocessor;

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

public class PasswordDecodePostProcessor implements BeanPostProcessor {
	public Object postProcessAfterInitialization(Object object, String beanName)
			throws BeansException {
		return object;
	}
	public Object postProcessBeforeInitialization(Object object, String beanName)
			throws BeansException {
		if(object instanceof PasswordDecodable){
			String encodedPassword = ((PasswordDecodable)object).getEncodedPassword();
			String decodedPassword = decodePassword(encodedPassword);
			((PasswordDecodable)object).setDecodedPassword(decodedPassword);
		}
		return object;
	}
	private String decodePassword(String encodedPassword) {
		// 實現解碼邏輯
		encodedPassword = encodedPassword + "2mingwen";
		return encodedPassword;
	}
}
  • (3) 將自定義的BeanPostProcessor註冊到容器
    將PasswordDecodePostProcessor注入到容器中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
	   xmlns:aop="http://www.springframework.org/schema/aop">
	<bean id="dowJonesNewsListener" class="org.springframework.mylearntest.beanpostprocessor.DowJonesNewsListener">
		<property name="decodedPassword" value="123sjfg@LL"></property>
	</bean>

	<bean id="passwordDecodePostProcessor" class="org.springframework.mylearntest.beanpostprocessor.PasswordDecodePostProcessor">
	</bean>
</beans>
// 測試類
package org.springframework.mylearntest.beanpostprocessor;

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

public class Test4BeanPostProcessor {
	public static void main(String[] args) {
		ApplicationContext beanFactory = new ClassPathXmlApplicationContext("beanpostprocessor/beanpostprocessor.xml");
		DowJonesNewsListener dowJonesNewsListener = (DowJonesNewsListener) beanFactory.getBean("dowJonesNewsListener");
		String encodedPassword = dowJonesNewsListener.getEncodedPassword();
		System.out.println("encodedPassword = " + encodedPassword);// encodedPassword = 123sjfg@LL2mingwen
	}
}

實際上,有一種特殊類型的BeanPostProcessor咱們沒有提到,它的執行時機與一般的BeanPostProcessor不一樣。org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor接口能夠在對象的實例化過程當中致使某種相似於電路「短路」的效果。實際上,並不是全部註冊到Spring容器內的bean定義都是按照圖4-10的流程實例化的。在全部的步驟以前,也就是實例化bean對象步驟以前,容器會首先檢查容器中是否註冊有InstantiationAwareBeanPostProcessor類型的BeanPostProcessor。若是有,首先使用相應的InstantiationAwareBeanPostProcessor來構造對象實例。構形成功後直接返回造完成的對象實例,而不會按照「正規的流程」繼續執行。這就是它可能形成「短路」的緣由。

InitializingBean 和 init-method

org.springframework.beans.factory.InitializingBean是容器內部普遍使用的一個對象生命週期標識接口。

public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}

其做用在於,在對象實例化過程調用過「BeanPostProcessor的前置處理」以後,會接着檢測當前對象是否實現了InitializingBean接口,若是是,則會調用其afterPropertiesSet()方法進一步調整對象實例的狀態。好比,在有些狀況下,某個業務對象實例化完成後,還不能處於可使用狀態。這個時候就可讓該業務對象實現該接口,並在方法afterPropertiesSet()中完成對該業務對象的後續處理。

若是系統開發過程當中規定:全部業務對象的自定義初始化操做都必須以init()命名,爲了省去挨個 的設置init-method這樣的煩瑣,咱們還能夠經過最頂層的 的default-init-method統一指定這一init()方法名。

統一資源加載策略

Spring提出了一套基於org.springframework.core.io.Resource和org.springframework.core.io.ResourceLoader接口的資源抽象和加載策略。

Resource:
Resource接口能夠根據資源的不一樣類型,或者資源所處的不一樣場合,給出相應的具體實現。能夠幫助咱們查詢資源狀態、訪問資源內容,甚至根據當前資源建立新的相對資源。咱們能夠繼承org.springframework.core.io.AbstractResource抽象類。

ResourceLoader:
但如何去查找和定位這些資源,則應該是ResourceLoader的職責所在了。 org.springframework.core.io.ResourceLoader接口是資源查找定位策略的統一抽象,具體的資源查找定位策略則由相應的ResourceLoader實現類給出。

DefaultResourceLoader
ResourceLoader有一個默認的實現類,即org.springframework.core.io.DefaultResourceLoader,該類默認的資源查找處理邏輯以下。

  • (1) 首先檢查資源路徑是否以classpath:前綴打頭,若是是,則嘗試構造ClassPathResource類型資源並返回。
  • (2) 不然, (a) 嘗試經過URL,根據資源路徑來定位資源,若是沒有拋出MalformedURLException,有則會構造UrlResource類型的資源並返回; (b)若是仍是沒法根據資源路徑定位指定的資源,則委派getResourceByPath(String) 方 法 來 定 位 , DefaultResourceLoader 的getResourceByPath(String)方法默認實現邏輯是,構造ClassPathResource類型的資源並返回。

Resource和ResourceLoader類層次圖

AbstractApplicationContext做爲ResourceLoader和ResourcePatternResolver

四種加載方式:

  1. 使用以ResourceLoader身份登場的ApplicationContext
    ResourceLoader resourceLoader = new ClassPathXmlApplicationContext("配置文件路徑");

  2. ResourceLoader類型的注入

    • 2.1.0 依賴於ResourceLoader
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean id="resourceLoader" class="org.springframework.core.io.DefaultResourceLoader">
	</bean>

	<bean id="fooBar" class="org.springframework.mylearntest.resourceloader.FooBar">
		<property name="resourceLoader">
			<ref bean="resourceLoader"/>
		</property>
	</bean>
</beans>
  • 2.2.0 實現了ResourceLoaderAware或者ApplicationContextAware接口的實例類
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="fooBar" class="org.springframework.mylearntest.resourceloader.FooBarImplApplicationContextAware">
	</bean>
</beans>
  1. Resource類型的注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="xMailer" class="org.springframework.mylearntest.resourceloader.XMailer">
		<property name="template" value="resourceloader/resources.default_template.vm"/>
	</bean>
</beans>
  1. 在特定狀況下, ApplicationContext的Resource加載行爲
    當ClassPathXmlApplicationContext在實例化的時候,即便沒有指明classpath:或者classpath*:等前綴,它會默認從classpath中加載bean定義配置文件,
    而FileSystemXmlApplicationContext則有些 不一樣 ,若是 咱們 像以下 代碼 那樣指 定conf/appContext.xml,它會嘗試從文件系統中加載bean定義文件

Java SE 提供的國際化支持

  1. Locale
    不一樣的Locale表明不一樣的國家和地區,每一個國家和地區在Locale這裏都有相應的簡寫代碼表示,包括語言代碼以及國家代碼,這些代碼是ISO標準代碼。如,Locale.CHINA表明中國。

  2. ResourceBundle
    ResourceBundle用來保存特定於某個Locale的信息(能夠是String類型信息,也能夠是任何類型的對象)。一般, ResourceBundle管理一組信息序列,全部的信息序列有統一的一個basename,而後特定的Locale的信息,能夠根據basename後追加的語言或者地區代碼來區分。好比,咱們用一組properties文件來分別保存不一樣國家地區的信息,能夠像下面這樣來命名相應的properties文件:

messages.properties
messages_zh.properties
messages_zh_CN.properties
messages_en.properties
messages_en_US.properties
...

其中,文件名中的messages部分稱做ResourceBundle將加載的資源的basename,其餘語言或地區的資源在basename的基礎上追加Locale特定代碼。

MessageSource類層次結構圖

若是某個業務對象須要國際化的信息支持,那麼最簡單的辦法就是讓它實現MessageSourceAware接口,而後註冊到ApplicationContext容器。不過這樣一來,該業務對象對ApplicationContext容器的依賴性就太強了,顯得容器具備較強的侵入性。而實際上, 若是真的某個業務對象須要依賴於MessageSource的話,直接經過構造方法注入或者setter方法注入的方式聲明依賴就能夠了。

容器內部事件發佈

  1. 自定義事件發佈
    給出自定義事件類型( define your own event object)。 爲了針對具體場景能夠區分具體的事件類型, 咱們須要給出本身的事件類型的定義,一般作法是擴展java.util.EventObject類來實現自定義的事件類型。
    • 1.1 定義事件類型
package org.springframework.mylearntest.eventpublication.event;

import java.util.EventObject;

/**
 * 自定義事件類型
 */
public class MethodExecutionEvent extends EventObject {
	private static final long serialVersionUID = -71960369269303337L;
	private String methodName;

	public MethodExecutionEvent(Object source) {
		super(source);
	}

	public MethodExecutionEvent(Object source, String methodName) {
		super(source);
		this.methodName = methodName;
	}

	public String getMethodName() {
		return methodName;
	}

	public void setMethodName(String methodName) {
		this.methodName = methodName;
	}
}
  • 1.2 定義事件監聽器接口以及實現類
package org.springframework.mylearntest.eventpublication.event;


import java.util.EventListener;

/**
 * 自定義事件監聽器
 */
public interface MethodExecutionEventListener extends EventListener {
	/**
	 * 處理方法開始執行的時候發佈的MethodExecutionEvent事件
	 */
	void onMethodBegin(MethodExecutionEvent evt);
	/**
	 * 處理方法執行將結束時候發佈的MethodExecutionEvent事件
	 */
	void onMethodEnd(MethodExecutionEvent evt);
}
package org.springframework.mylearntest.eventpublication.event;

/**
 * 自定義事件監聽器實現
 */
public class SimpleMethodExecutionEventListener implements MethodExecutionEventListener {

	public void onMethodBegin(MethodExecutionEvent evt) {
		String methodName = evt.getMethodName();
		System.out.println("start to execute the method[" + methodName + "].");
	}

	public void onMethodEnd(MethodExecutionEvent evt) {
		String methodName = evt.getMethodName();
		System.out.println("finished to execute the method[" + methodName + "].");
	}
}
  • 1.3 定義事狀態枚舉類以及事件發佈者
package org.springframework.mylearntest.eventpublication.event;

public enum MethodExecutionStatus {
	BEGIN,END
}
package org.springframework.mylearntest.eventpublication.event;

import java.util.ArrayList;
import java.util.List;

public class MethodExeuctionEventPublisher {
	private List<MethodExecutionEventListener> listeners = new
			ArrayList<MethodExecutionEventListener>();

	public void methodToMonitor() {
		MethodExecutionEvent event2Publish =
				new MethodExecutionEvent(this, "methodToMonitor");
		publishEvent(MethodExecutionStatus.BEGIN, event2Publish);
		// 執行實際的方法邏輯
		// ...
		publishEvent(MethodExecutionStatus.END, event2Publish);
	}

	// 爲了不事件處理期間事件監聽器的註冊或移除操做影響處理過程,咱們對事件發佈時點的監聽器列表進行了一個安全複製( safe-copy)
	protected void publishEvent(MethodExecutionStatus status,
								MethodExecutionEvent methodExecutionEvent) {
		List<MethodExecutionEventListener> copyListeners =
				new ArrayList<MethodExecutionEventListener>(listeners);
		for (MethodExecutionEventListener listener : copyListeners) {
			if (MethodExecutionStatus.BEGIN.equals(status))
				listener.onMethodBegin(methodExecutionEvent);
			else
				listener.onMethodEnd(methodExecutionEvent);
		}
	}

	public void addMethodExecutionEventListener(MethodExecutionEventListener listener) {
		this.listeners.add(listener);
	}

	public void removeListener(MethodExecutionEventListener listener) {
		if (this.listeners.contains(listener))
			this.listeners.remove(listener);
	}

	public void removeAllListeners() {
		this.listeners.clear();
	}
}
  • 1.4 測試類
package org.springframework.mylearntest.eventpublication.event;

public class Test4Event {
	public static void main(String[] args) {
		MethodExeuctionEventPublisher eventPublisher =
				new MethodExeuctionEventPublisher();
		eventPublisher.addMethodExecutionEventListener(new
				SimpleMethodExecutionEventListener());
		eventPublisher.methodToMonitor();
		eventPublisher.removeAllListeners();
	}
}

在實現中,須要注意到,爲了不事件處理期間事件監聽器的註冊或移除操做影響處理過程,咱們對事件發佈時點的監聽器列表進行了一個安全複製( safe-copy)。另外,事件的發佈是順序執行,因此爲了可以不影響處理性能,事件監聽器的處理邏輯應該儘可能簡短。

自定義事件結構圖

  1. Spring 的容器內事件發佈類結構分析
    Spring 的 ApplicationContext 容 器 內 部 允 許 以 org.springframework.context.ApplicationEvent的形式發佈事件 ,容器內註冊的org.springframework.context.ApplicationListener類型的bean定義會被ApplicationContext容器自動識別,它們負責監聽容器內發佈的全部ApplicationEvent類型的事件。

ApplicationEvent
Spring容器內自定義事件類型,繼承自java.util.EventObject,它是一個抽象類,須要根據狀況提供相應子類以區分不一樣狀況。默認狀況下, Spring提供了三個實現。

  • ContextClosedEvent: ApplicationContext容器在即將關閉的時候發佈的事件類型。
  • ContextRefreshedEvent: ApplicationContext容器在初始化或者刷新的時候發佈的事件類
    型。
  • RequestHandledEvent: Web請求處理後發佈的事件,其有一子類ServletRequestHandledEvent提供特定於Java EE的Servlet相關事件。
package org.springframework.mylearntest.eventpublication.applicationevent;

import org.springframework.context.ApplicationEvent;
import org.springframework.mylearntest.eventpublication.event.MethodExecutionStatus;

public class MethodExecutionEvent extends ApplicationEvent {
	private static final long serialVersionUID = -71960369269303337L;
	private String methodName;
	private MethodExecutionStatus methodExecutionStatus;

	public MethodExecutionEvent(Object source) {
		super(source);
	}

	public MethodExecutionEvent(Object source, String methodName,
								MethodExecutionStatus methodExecutionStatus) {
		super(source);
		this.methodName = methodName;
		this.methodExecutionStatus = methodExecutionStatus;
	}

	public String getMethodName() {
		return methodName;
	}

	public void setMethodName(String methodName) {
		this.methodName = methodName;
	}

	public MethodExecutionStatus getMethodExecutionStatus() {
		return methodExecutionStatus;
	}

	public void setMethodExecutionStatus(MethodExecutionStatus methodExecutionStatus) {
		this.methodExecutionStatus = methodExecutionStatus;
	}
}

ApplicationListener

  • ApplicationContext容器內使用的自定義事件監聽器接口定義,繼承自java.util.EventListener。 ApplicationContext容器在啓動時,會自動識別並加載EventListener類型bean定義,一旦容器內有事件發佈,將通知這些註冊到容器的EventListener。
package org.springframework.mylearntest.eventpublication.applicationevent;


import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

@SuppressWarnings("rawtypes")
public class MethodExecutionEventListener implements ApplicationListener {
	public void onApplicationEvent(ApplicationEvent evt) {
		if (evt instanceof MethodExecutionEvent) {
			// 執行處理邏輯
		}
	}
}

ApplicationContext

  • 還記得ApplicationContext的定義吧?除了以前的ResourceLoader和MessageSource, ApplicationContext接口定義還繼承了ApplicationEventPublisher接口,該接口提供了void publishEvent(ApplicationEvent event)方法定義。不難看出, ApplicationContext容器如今擔當的就是事件發佈者的角色。ApplicationContext容器的具體實現類在實現事件的發佈和事件監聽器的註冊方面,並沒事必躬親,而是把這些活兒轉包給了一個稱做org.springframework.context.event.ApplicationEventMulticaster的接口。
package org.springframework.mylearntest.eventpublication.applicationevent;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.mylearntest.eventpublication.event.MethodExecutionStatus;

public class MethodExeuctionEventPublisher implements ApplicationEventPublisherAware {
	private ApplicationEventPublisher eventPublisher;

	public void methodToMonitor() {
		MethodExecutionEvent beginEvt = new
				MethodExecutionEvent(this, "methodToMonitor", MethodExecutionStatus.BEGIN);
		this.eventPublisher.publishEvent(beginEvt);
		// 執行實際方法邏輯
		// ...
		MethodExecutionEvent endEvt = new
				MethodExecutionEvent(this, "methodToMonitor", MethodExecutionStatus.END);
		this.eventPublisher.publishEvent(endEvt);
	}

	public void setApplicationEventPublisher(ApplicationEventPublisher appCtx) {
		this.eventPublisher = appCtx;
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="methodExecListener" class="org.springframework.mylearntest.eventpublication.applicationevent.MethodExecutionEventListener">
	</bean>
	<bean id="evtPublisher" class="org.springframework.mylearntest.eventpublication.applicationevent.MethodExeuctionEventPublisher">
	</bean>

</beans>
package org.springframework.mylearntest.eventpublication.applicationevent;

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

public class Test4AppEvent {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("eventpublication/applicationevent.xml");
		MethodExeuctionEventPublisher evtPublisher = (MethodExeuctionEventPublisher) context.getBean("evtPublisher");
		evtPublisher.methodToMonitor();
	}
}

ApplicationEventMulticaster有一抽象實現類——org.springframework.context.event.AbstractApplicationEventMulticaster,它實現了事件監聽器的管理功能。事件的發佈功能則委託給了其子類。 org.springframework.context.event.SimpleApplicationEventMulticaster。其默認使用了SyncTaskExecutor進行事件的發佈。爲了不這種方式可能存在的性能問題,咱們能夠爲其提供其餘類型的TaskExecutor實現類。

容器啓動開始,就會檢查容器內是否存在名稱爲applicationEventMulticaster的ApplicationEventMulticaster對象實例。有的話就使用提供的實現,沒有則默認初始化一個SimpleApplicationEventMulticaster做爲將會使用的ApplicationEventMulticaster。

Spring容器內事件發部實現類圖

IoC相關注解

看着依賴注入相關的信息,一半分散在Java源代碼中( @Autowired標註的信息),一半依然留在XML配置文件裏,有不少bean標籤依然存在。
當使用@Autoware註解可以同時找到兩個或者多個同一類型的對象實例,可使用@Qualifier對依賴注入的條件作進一步限定,指定具體是哪一個id。

<beans>
<bean class="org.springframework.beans.factory.annotation. ➥
AutowiredAnnotationBeanPostProcessor"/>
<bean id="newsProvider" class="..FXNewsProvider"/>
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="reutersNewsListner" class="..ReutersNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>
public class FXNewsProvider {
	@Autowired
	@Qualifier("reutersNewsListner")// 此時注入id=reutersNewsListner
	private IFXNewsListener newsListener;
	@Autowired
	private IFXNewsPersister newPersistener;
	...
}
// @Qualifier註解位於參數上
public class FXNewsProvider{
	// ...
	@Autowired
	public void setUp(@Qualifier("reutersNewsListner") IFXNewsListener newsListener,IFXNewsPersister newPersistener) {
		this.newsListener = newsListener;
		this.newPersistener = newPersistener;
	}
	// ...
}

@Resource與@Autowired不一樣,它遵循的是byName自動綁定形式的行爲準則,也就是說, IoC容器將根據@Resource所指定的名稱,到容器中查找beanName與之對應的實例,而後將查找到的對象實例注入給@Resource所標註的對象。
@PostConstruct和@PreDestroy不是服務於依賴注入的,它們主要用於標註對象生命週期管理相關方法,這與Spring的InitializingBean和DisposableBean接口,以及配置項中的init-method和destroy-method起到相似的做用。

就像@Autowired須要AutowiredAnnotationBeanPostProcessor爲 它 與 IoC 容 器 牽 線 搭 橋 一 樣 , JSR250 的 這 些 注 解 也 同 樣 需 要 一 個BeanPostProcessor幫助它們實現自身的價值。 這個BeanPostProcessor就是org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,只有將CommonAnnotationBeanPostProcessor添加到容器, JSR250的相關注解才能發揮做用。
<beans>
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
<bean id="newsProvider" class="..FXNewsProvider"/>
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>

<context:annotation-config> 不 但 幫 我 們 把 AutowiredAnnotationBeanPostProcessor 和CommonAnnotationBeanPostProcessor註冊到容器,同時還會把PersistenceAnnotationBeanPostProcessor和RequiredAnnotationBeanPostProcessor一併進行註冊,可謂一舉四得啊!

使用相應的註解對組成應用程序的相關類進行標註以後, classpath-scanning功能能夠從某一頂層包( base package)開始掃描。當掃描到某個類標註了相應的註解以後,就會提取該類的相關信息,構建對應的BeanDefinition,而後把構建完的BeanDefinition註冊到容器。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="org.spring21"/>
</beans>

<context:component-scan>它同時將AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor一併註冊到了容器中,因此,依賴注入的需求得以知足。

相關文章
相關標籤/搜索