最全面的 Spring 學習筆記

泊浮目發表於2017-03-28

Spring致力於提供一種方法管理你的業務物件。在大量Java EE的應用中,隨處可見Spring。今天我將簡單的介紹一下Spring這個框架。

本文適合讀者:

  • 想學Spring的Java開發者
  • 剛用Spring不久的人

Why

為什麼要使用Spring?

Spring主要兩個有功能為我們的業務物件管理提供了非常便捷的方法:

  • DI(Dependency Injection,依賴注入)
  • AOP(Aspect Oriented Programming,面向切面程式設計)

Java Bean

每一個類實現了Bean的規範才可以由Spring來接管,那麼Bean的規範是什麼呢?

  • 必須是個公有(public)類
  • 有無參建構函式
  • 用公共方法暴露內部成員屬性(getter,setter)

實現這樣規範的類,被稱為Java Bean。即是一種可重用的元件。

額外補充:
Java 帝國之Java bean (上) 
Java 帝國之Java bean(下)

DI-依賴注入

簡單來說,一個系統中可能會有成千上萬個物件。如果要手工維護它們之間的關係,這是不可想象的。我們可以在Spring的XML檔案描述它們之間的關係,由Spring自動來注入它們——比如A類的例項需要B類的例項作為引數set進去。

擴充套件:Spring 的本質系列(1) — 依賴注入

AOP-面向切面程式設計

就以日誌系統為例。在執行某個操作前後都需要輸出日誌,如果手工加程式碼,那簡直太可怕了。而且等程式碼龐大起來,也是非常難維護的一種情況。這裡就需要面向切面來程式設計

擴充套件:Spring本質系列(2)-AOP

How

關於Bean

Bean的生命週期

如你所見,在bean準備就緒之前,bean工廠執行了若干啟動步驟。我們對圖進行詳細描述:

  1. Spring對bean進行例項化;
  2. Spring將值和bean的引用注入到bean對應的屬性中;
  3. 如果bean實現了BeanNameAware介面,Spring將bean的ID傳遞給setBean-Name()方法;
  4. 如果bean實現了BeanFactoryAware介面,Spring將呼叫setBeanFactory()方法,將BeanFactory容器例項傳入;
  5. 如果bean實現了ApplicationContextAware介面,Spring將呼叫setApplicationContext()方法,將bean所在的應用上下文的引用傳入進來;
  6. 如果bean實現了BeanPostProcessor介面,Spring將呼叫它們的post-ProcessBeforeInitialization()方法;
  7. 如果bean實現了InitializingBean介面,Spring將呼叫它們的after-PropertiesSet()方法。類似地,如果bean使用init-method宣告瞭初始化方法,該方法也會被呼叫;
  8. 如果bean實現了BeanPostProcessor介面,Spring將呼叫它們的post-ProcessAfterInitialization()方法;
  9. 此時,bean已經準備就緒,可以被應用程式使用了,它們將一直駐留在應用上下文中,直到該應用上下文被銷燬;
  10. 如果bean實現了DisposableBean介面,Spring將呼叫它的destroy()介面方法。同樣,如果bean使用destroy-method宣告瞭銷燬方法,該方法也會被呼叫。

Bean的作用域

Spring定義了多種Bean作用域,可以基於這些作用域建立bean,包括:

  • 單例(Singleton):在整個應用中,只建立bean的一個例項。
  • 原型(Prototype):每次注入或者通過Spring應用上下文獲取的時候,都會建立一個新的bean例項。
  • 會話(Session):在Web應用中,為每個會話建立一個bean例項。
  • 請求(Rquest):在Web應用中,為每個請求建立一個bean例項。

在程式碼裡看起來是這樣的:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MyIsBean{...}

XML版本:

<bean id="BEANID"
  class = "com.my.beans"
  scope="prototype"
>

在預設情況下,Spring應用上下文中所有bean都是作為以單例(singleton)的形式建立的。也就是說,不管給定的一個bean被注入到其他bean多少次,每次所注入的都是同一個例項。

在大多數情況下,單例bean是很理想的方案。初始化和垃圾回收物件例項所帶來的成本只留給一些小規模任務,在這些任務中,讓物件保持無狀態並且在應用中反覆重用這些物件可能並不合理。

有時候,可能會發現,你所使用的類是易變的(mutable),它們會保持一些狀態,因此重用是不安全的。在這種情況下,將class宣告為單例的bean就不是什麼好主意了,因為物件會被汙染,稍後重用的時候會出現意想不到的問題。

宣告Bean

以下是宣告Bean的註解:

  • @Component 元件,沒有明確的角色
  • @Service 在業務邏輯層使用
  • @Repository 在資料訪問層使用
  • @Controller 在展現層使用(MVC -> Spring MVC)使用
  • 在這裡,可以指定bean的id名:Component(“yourBeanName”)
  • 同時,Spring支援將@Named作為@Component註解的替代方案。兩者之間有一些細微的差異,但是在大多數場景中,它們是可以互相替換的。

關於依賴注入

注入Bean的註解

@Autowired Spring提供的註解

不僅僅是物件,還有在構造器上,還能用在屬性的Setter方法上。

不管是構造器、Setter方法還是其他的方法,Spring都會嘗試滿足方法引數上所宣告的依賴。假如有且只有一個bean匹配依賴需求的話,那麼這個bean將會被裝配進來。

如果沒有匹配的bean,那麼在應用上下文建立的時候,Spring會丟擲一個異常。為了避免異常的出現,你可以將@Autowired的required屬性設定為false。

將required屬性設定為false時,Spring會嘗試執行自動裝配,但是如果沒有匹配的bean的話,Spring將會讓這個bean處於未裝配的狀態。但是,把required屬性設定為false時,你需要謹慎對待。如果在你的程式碼中沒有進行null檢查的話,這個處於未裝配狀態的屬性有可能會出現NullPointerException。

@Inject註解來源於Java依賴注入規範,該規範同時還為我們定義了@Named註解。在自動裝配中,Spring同時支援@Inject和@Autowired。儘管@Inject和@Autowired之間有著一些細微的差別,但是在大多數場景下,它們都是可以互相替換的。

@Autowired 是最常見的註解之一,但在老專案中,你可能會看到這些註解,它們的作用和@Autowired 相近:

  • @Inject 是JSR-330提供的註解
  • @Resource 是JSR-250提供的註解

條件化的Bean

假設你希望一個或多個bean只有在應用的類路徑下包含特定的庫時才建立。或者我們希望某個bean只有當另外某個特定的bean也宣告瞭之後才會建立。我們還可能要求只有某個特定的環境變數設定之後,才會建立某個bean。

在Spring 4之前,很難實現這種級別的條件化配置,但是Spring 4引入了一個新的@Conditional註解,它可以用到帶有@Bean註解的方法上。如果給定的條件計算結果為true,就會建立這個bean,否則的話,這個bean會被忽略。

通過ConditionContext,我們可以做到如下幾點:

  • 藉助getRegistry()返回的BeanDefinitionRegistry檢查bean定義;
  • 藉助getBeanFactory()返回的ConfigurableListableBeanFactory檢查bean是否存在,甚至探查bean的屬性;
  • 藉助getEnvironment()返回的Environment檢查環境變數是否存在以及它的值是什麼;
  • 讀取並探查getResourceLoader()返回的ResourceLoader所載入的資源;
  • 藉助getClassLoader()返回的ClassLoader載入並檢查類是否存在。

處理自動裝配的歧義性

標示首選的bean

在宣告bean的時候,通過將其中一個可選的bean設定為首選(primary)bean能夠避免自動裝配時的歧義性。當遇到歧義性的時候,Spring將會使用首選的bean,而不是其他可選的bean。實際上,你所宣告就是“最喜歡”的bean。

限定自動裝配的bean

設定首選bean的侷限性在於@Primary無法將可選方案的範圍限定到唯一一個無歧義性的選項中。它只能標示一個優先的可選方案。當首選bean的數量超過一個時,我們並沒有其他的方法進一步縮小可選範圍。

與之相反,Spring的限定符能夠在所有可選的bean上進行縮小範圍的操作,最終能夠達到只有一個bean滿足所規定的限制條件。如果將所有的限定符都用上後依然存在歧義性,那麼你可以繼續使用更多的限定符來縮小選擇範圍。

@Qualifier註解是使用限定符的主要方式。它可以與@Autowired和@Inject協同使用,在注入的時候指定想要注入進去的是哪個bean。例如,我們想要確保要將IceCream注入到setDessert()之中:

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert){
  this.dessert = dessert;
}

這是使用限定符的最簡單的例子。為@Qualifier註解所設定的引數就是想要注入的bean的ID。所有使用@Component註解宣告的類都會建立為bean,並且bean的ID為首字母變為小寫的類名。因此,@Qualifier(“iceCream”)指向的是元件掃描時所建立的bean,並且這個bean是IceCream類的例項。

實際上,還有一點需要補充一下。更準確地講,@Qualifier(“iceCream”)所引用的bean要具有String型別的“iceCream”作為限定符。如果沒有指定其他的限定符的話,所有的bean都會給定一個預設的限定符,這個限定符與bean的ID相同。因此,框架會將具有“iceCream”限定符的bean注入到setDessert()方法中。這恰巧就是ID為iceCream的bean,它是IceCream類在元件掃描的時候建立的。

基於預設的bean ID作為限定符是非常簡單的,但這有可能會引入一些問題。如果你重構了IceCream類,將其重新命名為Gelato的話,那此時會發生什麼情況呢?如果這樣的話,bean的ID和預設的限定符會變為gelato,這就無法匹配setDessert()方法中的限定符。自動裝配會失敗。

這裡的問題在於setDessert()方法上所指定的限定符與要注入的bean的名稱是緊耦合的。對類名稱的任意改動都會導致限定符失效。

SpringEL

  • Value實現資源的注入

Bean的初始化和銷燬

  • Java配置方式:initMethod和destoryMethod
  • 註解:@PostConstruct和@PreDestory

Profile

提供在不同的環境下使用不同的配置

啟用Profile

Spring在確定哪個profile處於啟用狀態時,需要依賴兩個獨立的屬性:spring.profiles.active和spring.profiles.default。如果設定了spring.profiles.active屬性的話,那麼它的值就會用來確定哪個profile是啟用的。但如果沒有設定spring.profiles.active屬性的話,那Spring將會查詢spring.profiles.default的值。如果spring.profiles.active和spring.profiles.default均沒有設定的話,那就沒有啟用的profile,因此只會建立那些沒有定義在profile中的bean。

使用profile進行測試

當執行整合測試時,通常會希望採用與生產環境(或者是生產環境的部分子集)相同的配置進行測試。但是,如果配置中的bean定義在了profile中,那麼在執行測試時,我們就需要有一種方式來啟用合適的profile。

Spring提供了@ActiveProfiles註解,我們可以使用它來指定執行測試時要啟用哪個profile。在整合測試時,通常想要啟用的是開發環境的profile。

比如Profile(“dev”)

Application Event

使用Application Event可以做到Bean與Bean之間的通訊

Spring的事件需要遵循如下流程:

  • 自定義事件,整合ApplicationEvent
  • 定義事件監聽器,實現ApplicationListener
  • 使用容器釋出事件

關於AOP

名詞介紹

通知(Advice)

通知定義了切面是什麼以及何時使用。除了描述切面要完成的工作,通知還解決了何時執行這個工作的問題。它應該應用在某個方法被呼叫之前?之後?之前和之後都呼叫?還是隻在方法丟擲異常時呼叫?

Spring切面可以應用5種型別的通知:

  • 前置通知(Before):在目標方法被呼叫之前呼叫通知功能;
  • 後置通知(After):在目標方法完成之後呼叫通知,此時不會關心方法的輸出是什麼;
  • 返回通知(After-returning):在目標方法成功執行之後呼叫通知;
  • 異常通知(After-throwing):在目標方法丟擲異常後呼叫通知;
  • 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法呼叫之前和呼叫之後執行自定義的行為。

對應註解:

注  解 通  知
@After 通知方法會在目標方法返回或丟擲異常後呼叫
@AfterReturning 通知方法會在目標方法返回後呼叫
@AfterThrowing 通知方法會在目標方法丟擲異常後呼叫
@Around 通知方法會將目標方法封裝起來
@Before 通知方法會在目標方法呼叫之前執行

連線點(Join point)

連線點是在應用執行過程中能夠插入切面的一個點。這個點可以是呼叫方法時、丟擲異常時、甚至修改一個欄位時。切面程式碼可以利用這些點插入到應用的正常流程之中,並新增新的行為。

切點(Pointcut)

如果說通知定義了切面的“什麼”和“何時”的話,那麼切點就定義了“何處” 。切點的定義會匹配通知所要織入的一個或多個連線點。我們通常使用明確的類和方法名稱,或是利用正規表示式定義所匹配的類和方法名稱來指定這些切點。有些AOP框架允許我們建立動態的切點,可以根據執行時的決策(比如方法的引數值)來決定是否應用通知。

切面(Aspect)

通知+切點=切面

引入(Introduction)

引入允許我們向現有的類新增新方法或屬性

織入(Weaving)

織入是把切面應用到目標物件並建立新的代理物件的過程。切面在指定的連線點被織入到目標物件中。在目標物件的生命週期裡有多個點可以進行織入:

  • 編譯期:切面在目標類編譯時被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
  • 類載入期:切面在目標類載入到JVM時被織入。這種方式需要特殊的類載入器(ClassLoader),它可以在目標類被引入應用之前增強該目標類的位元組碼。AspectJ 5的載入時織入(load-time weaving,LTW)就支援以這種方式織入切面。
  • 執行期:切面在應用執行的某個時刻被織入。一般情況下,在織入切面時,AOP容器會為目標物件動態地建立一個代理物件。Spring AOP就是以這種方式織入切面的。

Spring對AOP的支援:

  1. 基於代理的經典Spring AOP;
  2. 純POJO切面(4.x版本需要XML配置);
  3. @AspectJ註解驅動的切面;
  4. 注入式AspectJ切面(適用於Spring各版本)。

前三種都是Spring AOP實現的變體,Spring AOP構建在動態代理基礎之上,因此,Spring對AOP的支援侷限於方法攔截。也就是說,AspectJ才是王道。

另外在代理類中包裹切面,Spring在執行期把切面織入到Spring管理的bean中。如下圖所示,代理類封裝了目標類,並攔截被通知方法的呼叫,再把呼叫轉發給真正的目標bean。當代理攔截到方法呼叫時,在呼叫目標bean方法之前,會執行切面邏輯。直到應用需要被代理的bean時,Spring才建立代理物件。 如果使用的是ApplicationContext的話,在ApplicationContext從BeanFactory中載入所有bean的時候,Spring才會建立被代理的物件。因為Spring執行時才建立代理物件,所以我們不需要特殊的編譯器來織入Spring AOP的切面。

例子

public interface Performance(){
  public void perform();
}

現在來寫一個切點表示式,這個表示式能夠設定當perform()方法執行時觸發通知的呼叫。

execution(* concert.Performance.perform(..))
//execution:在方法執行時觸發
//*:返回任意型別
//concert.Performance:方法所屬類
//perform:方法名
//(..):使用任意引數

不僅如此,還可以寫的更復雜一點

execution(* concert.Performance.perform(..)&&within(concert.*))
//增加了一個與操作,當concert包下的任意類方法被呼叫時也會觸發

在切點中選擇bean

execution(*concert.Performance.perform()) and bean('woodstock')
//限定bean id為woodstock

來個完整的切面

@Aspect
public class Audience{
  @Before("execution(**concert.Performance.perform(..))")
  public void silenceCellPhones(){
    System.out.println("Silencing cell phones");
  }
  @Before("execution{** concert.Performance.perform{..}}")
  public void taskSeats(){
    System.out.println("Talking seats");
  }
  @AfterReturning("execution{** concert.Performance.perform{..}}")
  public void applause(){
    System.out.println("CLAP CLAP CLAP!!!");
  }
  @AfterThrowing("execution{** concert.Performance.perform{..}}")
  public void demanRefund(){
    System.out.println("Demanding a refund");
  }
}

可以簡化一下

@Aspect
public class Audience{
  //避免頻繁使用切點表示式
  @Pointcut("execution(** concert.Performance.perform(..))")
  public void performance(){}

  @Before("performance()")
  public void silenceCellPhones(){
    System.out.println("Silencing cell phones");
  }
  @Before("performance()")
  public void taskSeats(){
    System.out.println("Talking seats");
  }
  @AfterReturning("performance()")
  public void applause(){
    System.out.println("CLAP CLAP CLAP!!!");
  }
  @AfterThrowing("performance()")
  public void demanRefund(){
    System.out.println("Demanding a refund");
  }
}

XML中宣告切面

AOP配置元素 用途
<aop:advisor> 定義AOP通知器
<aop:after> 定義AOP後置通知(不管被通知的方法是否執行成功)
<aop:after-returning> 定義AOP返回通知
<aop:after-throwing> 定義AOP異常通知
<aop:around> 定義AOP環繞通知
<aop:aspect> 定義一個切面
<aop:aspectj-autoproxy> 啟用@AspectJ註解驅動的切面
<aop:before> 定義一個AOP前置通知
<aop:config> 頂層的AOP配置元素。大多數的<aop:*>元素必須包含在<aop:config>元素內
<aop:declare-parents> 以透明的方式為被通知的物件引入額外的介面
<aop:pointcut> 定義一個切點

來個栗子

public class Audience{
  public void silenceCellPhones(){
    System.out.println("Silencing cell phones");
  }
  public void taskSeats(){
    System.out.println("Talking seats");
  }
  public void applause(){
    System.out.println("CLAP CLAP CLAP!!!");
  }
  public void demandRefund(){
    System.out.println("Demanding a refund");
  }
}

通過XML將無註解的Audience宣告為切面

<aop:config>
  <aop:aspect ref="audience">
    <aop:before
      pointcut ="execution(** concert.Performance.perform(..))"
      method="sillenceCellPhones"/>
    <aop:before
      pointcut ="execution(** concert.Performance.perform(..))"
      method="taskSeats"/>
    <aop:after-returning
      pointcut ="execution(** concert.Performance.perform(..))"
      method="applause"/>
    <aop:After-throwing
        pointcut ="execution(** concert.Performance.perform(..))"
        method="demanRefund"/>
  </aop:aspect>
</aop:config>

AspectJ關於Spring AOP的AspectJ切點,最重要的一點就是Spring僅支援AspectJ切點指示器(pointcut designator)的一個子集。讓我們回顧下,Spring是基於代理的,而某些切點表示式是與基於代理的AOP無關的。下表列出了Spring AOP所支援的AspectJ切點指示器。

Spring藉助AspectJ的切點表示式語言來定義Spring切面

AspectJ指示器 描  述
arg() 限制連線點匹配引數為指定型別的執行方法
@args() 限制連線點匹配引數由指定註解標註的執行方法
execution() 用於匹配是連線點的執行方法
this() 限制連線點匹配AOP代理的bean引用為指定型別的類
target 限制連線點匹配目標物件為指定型別的類
@target() 限制連線點匹配特定的執行物件,這些物件對應的類要具有指定型別的註解
within() 限制連線點匹配指定的型別
@within() 限制連線點匹配指定註解所標註的型別(當使用Spring AOP時,方法定義在由指定的註解所標註的類裡)
@annotation 限定匹配帶有指定註解的連線點

Spring高階特性

由於Spring特殊的依賴注入技巧,導致Bean之間沒有耦合度。

但是Bean有時需要使用spring容器本身的資源,這時你的Bean必須意識到Spring容器的存在。所以得使用Spring Aware,下面來看看Spring Aware提供的介面

BeanNameAware 獲得到容器中Bean的名稱
BeanFactory 獲得當前的bean factory,這樣可以呼叫容器的服務
ApplicationContextAware* 當前application context,這樣可以呼叫容器的服務
MessageSourceAware 獲得Message source
ApplicationEventPublisherAware 應用時間釋出器,可以釋出時間,
ResourceLoaderAware 獲得資源載入器,可以獲得外部資原始檔

@TaskExecutor

這樣可以實現多執行緒和併發程式設計。通過@EnableAsync開啟對非同步任務的支援,並通過實際執行的Bean的方法始中使用@Async註解來宣告其是一個非同步任務

@Scheduled 計劃任務

首先通過在配置類註解@EnableScheduling來開啟對計劃任務的支援,然後在要執行計劃任務的方法上註解@Scheduled,宣告這是一個計劃任務

@Conditional

根據滿足某一個特定條件建立一個特定的Bean。

組合註解與元註解

元註解就是可以註解到別的註解上的註解,被註解的註解稱之為組合註解,組合註解具備註解其上的元註解的功能。

@Enable*註解的工作原理

通過觀察這些@Enable*註解的原始碼,我們發現所有的註解都有一個@Import註解,@Import是用來匯入配置類的,這也就意外著這些自動開啟的實現其實是匯入了一些自動配置的Bean。這些匯入配置的方式主要範圍以下三種型別:

  • 第一類:直接匯入配置類
  • 第二類:依據條件選擇配置類
  • 第三類:動態註冊Bean

What

簡單的分析一下Spring。

Spring 框架中的核心元件只有三個:Core、Context 和 Bean。它們構建起了整個 Spring 的骨骼架構。沒有它們就不可能有 AOP、Web 等上層的特性功能。下面也將主要從這三個元件入手分析 Spring。

Spring的設計理念

用過Spring的同學都知道Bean在Spring的作用是非常重要的。通過一系列簡單的配置來滿足類與類之間的依賴關係——這叫做依賴注入。而依賴注入的關係是在一個叫IOC的容器中進行管理。

核心元件

我們說到Spring 框架中的核心元件只有三個:Core、Context 和 Bean。那麼Core和Context是如何協作的呢?

我們知道 Bean 包裝的是 Object,而 Object 必然有資料,如何給這些資料提供生存環境就是 Context 要解決的問題,對 Context 來說他就是要發現每個 Bean 之間的關係,為它們建立這種關係並且要維護好這種關係。所以 Context 就是一個 Bean 關係的集合,這個關係集合又叫 Ioc 容器 ,一旦建立起這個 Ioc 容器後 Spring 就可以為你工作了。那 Core 元件又有什麼用武之地呢?其實 Core 就是發現、建立和維護每個 Bean 之間的關係所需要的一些列的工具。

解析核心元件

Bean

前面已經說明了 Bean 元件對 Spring 的重要性,下面看看 Bean 這個元件式怎麼設計的。Bean 元件在 Spring 的 org.springframework.beans 包下。這個包下的所有類主要解決了三件事:Bean 的定義、Bean 的建立以及對 Bean 的解析。對 Spring 的使用者來說唯一需要關心的就是 Bean 的建立,其他兩個由 Spring 在內部幫你完成了,對你來說是透明的。

Spring Bean 的建立時典型的工廠模式,他的頂級介面是 BeanFactory,下圖是這個工廠的繼承層次關係:

BeanFactory 有三個子類:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。但是從上圖中我們可以發現最終的預設實現類是 DefaultListableBeanFactory,他實現了所有的介面。那為何要定義這麼多層次的介面呢?查閱這些介面的原始碼和說明發現,每個介面都有他使用的場合,它主要是為了區分在 Spring 內部在操作過程中物件的傳遞和轉化過程中,對物件的資料訪問所做的限制。例如 ListableBeanFactory 介面表示這些 Bean 是可列表的,而 HierarchicalBeanFactory 表示的是這些 Bean 是有繼承關係的,也就是每個 Bean 有可能有父 Bean。AutowireCapableBeanFactory 介面定義 Bean 的自動裝配規則。這四個介面共同定義了 Bean 的集合、Bean 之間的關係、以及 Bean 行為。

Context

ApplicationContext 是 Context 的頂級父類,他除了能標識一個應用環境的基本資訊外,他還繼承了五個介面,這五個介面主要是擴充套件了 Context 的功能。下面是 Context 的類結構圖:

從上圖中可以看出 ApplicationContext 繼承了 BeanFactory,這也說明了 Spring 容器中執行的主體物件是 Bean,另外 ApplicationContext 繼承了 ResourceLoader 介面,使得 ApplicationContext 可以訪問到任何外部資源,這將在 Core 中詳細說明。

ApplicationContext 的子類主要包含兩個方面:

  • ConfigurableApplicationContext 表示該 Context 是可修改的,也就是在構建 Context 中使用者可以動態新增或修改已有的配置資訊,它下面又有多個子類,其中最經常使用的是可更新的 Context,即 AbstractRefreshableApplicationContext類。
  • WebApplicationContext 顧名思義,就是為 web 準備的 Context 他可以直接訪問到 ServletContext,通常情況下,這個介面使用的少。

再往下分就是按照構建 Context 的檔案型別,接著就是訪問 Context 的方式。這樣一級一級構成了完整的 Context 等級層次。

總體來說 ApplicationContext 必須要完成以下幾件事:

  • 標識一個應用環境
  • 利用 BeanFactory 建立 Bean 物件
  • 儲存物件關係表
  • 能夠捕獲各種事件

Context 作為 Spring 的 IOC 容器,基本上整合了 Spring 的大部分功能,或者說是大部分功能的基礎。

Core

Core 元件作為 Spring 的核心元件,他其中包含了很多的關鍵類,其中一個重要組成部分就是定義了資源的訪問方式。這種把所有資源都抽象成一個介面的方式很值得在以後的設計中拿來學習。下面就重要看一下這個部分在 Spring 的作用。

從上圖可以看出 Resource 介面封裝了各種可能的資源型別,也就是對使用者來說遮蔽了檔案型別的不同。對資源的提供者來說,如何把資源包裝起來交給其他人用這也是一個問題,我們看到 Resource 介面繼承了 InputStreamSource 介面,這個介面中有個 getInputStream 方法,返回的是 InputStream 類。這樣所有的資源都被可以通過 InputStream 這個類來獲取,所以也遮蔽了資源的提供者。另外還有一個問題就是載入資源的問題,也就是資源的載入者要統一,從上圖中可以看出這個任務是由 ResourceLoader 介面完成,他遮蔽了所有的資源載入者的差異,只需要實現這個介面就可以載入所有的資源,他的預設實現是 DefaultResourceLoader

那麼, Context 和 Resource 是如何建立關係的?

從上圖可以看出,Context 是把資源的載入、解析和描述工作委託給了 ResourcePatternResolver 類來完成,他相當於一個接頭人,他把資源的載入、解析和資源的定義整合在一起便於其他元件使用。Core 元件中還有很多類似的方式。

小結

該文章算是以前學習Spring時候的一些筆記整理。如果有任何錯誤或者不解的地方,請留言給我。這是一個令我們可以一起學習進步的機會。

相關文章