1. 程式人生 > 實用技巧 >Spring IoC深入理解

Spring IoC深入理解

本文相關程式碼(來自官方原始碼spring-test模組)請參見spring-demysify org.springframework.mylearntest包下。

三種注入方式

1.構造方法注入

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

構造方法注入。 這種注入方式的優點就是,物件在構造完成之後,即已進入就緒狀態,可以馬上使用。缺點就是,當依賴物件比較多的時候,構造方法的引數列表會比較長。而通過反射構造物件的時候,對相同型別的引數的處理會比較困難,維護和使用上也比較麻煩。而且在Java中,構造方法無法被繼承,無法設定預設值。對於非必須的依賴處理,可能需要引入多個構造方法,而引數數量的變動可能造成維護上的不便。

2.setter 方法注入

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支援。缺點當然就是物件無法在構造完成後馬上進入就緒狀態。

  1. 介面注入

    FXNewsProvider為了讓IoC Service Provider為其注入所依賴的IFXNewsListener,首先需要實現IFXNewsListenerCallable介面,這個介面會宣告一個injectNewsListner方法(方法名隨意),該方法的引數,就是所依賴物件的型別。這樣, InjectionServiceContainer物件,即對應的IoCService Provider就可以通過這個介面方法將依賴物件注入到被注入物件FXNewsProvider當中。

介面注入。從注入方式的使用上來說,介面注入是現在不甚提倡的一種方式,基本處於“退役狀態”。因為它強制被注入物件實現不必要的介面,帶有侵入性。而構造方法注入和setter方法注入則不需要如此。

IoC Service Provider的職責

業務物件的構建管理:

在IoC場景中,業務物件無需關心所依賴物件如何構建如何獲得,但這部分工作始終需要有人來做。所以,IoC Service Provider需要將物件的構建邏輯從客戶端那裡剝離出來,以免這部分邏輯汙染業務物件的實現。

業務物件間的依賴繫結:

對於IoC Service Provider來說,這個職責是最艱鉅也是最重要的,這是它的最終使命之所在。如果不能完成這個職責,那麼,無論業務物件如何的“呼喊”,也不會得到依賴物件的任何反應(最常見的倒是會收到一個NullPointerException)。IoC Service Provider 通過結合之前構建和管理的所有業務物件,以及各個業務物件間可以識別依賴關係,將這些物件所依賴的物件注繫結,從而保證每個業務物件在使用的時候,可以處於就緒狀態。

IoC Service Provider 如何管理物件間的依賴關係

如何記錄物件之間的依賴關係:

  • 它可以通過最基本的文字檔案來記錄被注入物件和其依賴物件之間的對應關係;
  • 它也可以通過描述性較強的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框架支援、企業級服務整合等服務。



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:[email protected]
persist bean:[email protected]

使用方法注入後

<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()方法。

  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="[email protected]"></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 = [email protected]
}
}

實際上,有一種特殊型別的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特定程式碼。

如果某個業務物件需要國際化的資訊支援,那麼最簡單的辦法就是讓它實現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。

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一併註冊到了容器中,所以,依賴注入的需求得以滿足。