上文:Spring IOC 一——容器裝配Bean的簡單使用html
上篇文章介紹了 Spring IOC 中最重要的兩個概念——容器和Bean,以及如何使用 Spring 容器裝配Bean。本文接着記錄 Spring 中 IOC 的相關知識。java
部分參考資料:
《Spring實戰(第4版)》
《輕量級 JavaEE 企業應用實戰(第四版)》
Spring 官方文檔
W3CSchool Spring教程
易百教程 Spring教程程序員
目前,scope的取值有5種取值:
在Spring 2.0以前,有singleton和prototype兩種;
在Spring 2.0以後,爲支持web應用的ApplicationContext,加強另外三種:request,session和global session類型,它們只適用於web程序,一般是和XmlWebApplicationContext共同使用。web
getBean
方法獲取實例時,都會建立一個新的實例。配置方式:spring
(1) XML 文件配置:session
<bean id="helloSpring" class="com.sharpcj.hello.HelloSpring" scope="ConfigurableBeanFactory.SCOPE_SINGLETON"> <!-- singleton --> <property name="name" value="Spring"/> </bean>
(2) 註解配置:app
@Component @Scope("singleton") public class HelloSpring { }
默認狀況下,當容器啓動以後,會將全部做用域爲單例的bean建立好,如配置 lazy-init
值爲true,表示延遲加載,即容器啓動以後,不會當即建立該實例。yii
(1) XML文件配置:ide
<bean id="mb1" class="com.sharpcj.hello.HelloSpring" lazy-init="true"></bean>
(2) 註解配置:post
@Component @Lazy @Scope("singleton") public class HelloSpring { }
Bean 初始化回調和銷燬回調
HelloSpring.java
package com.sharpcj.cycle; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Component public class HelloSpring implements InitializingBean, DisposableBean{ public HelloSpring(){ System.out.println("構造方法"); } public void xmlInit(){ System.out.println("xml Init"); } public void xmlDestory(){ System.out.println("xml Destory"); } @PostConstruct public void init(){ System.out.println("annotation Init"); } @PreDestroy public void destory(){ System.out.println("annotation Destory"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("interface afterPropertiesSet"); } @Override public void destroy() throws Exception { System.out.println("interface destroy"); } }
(1) XML文件配置:
<bean id="hello" class="com.sharpcj.cycle.HelloSpring" init-method="xmlInit" destroy-method="xmlDestory"/>
(2) 註解配置:
@PostConstruct public void init(){ System.out.println("annotation Init"); } @PreDestroy public void destory(){ System.out.println("annotation Destory"); }
另外 Bean 能夠實現 org.springframework.beans.factory.InitializingBean
和 org.springframework.beans.factory.DisposableBean
兩個接口。
執行結果:
構造方法 interface afterPropertiesSet xml Init interface destroy xml Destory
或者
構造方法 annotation Init interface afterPropertiesSet annotation Destory interface destroy
建立 Bean 有三種方式:經過調用構造方法建立 Bean, 調用實例工廠方法建立Bean,調用靜態工廠方法建立 Bean。
這是最多見的狀況, 當咱們經過配置文件,或者註解的方式配置 Bean, Spring 會經過調用 Bean 類的構造方法,來建立 Bean 的實例。經過 xml 文件配置,明確指定 Bean 的 class 屬性,或者經過註解配置,Spring 容器知道 Bean 的完整類名,而後經過反射調用該類的構造方法便可。
直接上代碼:
Ipet.java
package com.sharpcj.factorytest; public interface IPet { void move(); }
Dog.java
package com.sharpcj.factorytest; public class Dog implements IPet { @Override public void move() { System.out.println("Dog can run!"); } }
Parrot.java
package com.sharpcj.factorytest; public class Parrot implements IPet { @Override public void move() { System.out.println("Parrot can fly!"); } }
工廠類, PetFactory.java
package com.sharpcj.factorytest; public class PetFactory { public IPet getPet(String type){ if ("dog".equals(type)) { return new Dog(); } else if ("parrot".equals(type)){ return new Parrot(); } else { throw new IllegalArgumentException("pet type is illegal!"); } } }
resources 文件夾下配置文件, factorybeantest.xml
<?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="petFactory" class="com.sharpcj.factorytest.PetFactory"></bean> <bean id="dog" factory-bean="petFactory" factory-method="getPet"> <constructor-arg value="dog"></constructor-arg> </bean> <bean id="parrot" factory-bean="petFactory" factory-method="getPet"> <constructor-arg value="parrot"></constructor-arg> </bean> </beans>
測試類,AppTest.java
package com.sharpcj.factorytest; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; public class AppTest { public static void main(String[] args) { Resource resource = new ClassPathResource("factorybeantest.xml"); BeanFactory factory = new DefaultListableBeanFactory(); BeanDefinitionReader bdr = new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory); bdr.loadBeanDefinitions(resource); Dog dog = (Dog) factory.getBean("dog"); Parrot parrot = (Parrot) factory.getBean("parrot"); dog.move(); parrot.move(); } }
程序結果:
能夠看到,程序正確執行了。注意看配置文件中,咱們並無配置 dog 和 parrot 兩個 Bean 類的 class
屬性,而是配置了他們的 factory-bean
和 factory-method
兩個屬性,這樣,Spring 容器在建立 dog 和 parrot 實例時會先建立 petFactory 的實例,而後再調用其工廠方法,建立對應的 dog 和 parrot 實例。
另外,假設咱們在測試類中經過 factory 獲取 Bean 實例時,傳入一個非法的參數,會如何? PetFactory 類工廠方法的代碼,看起來會拋出咱們自定義的異常?
好比調用以下代碼:
factory.getBean("cat");
結果是:
結果說明,Spring 自己就處理了參數異常,由於咱們並無在配置文件中配置中配置 name 爲 「cat」 的 Bean, 因此,Spring 容器拋出了此異常,程序執行不到工廠方法裏去了。
拋開 Spring 不談,相比實例工廠方法,其實咱們平時用的更多的多是靜態工廠方法。 Spring 固然也有靜態工廠方法建立 Bean 的實現。下面咱們修改工廠方法爲靜態方法:
package com.sharpcj.staticfactorytest; import com.sharpcj.factorytest.Dog; import com.sharpcj.factorytest.IPet; import com.sharpcj.factorytest.Parrot; public class PetFactory { public static IPet getPet(String type){ if ("dog".equals(type)) { return new Dog(); } else if ("parrot".equals(type)){ return new Parrot(); } else { throw new IllegalArgumentException("pet type is illegal!"); } } }
此時咱們也應該修改配置文件,這裏咱們從新建立了一個配置文件, staticfactorbeantest.xml
<?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="dog" class="com.sharpcj.staticfactorytest.PetFactory" factory-method="getPet"> <constructor-arg value="dog"></constructor-arg> </bean> <bean id="parrot" class="com.sharpcj.staticfactorytest.PetFactory" factory-method="getPet"> <constructor-arg value="parrot"></constructor-arg> </bean> </beans>
測試代碼:
package com.sharpcj.staticfactorytest; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; public class Apptest { public static void main(String[] args) { Resource resource = new ClassPathResource("staticfactorybeantest.xml"); BeanFactory factory = new DefaultListableBeanFactory(); BeanDefinitionReader bdr = new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory); bdr.loadBeanDefinitions(resource); Dog dog = (Dog) factory.getBean("dog"); Parrot parrot = (Parrot) factory.getBean("parrot"); dog.move(); parrot.move(); } }
測試結果以下:
結果正常,這裏注意配置文件,使用靜態工廠方法是,配置文件中咱們並無配置 PetFactory
, 而在配置
dog 和 parrot 時,咱們配置的 class
屬性的值是工廠類的完整類名com.sharpcj.staticfactorytest.PetFactory
,同事配置了 factory-method
屬性。
調用實例工廠方法和調用靜態工廠方法建立 Bean 的用法基本類似,區別以下:
factory-bvean
屬性肯定工廠 Bean; 而配置靜態工廠方法建立 Bean,則使用 class
屬性肯定靜態工廠類。factory-method
指定生產 Bean 實例的工廠方法;<constructor-arg.../>
元素指定參數值;<property.../>
元素肯定參數值。FactoryBean 和 BeanFactory 是兩個極易混淆的概念,須要理解清楚。下面分別來明說這兩個概念。
FactoryBean 翻譯過來就是 工廠Bean 。須要說明的是,這裏的 FactoryBean 和上一節提到的工廠方法建立Bean不是一個概念,切莫不要把實例工廠建立 Bean 時,配置的工廠 Bean ,和 FactoryBean 混爲一談。二者沒有聯繫,上一節說的是標準的工廠模式,Spring 只是經過調用工廠方法來建立 Bean 的實例。
這裏的所說的 工廠 Bean 是一種特殊的 Bean 。它須要實現 FactoryBean
這個接口。
FactoryBean
接口提供了三個方法:
T getObject() throws Exception; Class<?> getObjectType(); boolean isSingleton() {return true;}
當自定義一個類實現了FactoryBean
接口後,將該類部署在 Spring 容器裏,再經過 Spring 容器調用 getBean
方法獲取到的就不是該類的實例了,而是該類實現的 getObject
方法的返回值。這三個方法意義以下:
下面舉一個例子:
定義一個類 StringFactoryBean.java
package com.sharpcj.factorybeantest; import org.springframework.beans.factory.FactoryBean; public class StringFactoryBean implements FactoryBean<Object> { private String type; private String originStr; public void setType(String type) { this.type = type; } public void setOriginStr(String originStr) { this.originStr = originStr; } @Override public Object getObject() throws Exception { if("builder".equals(type) && originStr != null){ return new StringBuilder(originStr); } else if ("buffer".equals(type) && originStr != null) { return new StringBuffer(originStr); } else { throw new IllegalArgumentException(); } } @Override public Class<?> getObjectType() { if("builder".equals(type)){ return StringBuilder.class; } else if ("buffer".equals(type)) { return StringBuffer.class; } else { throw new IllegalArgumentException(); } } @Override public boolean isSingleton() { return false; } }
配置文件, factorybean.xml
<?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="strFactoryBean" class="com.sharpcj.factorybeantest.StringFactoryBean"> <property name="type" value="buffer"/> <property name="originStr" value="hello"/> </bean> </beans>
測試類 AppTest.java
package com.sharpcj.factorybeantest; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("factorybean.xml"); System.out.println(context.getBean("strFactoryBean")); System.out.println(context.getBean("strFactoryBean").getClass().toString()); } }
結果以下:
那有沒有辦法把獲取 FactoryBean 自己的實例呢?固然能夠,以下方式
context.getBean("&strFactoryBean")
在getBean
方法是,在Bean id 前面增長&
符號。
package com.sharpcj.factorybeantest; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("factorybean.xml"); System.out.println(context.getBean("strFactoryBean")); System.out.println(context.getBean("strFactoryBean").getClass().toString()); System.out.println(context.getBean("&strFactoryBean").getClass().toString()); } }
結果以下:
其實在前面的例子中,AppTest.java 類中咱們已經使用過 BeanFactory 了, BeanFactory 也是一個接口。Spring 有兩個核心的接口: BeanFactory 和 ApplicationContext ,其中 ApplicationContext 是 BeanFactory 的子接口,他們均可以表明 Spring 容器。Spring 容器是生成 Bean 實例的工廠,並管理容器中的 Bean 。
BeanFactory 包含以下幾個基本方法:
boolean containsBean(String name) // 判斷Spring容器中是否包含 id 爲 name 的 Bean 實例 <T> getBean(Class<T> requeriedType) // 獲取Spring容器中屬於 requriedType 類型的、惟一的 Bean 實例。 Object getBean(String name) // 返回容器中 id 爲 name 的 Bean 實例 <T> getBean(String name, Class requiredType) // 返回容器中 id 爲name,而且類型爲 requriedType 的Bean Class<T> getType(String name) // 返回 id 爲 name 的 Bean 實例的類型
Spring 提供了兩種經常使用的後處理使得 Spring 容器容許開發者對 Spring 容器進行擴展,分別是 Bean 後處理器和容器後處理器。
Bean 後處理器是一種特殊的 Bean, 它能夠對容器中的 Bean 進行後處理,對 Bean 進行額外增強。這種特殊的 Bean 不對外提供服務,它主要爲容器中的目標 Bean 進行擴展,例如爲目標 Bean 生成代理等。
Bean 後處理器須要實現 BeanPostProcessor 接口,該接口包含以下兩個方法:
Object postProcessBeforeInitialization(Object bean, String beanName) Object postProcessAfterInitialization(Object bean, String beanName)
這兩個方法的第一個參數都表示即將進行後處理的 Bean 實例,第二個參數是該 Bean 的配置 id ,這兩個方法會在目標 Bean 初始化以前和初始化以後分別回調。
看例子:
新建一個類,PetBeanPostProcessor.java 重寫上述兩個方法。
package com.sharpcj.beanpostprocessor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class PetBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if ("dog".equals(beanName)) { System.out.println("準備初始化 dog ..."); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if ("parrot".equals(beanName)) { System.out.println("parrot 初始化完成 ... "); } return bean; } }
配置文件, beanpostprocessor.xml:
<?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="dog" class="com.sharpcj.beanpostprocessor.Dog"/> <bean id="parrot" class="com.sharpcj.beanpostprocessor.Parrot"/> <bean class="com.sharpcj.beanpostprocessor.PetBeanPostProcessor"/> </beans>
最後看測試代碼:AppTest.java
package com.sharpcj.beanpostprocessor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beanpostprocessor.xml"); Dog dog = (Dog) context.getBean("dog"); Parrot parrot = (Parrot) context.getBean("parrot"); dog.move(); parrot.move(); } }
執行結果以下:
能夠看到,咱們像配置其它 Bean 同樣配置該 Bean 後處理器,可是咱們沒有配置 id ,這是由於咱們使用的 ApplicationContext 做爲 Spring 容器,Spring 容器會自動檢測容器中全部的 Bean ,若是發現某個 Bean 實現了 BeanPostProcessor 接口,ApplicationContext 就會自動將其註冊爲 Bean 後處理器。 若是使用 BeanFactory 做爲 Spring 的容器,則需手動註冊 Bean 後處理器。這時,須要在配置文件中爲 Bean 後處理器指定 id 屬性,這樣容器能夠先獲取到 Bean 後處理器的對象,而後註冊它。以下:
配置文件:
<?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="dog" class="com.sharpcj.beanpostprocessor.Dog"/> <bean id="parrot" class="com.sharpcj.beanpostprocessor.Parrot"/> <bean id="petBeanPostProcessor" class="com.sharpcj.beanpostprocessor.PetBeanPostProcessor"/> </beans>
測試代碼:
Resource resource = new ClassPathResource("beanpostprocessor.xml"); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(beanFactory).loadBeanDefinitions(resource); PetBeanPostProcessor petBeanPostProcessor = (PetBeanPostProcessor) beanFactory.getBean("petBeanPostProcessor"); beanFactory.addBeanPostProcessor(petBeanPostProcessor); Dog dog = (Dog) beanFactory.getBean("dog"); Parrot parrot = (Parrot) beanFactory.getBean("parrot"); dog.move(); parrot.move();
上面例子中咱們只是在實例化 Bean 先後打印了兩行 Log , 那麼實際開發中 Bean 後處理有什麼用處呢?其實 Bean 後處理器的做用很明顯,至關於一個攔截器,對目標 Bean 進行加強,在目標 Bean 的基礎上生成新的 Bean。 若咱們須要對容器中某一批 Bean 進行加強處理,則能夠考慮使用 Bean 後處理器,結合前面一篇文章講到到代理模式,能夠想到,咱們徹底能夠經過 Bean 後處理器結合代理模式作更多實際工做,好比初始化,深圳徹底改變容器中一個或者一批 Bean 的行爲。
你能夠配置多個 BeanPostProcessor 接口,經過設置 BeanPostProcessor 實現的 Ordered 接口提供的 order 屬性來控制這些 BeanPostProcessor 接口的執行順序。
容器後處理器則是對容器自己進行處理。容器後處理器須要實現 BeanFactoryPostProcessor 接口。該接口必須實現以下一個方法:
postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
相似於 BeanPostProcessor , ApplicationContext 能夠自動檢測到容器中的容器後處理器,並自動註冊,若使用 BeanFactory 做爲 Spring 容器,則須要手動獲取到該容器後處理器的對象來處理該 BeanFactory 容器。
例子:容器後處理器, PetBeanFactoryPostProcessor.java
package com.sharpcj.beanfactorypostprocessor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; public class PetBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("容器後處理器沒有對容器作改變..."); } }
配置文件:
<?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="dog" class="com.sharpcj.beanfactorypostprocessor.Dog"/> <bean id="parrot" class="com.sharpcj.beanfactorypostprocessor.Parrot"/> <bean id="petBeanFactoryPostProcessor" class="com.sharpcj.beanfactorypostprocessor.PetBeanFactoryPostProcessor"/> </beans>
測試代碼:
package com.sharpcj.beanfactorypostprocessor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beanfactorypostprocessor.xml"); Dog dog = (Dog) context.getBean("dog"); Parrot parrot = (Parrot) context.getBean("parrot"); dog.move(); parrot.move(); } }
結果以下:
容器後處理器的做用對象是容器自己,展開 BeanFactoryPostProcessor 接口的繼承關係,咱們能夠看到 Spring 自己提供了不少常見的容器後處理器。
其中一些在實際開發中很經常使用,如屬性佔位符配置器 PropertyPlaceholderConfigurer 、 重寫佔位符配置器 PropertyOverrideConfigurer 等。
程序啓動時,初始化 Spring 容器,咱們已經知道如何經過容器,獲取 Bean 的實例方式,形如:
BeanFactory factory = xxx ; factory.getBean(xxx...);
在某些特殊狀況下,咱們須要讓 Bean 獲取 Spring 容器,這個如何實現呢?
咱們只須要讓 Bean 實現 BeanFactoryAware 接口,該接口只有一個方法:
void setBeanFactory(BeanFactory beanFactory);
該方法的參數即指向建立該 Bean 的 BeanFactory ,這個 setter 方法看起來有點奇怪,習慣上在 java 中 setter 方法都是由程序員調用,傳入參數,而此處的方法則由 Spring 調用。與次相似的,還有 ApplicationContextAware 接口,須要實現一個方法
void setApplicationContext(ApplicationContext applicationContext);
下面經過例子來講明:
此次咱們的 Dog 類,修改了:
package com.sharpcj.beanfactoryaware; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; public class Dog implements IPet, BeanFactoryAware { private BeanFactory factory; @Override public void move() { System.out.println("Dog can run!"); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { factory = beanFactory; } public void test() { Parrot parrot = (Parrot) factory.getBean("parrot"); parrot.move(); } }
測試代碼,AppTest.java
package com.sharpcj.beanfactoryaware; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beanfactoryaware.xml"); Dog dog = (Dog) context.getBean("dog"); dog.test(); } }
輸出結果:
Parrot can fly!
結果代表,咱們確實在 Dog 類裏面獲取到了 Spring 容器,而後經過該容器建立了 Parrot 實例。
有時候,當咱們在開發一個 Bean 類時,Bean 什麼時候被部署到 Spring 容器中,部署到 Spring 容器中的 id 又是什麼,開發的時候咱們須要提早預知,這是就能夠藉助 Spring 提供的 BeanNameAware
接口,該接口提供一個方法:
void setBeanName(String name);
用法與上面同樣,這裏再也不過多解釋,修改上面的例子:
Dog.java
package com.sharpcj.beanfactoryaware; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; public class Dog implements IPet, BeanFactoryAware, BeanNameAware { private BeanFactory factory; private String id; @Override public void move() { System.out.println("Dog can run!"); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { factory = beanFactory; } public void test() { System.out.println("Dog 的 id 是: " + id); } @Override public void setBeanName(String name) { id = name; } }
測試類:AppTest.java
package com.sharpcj.beanfactoryaware; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beanfactoryaware.xml"); Dog dog = (Dog) context.getBean("dog"); dog.test(); } }
結果:
Dog 的 id 是: dog
ApplicationContext 的事件機制是觀察者模式的實現,由 事件源、事件和事件監聽器組成。經過 ApplicationEvent
類 和 ApplicationListener
接口實現。
Spring 事件機制的兩個重要成員:
事件機制原理:有 ApplicationContext 經過 publishEvent()
方法發佈一個實現了ApplicationEvent
接口的事件,任何實現了 ApplicationListener
接口的 Bean 充當事件監聽器,能夠對事件進行處理。這個原理有點相似於 Android 裏面廣播的實現。
下面給出一個例子:
ITeacher.java
package com.sharpcj.appevent; public interface ITeacher { void assignWork(); }
ChineseTeacher.java
package com.sharpcj.appevent; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; public class ChineseTeacher implements ITeacher, ApplicationListener { @Override public void assignWork() { System.out.println("背誦三首唐詩"); } @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ComplainEvent) { System.out.println("語文老師收到了抱怨..."); System.out.println("抱怨的內容是:" + ((ComplainEvent) event).getMsg()); System.out.println("認真傾聽抱怨,可是做業量依然不能減小..."); } } }
MathTeacher.java
package com.sharpcj.appevent; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; public class MathTeacher implements ITeacher, ApplicationListener { @Override public void assignWork() { System.out.println("作三道數學題"); } @Override public void onApplicationEvent(ApplicationEvent event) { System.out.println("數學老師收到了事件,但沒有判斷事件類型,不做處理。。。。"); } }
定義一個事件 ComplainEvent.java 繼承自 ApplicationContextEvent:
package com.sharpcj.appevent; import org.springframework.context.ApplicationContext; import org.springframework.context.event.ApplicationContextEvent; public class ComplainEvent extends ApplicationContextEvent { private String msg; /** * Create a new ContextStartedEvent. * * @param source the {@code ApplicationContext} that the event is raised for * (must not be {@code null}) */ public ComplainEvent(ApplicationContext source) { super(source); } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
IStudent.java
package com.sharpcj.appevent; public interface IStudent { void doWork(); }
XiaoZhang.java
package com.sharpcj.appevent; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class XiaoZhang implements IStudent, ApplicationContextAware { private ApplicationContext mContext; @Override public void doWork() { System.out.println("小張背了李白的唐詩,作了三道幾何體"); } public void complain() { ComplainEvent complainEvent = new ComplainEvent(mContext); complainEvent.setMsg("做業太多了"); mContext.publishEvent(complainEvent); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.mContext = applicationContext; } }
配置文件, appevent.xml:
<?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="chineseTeacher" class="com.sharpcj.appevent.ChineseTeacher"/> <bean id="mathTeacher" class="com.sharpcj.appevent.MathTeacher"/> <bean id="xiaoZhang" class="com.sharpcj.appevent.XiaoZhang"/> </beans>
測試類, AppTest.java:
package com.sharpcj.appevent; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("appevent.xml"); XiaoZhang xiaoZhang = (XiaoZhang) context.getBean("xiaoZhang"); xiaoZhang.complain(); } }
測試結果以下:
咦,數學老師也受到了事件,爲何還受到兩次事件?首先根據代碼,咱們能想明白,只要是容器發佈了事件,全部實現了ApplicationListener
接口的監聽器都能接收到事件,那爲何,數學老師打印出了兩條呢?我猜,容器初始化期間,自己發佈了一次事件。下面稍微修改了一下代碼,便驗證了個人猜測是正確的。
MathTeacher.java
package com.sharpcj.appevent; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; public class MathTeacher implements ITeacher, ApplicationListener { @Override public void assignWork() { System.out.println("作三道數學題"); } @Override public void onApplicationEvent(ApplicationEvent event) { System.out.println("數學老師收到了事件,但沒有判斷事件類型,不做處理。。。。" + event.getClass().getSimpleName()); } }
而後再次執行,結果以下:
事實證實,容器初始化時,確實發佈了一次 ContextRefreshedEvent 事件。
既上一篇文章總結了一下 Spring 裝配 Bean 的三種方式以後,這篇文章繼續記錄了一寫 SpringIOC 的高級知識,本文沒有按照通常書籍的順序介紹 Spring 容器的相關知識,主要是從橫向對幾組關鍵概念進行對比解釋,主要記錄了一下 SpringIOC 中的一些關鍵知識點。固然 Spring IOC 其它的知識點還有不少,好比裝配 Bean 時屬性歧義性處理、 Bean 的組合屬性、注入集合值、國際化、基於 XML Schema 的簡化配置方式等。其它知識點能夠經過查閱官方文檔或者專業書籍學習。 接下來會再整理一篇 Spring AOP 的文章。