WHY
在誕生之初,創建Spring的主要目的是用來替代更加重量級的企業級Java技術,尤其是EJB。相對於EJB來說,Spring提供了更加輕量級和簡單的編程模型。
WHAT
Spring是一個開源框架,最早由RodJohnson創建,Spring是為了解決企業級應用開發的複雜性而創建的,使用Spring可以讓簡單的JavaBean實現之前只有EJB才能完成的事情。 Spring不僅僅限於服務端的開發,任何Java應用都能在簡單性、可測試性和松耦合等方面從Spring中獲益。
如今Spring在移動開發、社交API集成、NoSQL數據庫、雲計算及大數據方面都在涉足。隨著時間的推移EJB也採用了依賴注入(DependencyInjection,DI)和麵向切面編程(Aspect-OrientedProgramming,AOP)的理念。總之,Spring最根本的使命就是簡化Java開發
HOW
為了降低Java開發的複雜性,Spring採取了4鐘關鍵策略
基於POJO的輕量級和最小侵入性編程通過依賴注入和麵向接口實現松耦合
基於切面和慣例進行聲明式編程通過切面和模板減少樣式代碼
POJO潛能
很多框架通過強迫應用繼承它們的類或實現它們的接口從而導致應用與框架綁定在一起,這就是侵入性編程,導致無法復用代碼塊。 Spring竭力避免因自身的API而弄亂你的應用代碼,在基於Spring構建的應用程序中,他的類通常沒有任何痕跡表明你使用了Spring
public class HelloWorldBean { public String sayHello(){ return "Hello World"; }}以上的實例代碼表示一個很簡單普通的Java類(POJO),你看不出來他是一個Spring組件,Spring的非侵入式編程體現在這個類在Spring應用和非Spring應用中都可以發揮作用。僅僅這麼一段代碼並沒有能實際體現Spring功能,還需要後面的知識。
依賴注入(將自身依賴的類註入的自身)
依賴注入這個此在Spring中並不是這麼高大上,儘管現在已經演變成一項複雜的編程技巧或者設計模式理念。在Spring中可以這麼理解,注入依賴。一個具有實際意義的應用都需要多個類進行相互協作來完成特定的業務邏輯。傳統的做法是每個對象負責管理與自己有關的對象(這個有關的對象就是Spring中表述的所依賴的對象)的引用,不過這將會導致高度耦合和代碼難以測試
考慮如下代碼
/**這個拗口的類名是作者為了擬合一個模擬的場景特地取名* 這個類表示營救少女的騎士* Created by Wung on 2016/8/25. */public class DamselRescuingKnight implements Knight{private RescueDamselQuest quest;/** * 在它的構造函數中自行創建了RescueDamselQuest * 這使得DamselRescuingKnight和在它的構造函數中自行創建了RescueDamselQuest耦合在了一起*/public DamselRescuingKnight(){this.quest = new RescueDamselQuest();}public void embarkOnQuest(){quest.embark();}}耦合具有兩面性,一方面,緊密耦合的代碼難以測試難以復用難以理解,並且會出現"打地鼠"式的BUG。另一方面,一定程度的耦合又是必要的,不同的類必須以適當的方式進行交互。
問題出現了,那麼Spring是如何解決的呢
在Spring中,通過依賴注入(DI),對象之間的依賴關係由系統中負責協調各對象的第三方組件在創建對象的時候進行設定。也就是說對像只需要管理自己內部的屬性而無需自行創建或者管理它們的依賴關係,依賴關係會被自動注入到需要它們的對象當中去。
/** * 這個騎士可以執行各種冒險任務而不在僅僅是之前那個營救少女的任務* Created by Wung on 2016/8/25. */public class BraveKnight implements Knight{private Quest quest;/** * BraveKnight沒有自行創建冒險類型,而是在構造的時候把冒險任務作為參數傳入* 這是依賴注入的方式之一:構造器注入*/public BraveKnight(Quest quest){this.quest = quest;}public void embarkOnQuest(){quest.embark();}} BraveKnight沒有與任何特定的Quest實現發生耦合,只要某個任務實現了Quest接口,那麼具體是哪種類型的冒險就無所謂了,這就打到了DI的目的――松耦合
如果一個對像只通過接口來表明依賴關係,那麼這種依賴關係就能夠在對象本身毫不知情的情況下用不同的具體實現來進行替代。
那麼現在來進行實際意義的注入
對於上面那塊代碼我們來將一個具有具體實現的冒險任務注入到勇敢騎士中去
/**屠龍的冒險任務(這個作者好中二阿) * Created by Wung on 2016/8/25. */public class SlayDragonQuest implements Quest{private PrintStream stream;public SlayDragonQuest(PrintStream stream){this.stream = stream;}public void embark() {stream.print("slay the dragon");}}那麼現在問題來了,在SlayDragonQuest中如何注入它所依賴的PrintStream對象,在BraveKnight中如何注入它所依賴的Quest對象。前面提到的Spring會將這些依賴進行集中管理,創建應用組件之間協作的行為通常稱為裝配(wiring)也就是注入。 Spring提供了多種裝配的方式在後面會更加詳細的介紹,此處簡單介紹基於XML的裝配和基於Java註解的裝配
<!-- 這就是一個裝配的過程,將SlayDragonQuest聲明為一個bean,取名為"quest" 因為是構造器注入所以使用constructor-arg屬性value表示注入的值那麼這個過程解決了之前的問題,將PrintStream對象注入到SlayDragonQuest對像中--> <bean id="quest"> <constructor-arg value="#{T(System).out}"> </constructor-arg></bean> <!-- 這個過程同上,BraveKnight聲明為一個bean取名為"knight"(不一定使用到這個名字) 然後BraveKnight的構造器參數要求為Quest類型此時傳入了另外一個bean的引用就是上面這個名字"quest"的bean的引用,此時也完成了對BraveKnight的構造器注入--> <bean id="knight"> <constructor-arg ref="quest"> </constructor-arg></bean>Spring提供了基於Java的配置,可作為XML的替代方案
/**基於Java的配置文件實現對象的裝配* Created by Wung on 2016/8/26. */@Configurationpublic class KnightConfig { @Bean public Knight knight(){ return new BraveKnight(quest()); } @Bean public Quest quest(){ return new SlayDragonQuest(System.out); }}效果是相同的,具體的解釋在下一章中詳細描述。現在再來回顧一下,一直說Spring會自動管理對象之間的依賴關係,那麼這種管理者是什麼。答案是Application Context(應用上下文),它是Spring的一種容器,可以裝載bean的定義並將它們組裝起來。 Spring應用上下文全權負責對象的創建和組裝。實際上這個Context有好多中實現他們之間的區別僅僅是加載配置的方式不同,下面來看一種加載配置的方式
public class KnightMain { public static void main(String[] args){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(KnightConfig.class); //從配置文件中就可以獲取到bean的定義了Knight knight = context.getBean(Knight.class); knight.embarkOnQuest(); context.close(); } }應用切面
DI能夠讓相互協作的軟件組件保持松耦合,而面向切面編程允許你把遍布應用各處的功能分離出來形成可重用的組件,更詳細的說,它是促使軟件系統實現關注點奮力的一項技術。什麼是關注點呢,諸如日誌、事務管理、安全管理這樣的系統服務經常需要融入到其他自身俱有業務邏輯的組件中去,那麼這些系統服務通常就被稱為橫切關注點,因為它們會在多個地方被重用,跨越系統的多個組件。簡單來說,就是你把需要重用的經常會服務與各種其他組件的組件抽離出來,但是抽離出來如何使用呢,其實就是在用的時候將方法插入到需要使用的地方。但是根據這個"切面"的術語,應該表述為將重用的組件抽離出來作為一個切面,在需要使用的時候將切面橫切進組件。所以這樣就做到了核心應用不需要知道這些切面的存在,切面也不會將業務邏輯融合在核心應用中。
/**一個歌手類用來歌頌騎士也就是服務於騎士類* Created by Wung on 2016/8/26. */public class Minstrel { private PrintStream stream; public Minstrel(PrintStream stream){ this.stream = stream; } //在冒險之前執行public void singBeforeQuest(){ stream.print("Begin"); } //在冒險之後執行public void singAfterQuest(){ stream.print("End"); } } public class BraveKnight implements Knight{private Quest quest;private Minstrel minstrel;/** * BraveKnight沒有自行創建冒險類型,而是在構造的時候把冒險任務作為參數傳入* 這是依賴注入的方式之一:構造器注入*/// public BraveKnight(Quest quest){// this.quest = quest;// }public BraveKnight(Quest quest, Minstrel minstrel){this.quest = quest;this.minstrel = minstrel;}public void embarkOnQuest(){minstrel.singBeforeQuest();quest.embark();minstrel.singAfterQuest();}}這個時候這個勇敢的騎士開始執行,但是他發現在他的職責中不僅僅是冒險了,現在竟然還要管理這個歌手要為他歌頌,然而這本身並不應該屬於這個類應該管理的。所以應用切面的思想,我們需要將這個歌手的歌頌行為抽離出來成為一個切面,在騎士冒險之前這個切面將會橫切進去執行singBeforeQuest方法在冒險之後執行singAfterQuest方法。那麼這樣是不是就實現了騎士中不需要歌頌的代碼,歌手也不存在與騎士對像中,他將不僅僅讚頌騎士,也可以讚頌任何人只要別人使用這個切面切入即可
<!-- 意思就是將上述id為minstrel的bean配置為切面實際上就是把歌手配置為切面--> aspect ref="minstrel"> <!-- 定義切入點,也就是在哪裡會使用切面expression="execution(* *.embarkOnQuest(..)) 是一種AspectJ的切點表達式語言後面會深入此處的意思是會在embarkOnQuest()方法處切入下面那個分別為前置通知和後置通知在切入點之前和之後執行--> </aop:after></aop:before></aop:pointcut></aop:</aop:config>
現在的情況是Minstrel仍然是獨立的一個POJO,Spring的上下文已經將他變成了一個切面了。最重要的是此時騎士完全不知道這個切面的存在,這只是一個小小的栗子,實際上可以做很多重要的事情。
使用模板消除樣式代碼
有這樣一種情況,當我們使用JDBC訪問數據庫查詢數據的時候,完整的流程需要建立連接、創建語句對象、處理結果集、查詢、關閉各種連接,此外還需要各種異常的捕獲,然後對於各種場景的查詢都需要如此費心費力的重複。 JDBC還不僅僅是唯一這樣會有大量的樣式代碼的情況,Spring旨在通過模板封裝來消除樣式代碼,比如Spring的jdbcTemplate。
容納你的Bean
在基於Spring的應用中,你的應用對像生存於Spring容器,容器負責創建對象裝配對象並且管理他們的生命週期。那什麼是Spring的容器呢,容器並不是只有一種,Spring自帶了多個容器實現。分為兩類:bean工廠,是最簡單的容器提供基本的DI支持。應用上下文比較高級提供應用框架級別的服務。大多數情況下應用上下文更加受歡迎。
應用上下文也分為多種,顯著的區別的是加載配置的方式不同
Spring的各種功能
總結
Spring是一個可以簡化開發的框架技術,核心內容是DI和AOP。
以上就是本文關於閒言碎語-逐步了解Spring的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續參閱本站:
SpringMVC入門實例
Spring中利用配置文件和@value注入屬性值代碼詳解
Spring集成Redis詳解代碼示例
如有不足之處,歡迎留言指出。