1. 程式人生 > 其它 >Spring:Spring容器(BeanFactory和ApplicationContext)

Spring:Spring容器(BeanFactory和ApplicationContext)

技術標籤:Spring

Spring的IoC容器是一個提供IoC支援的輕量級容器,IoC容器為管理物件之間的依賴關係提供了基礎功能。Spring為我們提供了兩種容器:BeanFactoryApplicationContext在基於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容器最基本的介面就是BeanFactoryBeanFactory負責配置、建立、管理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的核心都圍繞這BeanFactoryBeanFactory只是個介面,並不是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)更方便的方法
  • 提供了文字訊息解析的方法
  • 支援國際化訊息
  • 載入多個配置檔案等

其常用實現類:FileSystemXmlApplicationContextClassPathXmlApplicationContext和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){
        
    }
}