Spring:Spring容器(BeanFactory和ApplicationContext)
技術標籤:Spring
Spring的IoC容器是一個提供IoC支援的輕量級容器,IoC容器為管理物件之間的依賴關係提供了基礎功能。Spring為我們提供了兩種容器:BeanFactory和ApplicationContext。在基於Spring的JavaEE應用中,所有的元件都被當成Bean處理,包括資料來源、Hibernate的SessionFactory。
應用中所有元件都處於Spring的管理下,都被Spring以Bean的方式管理,Spring負責建立Bean例項,並管理其生命週期。Spring裡的Bean是非常廣義的概念,任何的Java物件、Java元件都被當成Bean處理。對於Spring而言,一切Java物件都是Bean。
Bean在Spring容器中執行,無須感受Spring容器的存在,一樣可以接受Spring的依賴注入,包括Bean成員變數的注入、協作者的注入、依賴關係的注入等。
Java程式面向介面程式設計,無須關心Bean例項的實現類:但Spring容器負責建立Bean例項,因此必須精確知道每個Bean例項的實現類,故Spring配置檔案必須制定Bean例項的實現類。
Spring容器最基本的介面就是BeanFactory。BeanFactory負責配置、建立、管理Bean,它有一個子介面:ApplicationContext,因此也被稱為Spring上下文。Spring還負責管理Bean與Bean之間的關係。
- BeanFactory由org.springframework.beans.factory.BeanFactory介面定義,是基礎型別的IoC容器,並能提供完整的IoC服務支援。IoC容器需要為其具體的實現提供基本的功能規範,而BeanFactory介面則提供了該功能規範的設計,每個具體的Spring IoC容器都要滿足BeanFactory介面的定義。
- ApplicationContext由org.springframework.context.ApplicationContext介面定義,是以BeanFactory為基礎構建的。ApplicationContext實現了HierarchicalBeanFactory
介面,HierarchicalBeanFactory又繼承自BeanFactory介面。
1,BeanFactory
BeanFactory採用了工廠設計模式,即Bean容器模式,負責讀取Bean的配置檔案,管理物件的生成、載入,維護Bean物件與Bean物件之間的依賴關係,負責Bean的生命週期。對於簡單的應用程式來說,使用BeanFactory就已經足夠管理Bean,在物件的管理上就可以獲得許多便利性。
BeanFactory介面包含如下幾個基本方法:
方法 描述 boolean containsBean(String name) 判斷Spring容器是否包含id為name的Bean例項 <T> T getBean(Class<T> requiredType) 獲取Spring容器中屬於requiredType型別的、惟一的Bean例項 Object getBean(String name) 返回容器id為name的Bean例項 <T> T getBean(String name.Class requiredType) 返回容器中id為name,並且型別為requiredType的Bean。 Class<?> getType(String name) 返回容器中id為name的Bean例項的型別 呼叫者只需要使用getBean()方法即可獲得指定Bean的引用,無須關心Bean的例項化過程。Bean例項的建立、初始化以及依賴關係的注入都由Spring容器完成。
BeanFactory是整個Spring的重點所在,整個Spring的核心都圍繞這BeanFactory。BeanFactory只是個介面,並不是IOC容器的具體實現,但是Spring容器給出了很多種實現,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。
BeanFacotry是spring中比較原始的Factory。如DefaultListableBeanFactory就是一種典型的BeanFactory。原始的BeanFactory無法支援spring的許多外掛,如AOP功能、Web應用等。
建立Spring容器的例項時,必須提供Spring容器管理Bean的詳細配置資訊。Spring的配置資訊通暢採用XML配置檔案來設定,因此,建立BeanFactory例項時,應該提供XML配置檔案作為引數。
XML配置檔案通常使用Resource物件傳入。Resource介面是Spring提供的資源訪問介面,通過使用該介面,Spring能以簡單、透明的方式訪問磁碟、類路徑以及網路上的資源。
//搜尋類載入路徑下的applicationContext.xml Resource resource = new ClassPathResource("applicationContext.xml"); //建立預設的BeanFactory容器 DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); //讓預設的BeanFactory容器載入isr對應的XML檔案 new XmlBeanDefinitionReader(beanFactory).loadBeanDefinitions(resource); Person chinese = beanFactory.getBean("chinese",Person.class);
或者
//搜尋類載入路徑下的applicationContext.xml Resource resource = new FileSystemResource("applicationContext.xml");
如果需要載入多個配置檔案來建立Spring容器,則應該採用BeanFactory的子介面ApplicationContext來建立。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Person chinese = context.getBean("chinese", Person.class); chinese.useAxe();
或者
ApplicationContext context = new FileSystemXmlApplicationContext("applicationContext.xml");
2,ApplicationContext
ApplicationContext 面向使用 Spring 框架的開發者,幾乎所有的應用場合(除非對於某些記憶體非常關鍵的應用)都直接使用 ApplicationContext 而非底層的 BeanFactory作為Spring容器,因此把Spring容器稱為Spring上下文。
ApplicationContext是BeanFactory介面的子介面,增強了BeanFactory的功能,對於大部分Java EE應用而言,使用它作為Spring容器更方便,可以使用Spring所提供的一些特色及高階的功能:
- ApplicationContext提供BeanFactory所有的功能
- 提供了取得資原始檔(Resource File)更方便的方法
- 提供了文字訊息解析的方法
- 支援國際化訊息
- 載入多個配置檔案等
其常用實現類:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext和AnnotationConfigApplicationContext(建議不用)。
當系統建立ApplicationContext容器時,預設會預初始化所有的singleton Bean。也就是說,當ApplicationContext容器初始化完成後,容器會自動初始化所有的singleton Bean,包括呼叫構造器建立時將有較大的系統開銷,但一旦ApplicationContext初始化完成,程式後面獲取singleton Bean例項時將擁有較好的效能。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Chinese chinese = context.getBean("chinese", Chinese.class); chinese.useAxe();
(1)FileSystemXmlApplicationContext:可以指定XML的絕對或相對路徑在讀取Bean的定義檔案;預設是去專案的路徑下載入,可以是相對路徑,也可以是絕對路徑,若是絕對路徑,“file:” 字首可以預設。
ApplicationContext factory=new FileSystemXmlApplicationContext("src/appcontext.xml"); //使用了 classpath: 字首,作為標誌, 這樣,FileSystemXmlApplicationContext 也能夠讀入classpath下的相對路徑 ApplicationContext factory=new FileSystemXmlApplicationContext("classpath:appcontext.xml"); ApplicationContext factory=new FileSystemXmlApplicationContext("file:G:/Test/src/appcontext.xml"); ApplicationContext factory=new FileSystemXmlApplicationContext("G:/Test/src/appcontext.xml");
(2)ClassPathXmlApplicationContext:從Classpath設定的路徑中讀取Bean的定義檔案;預設會去 classPath 路徑下找。classPath 路徑指的就是編譯後的 classes 目錄;“classpath:” 是可以預設的。如果是絕對路徑,就需要加上 “file:” 字首,不可預設。
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); ApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml","dao.xml"}); ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:/*.xml"); ApplicationContext factory=new ClassPathXmlApplicationContext("classpath:appcontext.xml"); ApplicationContext factory=new ClassPathXmlApplicationContext("appcontext.xml"); // src目錄下的 ApplicationContext factory=new ClassPathXmlApplicationContext("conf/appcontext.xml");// src/conf 目錄下的 ApplicationContext factory=new ClassPathXmlApplicationContext("file:G:/Test/src/appcontext.xml");
(3) AnnotationConfigApplicationContext:基於Java的配置類載入Spring的應用上下文。避免使用application.xml進行配置。不建議使用。
ApplicationContext context=new AnnotationConfigApplicationContext(BasedJava.class); UserInfo userInfo = (UserInfo) context.getBean(UserInfo.class); System.out.println(userInfo.getName() + ":" + userInfo.getAddress()); -------------------------------------------------------- AnnotationConfigApplicationContext appl=new AnnotationConfigApplicationContext(); appl.register(BasedJava.class); appl.scan("exec"); appl.refresh();//此步驟不可少 UserInfo userInfo2 = (UserInfo) appl.getBean(UserInfo.class); System.out.println(userInfo2.getName() + ":" + userInfo2.getAddress());
3,ApplicationContext的事件機制
ApplicationContex的事件機制使觀察者設計模式的實現,通過ApplicationEvent類和ApplicationListener介面,可以實現ApplicationContext的事件處理。如果容器有一個ApplicationListener Bean,每當ApplicationContext釋出ApplicationEvent時,ApplicationListener Bean將自動被觸發。
Spring的事件框架有兩個重要成員:
- ApplicationEvent:容器事件,必須由ApplicationContext釋出。
- ApplicationListener:監聽器,可由容器中的任何監聽器Bean擔任。
實際上,Spring的事件機制與所有的事件機制基本相似,它們都需要由事件源、事件和事件監聽器組成。只是此處的事件源是ApplicationContext,且事件必須由Java程式顯式觸發。
Spring的內建事件:
- ContextRefreshedEvent:ApplicationContext容器初始化或重新整理觸發該事件。此處的初始化是指,所有的Bean被成功載入,後處理的Bean被檢測並激活,所有的singleton Bean被預例項化,ApplicationContext容器已就緒可用。
- ContextStartedEvent:當使用ConfigurableApplicationContext(ApplicationContext的子介面)介面的start()方法啟動ApplicationContext容器時觸發該事件。容器管理生命週期的Bean例項將獲得一個指定的啟動訊號,這在經常需要停滯後重新啟動的場合比較常見。
- ContextClosedEvent:當使用ConfigurableApplicationContext(ApplicationContext的子介面)介面的close()方法關閉ApplicationContext容器時觸發該事件。
- ContextStoppedEvent:當使用ConfigurableApplicationContext(ApplicationContext的子介面)介面的stop()方法使ApplicationContext停止時觸發該事件。此處的停止意味著容器管理生命週期的Bean例項將獲得一個指定的停止訊號,被停止的Spring容器可再次呼叫start()方法重新啟動。
- RequestHandledEvent:Web相關的事件,只能應用於使用DispatcherServlet的Web應用中。在使用Spring作為前端的MVC控制器時,當Spring處理使用者請求結束後,系統會自動觸發該事件。
程式:Spring容器的事件機制。
package Bean; import org.springframework.context.ApplicationEvent; public class EmailEvent extends ApplicationEvent { private String address; private String text; public EmailEvent(Object source) { super(source); } public EmailEvent(Object source, String address, String text) { super(source); this.address = address; this.text = text; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getText() { return text; } public void setText(String text) { this.text = text; } }
上面的EmailEvent類繼承了Application類,除此之外,它就是一個普通的Java類。只要一個Java類繼承了ApplicationEvent積累,那該物件可作為Spring容器的容器事件。
容器事件的監聽器類必須實現ApplicationListener介面,實現該介面必須實現如下方法:
- onApplicationEvent(ApplicationEvent event):每當容器內發生任何事件時,此方法就會被觸發。
package Bean; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; public class EamilNotifier implements ApplicationListener { public void onApplicationEvent(ApplicationEvent applicationEvent) { //只處理EmailEvent事件,模擬傳送 if (applicationEvent instanceof EmailEvent){ EmailEvent emailEvent = (EmailEvent) applicationEvent; System.out.println("需要傳送郵件的地址為:"+emailEvent.getAddress()); System.out.println("需要傳送郵件的內容為:"+emailEvent.getText()); }else{ //其他事件不做任何處理 System.out.println("其他事件:"+applicationEvent); } } }
將監聽器注入Spring容器。
<bean class="Bean.EamilNotifier"></bean>
當系統建立Spring容器、載入Spring容器時會自動觸發容器時間,容器時間監聽器可以監聽到這些事件。除此之外,程式可呼叫ApplicationContext的publisEvent()方法主動觸發容器事件。
import Bean.EmailEvent; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) throws BeansException { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); EmailEvent emailEvent = new EmailEvent("test","[email protected]","你好"); context.publishEvent(emailEvent); } } =================================================== 其他事件:org.springframework.context.event.ContextRefreshedEvent[source=org[email protected]446cdf90, started on Sat Jan 23 16:24:55 CST 2021] 需要傳送郵件的地址為:[email protected] 需要傳送郵件的內容為:你好
從結果可以看出,監聽器不僅監聽到程式所觸發的事件也堅挺到容器內建的事件。實際上,如果開發者需要在Spring容器初始化、銷燬時回撥自定義方法,就可以通過上面的事件監聽器來實現。
4,讓Bean獲取Spring容器
之前所說,都是程式先建立Spring容器,再呼叫Spring容器的getBean()方法來獲取Spring容器中的Bean。在這種訪問模式下,程式中總是持有Spring容器的引用。但在Web應用中,Spring容器通常採用宣告式方式配置產生:開發者只要在web.xml檔案中配置一個Listener,該Listener將會負責初始化Spring容器,前端MVC框架可以直接呼叫Spring中的Bean,無須訪問Spring容器本身。這種情況下,容器中的Bean處於容器管理下,無須主動訪問容器,只需接受容器的依賴注入即可。
在某些特殊的情況下,Bean需要實現某個功能,但該功能必須藉助Spring容器才能實現,此時就必須讓該Bean先獲取Spring容器,然後藉助於Spring容器來實現該功能。
為了讓Bean獲取它所在的Spring容器,可以讓Bean實現BeanFactoryAware介面。Spring呼叫介面中的方法將Spring容器作為引數傳入該方法。
package org.springframework.context; import org.springframework.beans.BeansException; import org.springframework.beans.factory.Aware; public interface ApplicationContextAware extends Aware { void setApplicationContext(ApplicationContext var1) throws BeansException; }
Spring容器會檢查容器中的所有Bean,如果發現某個Bean實現了ApplicationContextAware介面,Spring容器會在建立該Bean之後,自動呼叫該Bean的setApplicationContextAware()方法,呼叫該方法時,會將容器本身作為引數傳給該方法——該方法的實現部分將Spring摻入的引數賦值給該Person的applicationContext例項變數,因此接下來即可通過該applicationContext例項變數來訪問容器本身。
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class Person implements ApplicationContextAware { private ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public void sayHi(String name){ } }