1. 程式人生 > 實用技巧 >Spring概述 -- 《Spring In Action》

Spring概述 -- 《Spring In Action》

Spring 根本使命: 簡化Java開發替換重量級的JavaEE技術(eg: EJB)

簡化Java開發

  • 為了降低Java開發的複雜性,Spring提供4中策略:
    1. 基於POJO的輕量級和最小侵入式開發;
    2. 通過依賴注入和麵向介面實現鬆耦合;
    3. 基於切面和慣例進行宣告式程式設計
    4. 通過切面和樣板減少樣板式程式碼

Spring使用Java Bean或Bean來表示應用元件,一個Spring元件可以是任何形式的POJO(Plain Old Java Object)

依賴注入

任何一個有實際意義的App都是由兩個或更多的類組成,這些類之間相互協作來完成特定的業務邏輯。每個物件負責管理與自己相互協作的物件引用,這將導致高度耦合難以測試的程式碼~
建立應用元件之間協作的行為稱為裝配(wiring)

第一種裝配方式——構造器注入

class 鴨叫行為{
    public void 鴨叫(){
        System.out.println("嘎 嘎 嘎 ...");
    }
}

interface 鴨子{

}

class 紅頭鴨 implements 鴨子{

    private 鴨叫行為 鴨叫行為;

    // 與鴨叫行為緊密耦合,難以複用、擴充套件以及測試
    public 紅頭鴨(){
        this.鴨叫行為 = new 鴨叫行為();
    }
    
    public void 執行鴨叫行為(){
        鴨叫行為.鴨叫();
    }
}

在紅頭鴨的建構函式中自定建立鴨叫行為,使得紅頭鴨與鴨叫行為緊密耦合在一起難以擴充套件

耦合具有兩面性(two-headed beast)
壞處:緊密耦合的程式碼難以測試、難以複用、難以理解,會出現“打地鼠”一樣的bug
好處:一定程度的耦合是必須的-完全沒有耦合的程式碼什麼也做不了,為了完成有實際意義的功能,不同類必須以適當的方式進行互動。

⭐️ DI(Dependency Injection) : 通過DI,物件的依賴關係將由系統中負責協調各物件的第三方元件在建立物件的時候進行設定。物件無需自行建立或管理他們的依賴關係。

DI會將依賴關係自動交給目標物件,而不是讓物件自己去獲取依賴。

interface 鴨叫行為{
    public void 鴨叫();
}

interface 鴨子{

}

class 紅頭鴨 implements 鴨子{

    private 鴨叫行為 鴨叫行為;

    // 鴨叫行為注入進來(此種注入是DI注入方式之一:構造器注入)
    public 紅頭鴨(鴨叫行為 _鴨叫行為){
        this.鴨叫行為 = _鴨叫行為;
    }

    public void 執行鴨叫行為(){
        鴨叫行為.鴨叫();
    }
}

此時鴨叫行為是一種介面,紅頭鴨沒有自行建立物件之間關係而是在構造的時候將物件關係構建起來,傳入的鴨叫行為是一種介面是所有鴨叫行為實現類都必須實現的介面,這樣鴨叫行為不單單是一種行為而是多種實現。具體型別將不在單一,這就是DI帶來最大好處 -- 鬆耦合

如果一個物件只通過介面(而不是具體實現或初始化過程)來表明依賴關係,這種依賴能在物件本身不知情的情況下,用不同的具體實現進行替換。

第二種裝配方式——XML注入

  • Java核心邏輯
interface 鴨叫行為{
    public void 鴨叫();
}

class 吱吱吱叫 implements 鴨叫行為{
    
    private PrintStream printStream;
    
    public 吱吱吱叫(PrintStream printStream){
        this.printStream = printStream;
    }
    
    @Override
    public void 鴨叫() {
        printStream.println("吱吱吱...");
    }
}
  • 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="吱吱吱" class="ink.openmind.space01.吱吱吱叫">
        <constructor-arg value="#{T(System).out}" />
    </bean>
</beans>

第三種裝配方式——基於Java配置注入(註解注入)

interface Knight{
    
}

interface Quest{
    
}

class BraveKnight implements Knight{
    private Quest quest;
    public BraveKnight(Quest quest){
        this.quest = quest;
    }
}

class SlayDragonQuest implements Quest{
    private PrintStream printStream;
    public SlayDragonQuest(PrintStream printStream){
        this.printStream = printStream;
    }
}

@Configuration
class KnightConfig{
    @Bean
    public Knight knight(){
        return new BraveKnight(quest());
    }
    
    @Bean
    public Quest quest(){
        return new SlayDragonQuest(System.out);
    }
}

BraveKnight依賴於Quest但不知道傳遞給它的是什麼型別的Quest,也不知道Quest來自哪裡。SlayDragonQuest依賴於PrintStream但不知道這個PrintStream是什麼樣子,只有Spring通過了解這些組成部分是如何裝配起來的

Spring AOP 實現應用切面(公共服務如何能夠實現在保持自身內聚性和不侵入核心業務邏輯的前提下相互協作實現業務功能)

DI能夠讓相互協作的軟體元件保持鬆散耦合,而面向切面程式設計(Aspect-oriented Programming,AOP)允許把遍佈應用各處的功能分離出來形成可重用的元件。
面向切面程式設計促使軟體系統實現關注點分離的一項技術。

系統由許多不同的元件組成,每一個元件負責一塊特定功能。除了實現自身核心的功能之外,這些元件還承擔額外的職責例如日誌、事務管理和和安全等系統服務經常需要融合到自身具體核心業務邏輯的元件中,這些公共系統服務稱之為橫切關注點,因為他們橫跨系統的多個元件。

利用AOP,系統範圍內的關注點覆蓋在他們所影響元件之上

  • 定義切點和切面類
class 公共服務{

    public void 執行日誌(){
        // 內部邏輯
    }
    public void 結束日誌(){
        // 內部邏輯
    }
}

class 支付系統{
    // 切點
    public void 支付訂單(){

    }
}
  • 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" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="支付系統" class="ink.openmind.space01.支付系統"/>
    <bean id="公共服務" class="ink.openmind.space01.公共服務"/>

    <aop:config>
        <!-- 宣告切面 -->
        <aop:aspect ref="公共服務">
            <!--宣告切點-->
            <aop:pointcut id="支付系統切點" expression="execution(* *.支付訂單(..))"/>


        <!--宣告前置通知-->
        <aop:before pointcut-ref="支付系統切點" method="執行日誌"/>

        <!--聲明後置通知-->
        <aop:after pointcut-ref="支付系統切點" method="結束日誌"/>
        </aop:aspect>
    </aop:config>
</beans>

Spring的aop配置名稱空間將公共服務bean宣告一個切面。
aop:aspect宣告切面bean
aop:pointcut宣告切點
aop:before在切點之前執行前置通知(before advice)
aop:after在切點之後執行後置通知(after advice)
pointcut-ref: 引用名字為“支付系統切點"的切入點由pointcut定義

Spring 容器(存放和管理bean的區域)

基於Spring開發的應用,應用物件生存與Spring Container中,Spring容器負責建立物件,裝配物件以及配置物件並管理其整個生命週期(new -> finalize())。

Spring容器使用DI管理構成應用的元件,建立相互協作之間的關聯。
Spring存在多個容器可歸為兩種型別

  1. bean工廠(org.springframework.beans.factory.beanFactory)介面定義,提供簡單的DI支援。
  2. ApplicationContext應用上下文(org.springframeowrk.context.ApplicationContext)介面定義。

使用ApplicationContext應用上下文

  • AnnotationConfigApplicationContext: 從一個或多個基於Java的配置類載入Spring應用上下文
      ApplicationContext context = new AnnotationConfigApplicationContext(com.springinaction.test.config.BeanConfig.class)
  • AnnotationConfigWebApplicationContext: 從一個或多個基於Java的配置類載入Spring Web應用上下文
  • ClassPathXmlApplicationContext: 從類路徑下的一個或多個XML配置檔案中載入上下文定義,把應用上下文的定義檔案作為類資源
      ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  • FileSystemXmlApplicationContext: 從檔案系統下的一個或多個XML配置檔案載入上下文定義
      ApplicationContext context = new FileSystemXmlApplicationContext("c:/applicationContext.xml");
  • XmlWebApplicationContext: 從Web應用下的一個或多個XML中載入上下文定義

Spring系列框架

總結

Spring致力Java EE開發,促進程式碼鬆散耦合。成功的關鍵在於依賴注入和AOP。