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
接口注入。從注入方式的使用上來講,接口注入是如今不甚提倡的一種方式,基本處於「退役狀態」。由於它強制被注入對象實現沒必要要的接口,帶有侵入性。而構造方法注入和setter方法注入則不須要如此。spring
業務對象的構建管理:
在IoC場景中,業務對象無需關心所依賴對象如何構建如何得到,但這部分工做始終須要有人來作。因此,IoC Service Provider須要將對象的構建邏輯從客戶端那裏剝離出來,以避免這部分邏輯污染業務對象的實現。
業務對象間的依賴綁定:
對於IoC Service Provider來講,這個職責是最艱鉅也是最重要的,這是它的最終使命之所在。若是不能完成這個職責,那麼,不管業務對象如何的「呼喊」,也不會獲得依賴對象的任何反應(最多見的卻是會收到一個NullPointerException)。IoC Service Provider 經過結合以前構建和管理的全部業務對象,以及各個業務對象間能夠識別依賴關係,將這些對象所依賴的對象注綁定,從而保證每一個業務對象在使用的時候,能夠處於就緒狀態。sql
如何記錄對象之間的依賴關係:apache
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對象。數組
<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,可是,這只是它被冠以IoC之名的部分緣由,咱們不能忽略的是「容器」。 Spring的IoC容器是一個提供IoC支持的輕量級容器,除了基本的IoC支持,它做爲輕量級容器還提供了IoC以外的支持。如在Spring的IoC容器之上, Spring還提供了相應的AOP框架支持、企業級服務集成等服務。
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); }
// 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 { ... }
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 { }
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; } }
local、 parent和bean的區別在於:
local只能指定與當前配置的對象在同一個配置文件的對象定義的名稱(能夠得到XML解析器的id約束驗證支持);
parent則只能指定位於當前容器的父容器中定義的對象引用;
bean則基本上通吃,因此,一般狀況下,直接使用bean來指定對象引用就能夠了
但這種場合下,使用idref纔是最爲合適的。由於使用idref,容器在解析配置的時候就能夠幫 你檢查這個beanName究竟是否存在,而不用等到運行時才發現這個beanName對應的對象實例不存在。畢竟,輸錯名字的問題很常見。如下代碼演示了idref的使用:
<property name="newsListenerBeanName"> <idref bean="djNewsListener"/> </property>
autodetect
自動綁定和手動明確綁定各有利弊。自動綁定的優勢有以下兩點。
僅指定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>
代碼清單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屬性賦值true,以免容器將其實例化。對於ApplicationContext容器尤爲如此,由於默認狀況下, ApplicationContext會在容器啓動的時候就對其管理的全部bean進行實例化,只有標誌爲abstract的bean除外。
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>
經過
即便沒有方法注入, 只要在實現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(); } }
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(); } }
容器啓動階段
容器啓動開始,首先會經過某種途徑加載Configuration MetaData。除了代碼方式比較直接,在大部分狀況下,容器須要依賴某些工具類(BeanDefinitionReader)對加載的Configuration MetaData進行解析和分析,並將分析後的信息編組爲相應的BeanDefinition,最後把這些保存了bean定義必要信息的BeanDefinition,註冊到相應的BeanDefinitionRegistry,這樣容器啓動工做就完成了。
Bean實例化階段
通過第一階段,如今全部的bean定義信息都經過BeanDefinition的方式註冊到了BeanDefinitionRegistry中。當某個請求方經過容器的getBean方法明確地請求某個對象時,或者因依賴關係容器須要隱式地調用getBean方法時,就會觸發第二階段的活動。
該階段,容器會首先檢查所請求的對象以前是否已經初始化。若是沒有,則會根據註冊的BeanDefinition所提供的信息實例化被請求對象,併爲其注入依賴。若是該對象實現了某些回調接口,也會根據回調接口的要求來裝配它。當該對象裝配完畢以後,容器會當即將其返回請求方使用。若是說第一階段只是根據圖紙裝配生產線的話,那麼第二階段就是使用裝配好的生產線來生產具體的產品了。
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。
<?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或者覆蓋它。
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer"> <property name="location" value="pool-adjustment.properties"/> </bean>
dataSource.minEvictableIdleTimeMillis=1000 dataSource.maxOpenPreparedStatements=50
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定義。咱們知道,容器如今僅僅擁有全部對象的BeanDefinition來保存實例化階段將要用的必要信息。只有當請求方經過BeanFactory的getBean()方法來請求某個對象實例的時候,纔有可能觸發Bean實例化階段的活動。 BeanFactory的getBe法能夠被客戶端對象顯式調用,也能夠在容器內部隱式地被調用。隱式調用有以下兩種狀況。
容器在內部實現的時候,採用「策略模式(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));
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是存在於對象實例化階段,而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發送這個密碼給新聞服務器進行鏈接驗證的時候,首先須要對系統中取得的密碼進行解密,而後才能發送。
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; } }
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; } }
<?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來構造對象實例。構形成功後直接返回造完成的對象實例,而不會按照「正規的流程」繼續執行。這就是它可能形成「短路」的緣由。
org.springframework.beans.factory.InitializingBean是容器內部普遍使用的一個對象生命週期標識接口。
public interface InitializingBean { void afterPropertiesSet() throws Exception; }
其做用在於,在對象實例化過程調用過「BeanPostProcessor的前置處理」以後,會接着檢測當前對象是否實現了InitializingBean接口,若是是,則會調用其afterPropertiesSet()方法進一步調整對象實例的狀態。好比,在有些狀況下,某個業務對象實例化完成後,還不能處於可使用狀態。這個時候就可讓該業務對象實現該接口,並在方法afterPropertiesSet()中完成對該業務對象的後續處理。
若是系統開發過程當中規定:全部業務對象的自定義初始化操做都必須以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,該類默認的資源查找處理邏輯以下。
Resource和ResourceLoader類層次圖
AbstractApplicationContext做爲ResourceLoader和ResourcePatternResolver
四種加載方式:
使用以ResourceLoader身份登場的ApplicationContext
ResourceLoader resourceLoader = new ClassPathXmlApplicationContext("配置文件路徑");
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>
<?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>
<?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>
Locale
不一樣的Locale表明不一樣的國家和地區,每一個國家和地區在Locale這裏都有相應的簡寫代碼表示,包括語言代碼以及國家代碼,這些代碼是ISO標準代碼。如,Locale.CHINA表明中國。
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方法注入的方式聲明依賴就能夠了。
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; } }
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 + "]."); } }
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(); } }
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)。另外,事件的發佈是順序執行,因此爲了可以不影響處理性能,事件監聽器的處理邏輯應該儘可能簡短。
ApplicationEvent
Spring容器內自定義事件類型,繼承自java.util.EventObject,它是一個抽象類,須要根據狀況提供相應子類以區分不一樣狀況。默認狀況下, Spring提供了三個實現。
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
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
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。
看着依賴注入相關的信息,一半分散在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一併註冊到了容器中,因此,依賴注入的需求得以知足。