Spring核心

Spring的核心

爲了下降 Java 開發的複雜性,Spring 採起了如下 4 種關鍵策略:spring

  • 基於 POJO 的輕量級和最小侵入性編程;
  • 經過依賴注入和麪向接口實現鬆耦合;
  • 基於切面和慣例進行聲明式編程;
  • 經過切面和模板減小樣板式代碼。
激發 POJO 的潛能

這是一個簡單普通的 Java 類 —— POJO。沒有任何地方代表它是一個 Spring 組件。Spring 的非侵入編程模型意味着這個類在 Spring 應用和非 Spring 應用中均可以發揮一樣的做用。express

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

Spring 賦予 POJO 魔力的方式之一就是經過 DI 來裝配它們。讓咱們看看 DI 是如何幫助應用對象彼此之間保持鬆散耦合的。編程

依賴注入(DI)

BraveKnight 足夠靈活能夠接受任何賦予他的探險任務:安全

public class BraveKnight implements Knight {  
    private Quest quest;  
    public BraveKnight(Quest quest){ //構造器參數注入
        this.quest = quest;  
    }  
    public void embarkOnQuest(){  
        quest.embark();  
    }  
}

這裏的要點是 BraveKnight 沒有與任何特定的 Quest 實現發生耦合。對它來講,被要求挑戰的探險任務只要實現了 Quest 接口,那麼具體是哪一種類型的探險就可有可無了。這就是 DI 所帶來的最大收益 —— 鬆耦合。若是一個對象只經過接口(而不是具體實現或初始化過程)來代表依賴關係,那麼這種依賴就可以在對象自己絕不知情的狀況下,用不一樣的具體實現進行替換。模塊化

SlayDragonQuest 是要注入到 BraveKnight 中的 Quest 實現:函數

public class SlayDragonQuest implements Quest {  
  
    private PrintStream stream;  
  
    public SlayDragonQuest(PrintStream stream){  
        this.stream = stream;  
    }  
  
    public void embark() {  
        stream.println("Embarking on quest to slay the dragon!");  
    }  
}

這裏最大的問題在於,咱們該如何將 SlayDragonQuest 交給 BraveKnight 呢?又如何將 PrintStream 交給 SlayDragonQuest 呢?
建立應用組件之間協做的行爲一般稱爲裝配(wiring)。this

Spring 提供了基於 Java 的配置,可做爲 XML 的替代方案:編碼

@Configuration  //至關於spring的配置文件XML
public class KnightConfig {  

  @Bean  //聲明爲 Spring 中的 bean,bean 的各類名稱……雖然 Spring 用 bean 或者 JavaBean 來表示應用組件,但並不意味着 Spring 組件必需要遵循 JavaBean 規範。一個 Spring 組件能夠是任何形式的 POJO。
  public Knight knight(){  
        return new BraveKnight(quest());  
    }  
  
  @Bean  
  public Quest quest() {  
        return new SlayDragonQuest(System.out);  
    }  
  
}

儘管 BraveKnight 依賴於 Quest,可是它並不知道傳遞給它的是什麼類型的 Quest,也不知道這個 Quest 來自哪裏。與之相似,SlayDragonQuest 依賴於 PrintStream,可是在編碼時它並不須要知道這個 PrintStream 是什麼樣子的。只有 Spring 經過它的配置,可以瞭解這些組成部分是如何裝配起來的。這樣的話,就能夠在不改變所依賴的類的狀況下,修改依賴關係。spa

啓動程序:日誌

public class KnightMain {  
    public static void main(String[] args) {  
       KnightConfig knightConfig = new KnightConfig();  
       Knight knight = knightConfig.knight();  
       knight.embarkOnQuest();  
    }  
}

獲得 Knight 對象的引用後,只需簡單調用 embarkOnQuest() 方法就能夠執行所賦予的探險任務了。注意這個類徹底不知道咱們的英雄騎士接受哪一種探險任務,並且徹底沒有意識到這是由 BraveKnight 來執行的。只有KnightConfig知道哪一個騎士執行哪一種探險任務。

應用切面

DI 可以讓相互協做的軟件組件保持鬆散耦合,而面向切面編程(aspect-oriented programming,AOP)容許你把遍及應用各處的功能分離出來造成可重用的組件。

圖 1.2 展現了這種複雜性。左邊的業務對象與系統級服務結合得過於緊密。每一個對象不但要知道它須要記日誌、進行安全控制和參與事務,還要親自執行這些服務。
image.png

AOP 可以使這些服務模塊化,並以聲明的方式將它們應用到它們須要影響的組件中去。

image.png

好比看看《Spring in Action》書中的一個例子:使用騎士的例子,這裏咱們爲他添加一個切面,假設咱們須要使用吟遊詩人這個服務類來記載騎士的全部事蹟。

//添加吟遊詩人這個服務類
public class Minstrel {  
    private PrintStream stream;  
  
    public Minstrel(PrintStream stream) {  
        this.stream = stream;  
    }  
  
    public void singBeforeQuest() {  
        stream.println("Fa la la, the knight is so brave!");  
    }  
  
    public void singAfterQuest() {  
        stream.println("Tee hee hee, the brave knight " +  
                "did embark on a quest!");  
    }  
}

咱們能夠經過DI構造函數來注入這個類。

public class BraveKnight implements Knight {  
  
    private Quest quest;  
    private Minstrel minstrel;  
  
    public BraveKnight(Quest quest, Minstrel minstrel) {  
        this.quest = quest;  
        this.minstrel = minstrel;  
    }  
  
    public void embarkOnQuest() throws QuestException {  
        minstrel.singBeforeQuest();  
        quest.embark();  
        minstrl.singAfterQuest();  
    }  
  
}

可是管理他的吟遊詩人並非騎士職責範圍內的工做。畢竟,用詩歌記載騎士的探險事蹟,這是吟遊詩人的職責。爲何騎士還須要提醒吟遊詩人去作他分內的事情呢?

此外,由於騎士須要知道吟遊詩人,因此就必須把吟遊詩人注入到 BarveKnight 類中。這不只使 BraveKnight 的代碼複雜化了,並且還讓我疑惑是否還須要一個不須要吟遊詩人的騎士呢?若是 Minstrel 爲 null 會發生什麼呢?我是否應該引入一個空值校驗邏輯來覆蓋該場景?

簡單的 BraveKnight 類開始變得複雜,若是你還須要應對沒有吟遊詩人時的場景,那代碼會變得更復雜。但利用 AOP,你能夠聲明吟遊詩人必須歌頌騎士的探險事蹟,而騎士自己並不用直接訪問 Minstrel 的方法。

<bean id="minstrel" class="sia.knights.Minstrel">  
    <constructor-arg value="#{T(System).out}" />  
</bean>  
  
<aop:config>  
    <aop:aspect ref="minstrel">  
        <aop:pointcut id="embark"  
  expression="execution(**.embarkOnQuest(..))"/>  
  
        <aop:before pointcut-ref="embark"  
  method="singBeforeQuest"/>  
  
        <aop:after pointcut-ref="embark"  
  method="singAfterQuest"/>  
    </aop:aspect>  
</aop:config>

這裏使用了 Spring 的 aop 配置命名空間把 Minstrel bean 聲明爲一個切面。首先,須要把 Minstrel 聲明爲一個 bean,而後在元素中引用該 bean。爲了進一步定義切面,聲明 (使用)在 embarkOnQuest() 方法執行前調用 Minstrel 的 singBeforeQuest() 方法。這種方式被稱爲前置通知(before advice)。同時聲明(使用)在 embarkOnQuest() 方法執行後調用 singAfterQuest() 方 法。這種方式被稱爲後置通知(after advice)。

這就是咱們須要作的全部的事情!經過少許的 XML 配置,就能夠把 Minstrel 聲明爲一個 Spring 切面

相關文章
相關標籤/搜索