為了降低Java開發(fā)的復(fù)雜性,Spring采取了以下4種關(guān)鍵策略:
- 基于POJO的輕量級和最小侵入性編程;
- 通過依賴注入和面向接口實現(xiàn)松耦合;
- 基于切面和慣例進行聲明式編程; 通過切面和模板減少樣板式代碼。
依賴注入
任何一個有實際意義的應(yīng)用(肯定比Hello World示例更復(fù)雜)都會由兩個或者更多的類組成,這些 類相互之間進行協(xié)作來完成特定的業(yè)務(wù)邏輯。按照傳統(tǒng)的做法,每個對象負責(zé)管理與自己相互協(xié)作 的對象(即它所依賴的對象)的引用,這將會導(dǎo)致高度耦合和難以測試的代碼。
耦合具有兩面性(two-headed beast)。一方面,緊密耦合的代碼難以測試、難以復(fù)用、難以理 解,并且典型地表現(xiàn)出“打地鼠”式的bug特性(修復(fù)一個bug,將會出現(xiàn)一個或者更多新的bug)。 另一方面,一定程度的耦合又是必須的——完全沒有耦合的代碼什么也做不了。為了完成有實際意 義的功能,不同的類必須以適當?shù)姆绞竭M行交互。總而言之,耦合是必須的,但應(yīng)當被小心謹慎地 管理。
通過DI,對象的依賴關(guān)系將由系統(tǒng)中負責(zé)協(xié)調(diào)各對象的第三方組件在創(chuàng)建對象的時候進行設(shè)定。對 象無需自行創(chuàng)建或管理它們的依賴關(guān)系,如圖所示,依賴關(guān)系將被自動注入到需要它們的對象 當中去。

Spring通過應(yīng)用上下文(Application Context)裝載bean的定義并把它們組裝起來。Spring應(yīng)用上 下文全權(quán)負責(zé)對象的創(chuàng)建和組裝。Spring自帶了多種應(yīng)用上下文的實現(xiàn),它們之間主要的區(qū)別僅僅在于如何加載配置。
當描述bean如何進行裝配時,Spring具有非常大的靈活性,它提供了三種主要的裝配機制:
- 在XML中進行顯式配置。
- 在Java中進行顯式配置。
- 隱式的bean發(fā)現(xiàn)機制和自動裝配。
Spring定義了多種作用域,可以基于這些作用域創(chuàng)建bean,包括:
- 單例(Singleton):在整個應(yīng)用中,只創(chuàng)建bean的一個實例。
- 原型(Prototype):每次注入或者通過Spring應(yīng)用上下文獲取的時候,都會創(chuàng)建一個新的 bean實例。
- 會話(Session):在Web應(yīng)用中,為每個會話創(chuàng)建一個bean實例。
- 請求(Rquest):在Web應(yīng)用中,為每個請求創(chuàng)建一個bean實例。
應(yīng)用切面
DI能夠讓相互協(xié)作的軟件組件保持松散耦合,而面向切面編程(aspect-oriented programming,AOP)允許你把遍布應(yīng)用各處的功能分離出來形成可重用的組件。
面向切面編程往往被定義為促使軟件系統(tǒng)實現(xiàn)關(guān)注點的分離一項技術(shù)。系統(tǒng)由許多不同的組件組 成,每一個組件各負責(zé)一塊特定功能。除了實現(xiàn)自身核心的功能之外,這些組件還經(jīng)常承擔著額外 的職責(zé)。諸如日志、事務(wù)管理和安全這樣的系統(tǒng)服務(wù)經(jīng)常融入到自身具有核心業(yè)務(wù)邏輯的組件中 去,這些系統(tǒng)服務(wù)通常被稱為橫切關(guān)注點,因為它們會跨越系統(tǒng)的多個組件。
如果將這些關(guān)注點分散到多個組件中去,你的代碼將會帶來雙重的復(fù)雜性。
- 實現(xiàn)系統(tǒng)關(guān)注點功能的代碼將會重復(fù)出現(xiàn)在多個組件中。這意味著如果你要改變這些關(guān)注點 的邏輯,必須修改各個模塊中的相關(guān)實現(xiàn)。即使你把這些關(guān)注點抽象為一個獨立的模塊,其他模塊只是調(diào)用它的方法,但方法的調(diào)用還是會重復(fù)出現(xiàn)在各個模塊中。
- 組件會因為那些與自身核心業(yè)務(wù)無關(guān)的代碼而變得混亂。一個向地址簿增加地址條目的方法 應(yīng)該只關(guān)注如何添加地址,而不應(yīng)該關(guān)注它是不是安全的或者是否需要支持事務(wù)。
AOP能夠使這些服務(wù)模塊化,并以聲明的方式將它們應(yīng)用到它們需要影響的組件中去。所造成的 結(jié)果就是這些組件會具有更高的內(nèi)聚性并且會更加關(guān)注自身的業(yè)務(wù),完全不需要了解涉及系統(tǒng)服務(wù) 所帶來復(fù)雜性。總之,AOP能夠確保POJO的簡單性。
我們可以把切面想象為覆蓋在很多組件之上的一個外殼。應(yīng)用是由那些實現(xiàn)各自業(yè) 務(wù)功能的模塊組成的。借助AOP,可以使用各種功能層去包裹核心業(yè)務(wù)層。這些層以聲明的方式 靈活地應(yīng)用到系統(tǒng)中,你的核心應(yīng)用甚至根本不知道它們的存在。這是一個非常強大的理念,可以 將安全、事務(wù)和日志關(guān)注點與核心業(yè)務(wù)邏輯相分離。
使用應(yīng)用上下文
Spring自帶了多種類型的應(yīng)用上下文。下面羅列的幾個是你最有可能遇到的。
- AnnotationConfigApplicationContext:從一個或多個基于Java的配置類中加載 Spring應(yīng)用上下文。
- AnnotationConfigWebApplicationContext:從一個或多個基于Java的配置類中加 載Spring Web應(yīng)用上下文。
- ClassPathXmlApplicationContext:從類路徑下的一個或多個XML配置文件中加載 上下文定義,把應(yīng)用上下文的定義文件作為類資源。
- FileSystemXmlapplicationcontext:從文件系統(tǒng)下的一個或多個XML配置文件中加 載上下文定義。
- XmlWebApplicationContext:從Web應(yīng)用下的一個或多個XML配置文件中加載上下文定義。
SpringMVC

在請求離開瀏覽器時 ,會帶有用戶所請求內(nèi)容的信息,至少會包含請求的URL。但是還可能帶有 其他的信息,例如用戶提交的表單信息。 請求旅程的第一站是Spring的DispatcherServlet。與大多數(shù)基于Java的Web框架一 樣,Spring MVC所有的請求都會通過一個前端控制器(front controller)Servlet。前端控制器是常 用的Web應(yīng)用程序模式,在這里一個單實例的Servlet將請求委托給應(yīng)用程序的其他組件來執(zhí)行實 際的處理。在Spring MVC中,DispatcherServlet就是前端控制器。 DispatcherServlet的任務(wù)是將請求發(fā)送給Spring MVC控制器(controller)??刂破魇且粋€用 于處理請求的Spring組件。在典型的應(yīng)用程序中可能會有多個控制器,DispatcherServlet需 要知道應(yīng)該將請求發(fā)送給哪個控制器。所以DispatcherServlet以會查詢一個或多個處理器映 射(handler mapping) 來確定請求的下一站在哪里。處理器映射會根據(jù)請求所攜帶的URL信息來 進行決策。 一旦選擇了合適的控制器,DispatcherServlet會將請求發(fā)送給選中的控制器 。到了控制 器,請求會卸下其負載(用戶提交的信息)并耐心等待控制器處理這些信息。(實際上,設(shè)計良好 的控制器本身只處理很少甚至不處理工作,而是將業(yè)務(wù)邏輯委托給一個或多個服務(wù)對象進行處理。)
控制器在完成邏輯處理后,通常會產(chǎn)生一些信息,這些信息需要返回給用戶并在瀏覽器上顯示。這 些信息被稱為模型(model)。不過僅僅給用戶返回原始的信息是不夠的——這些信息需要以用戶 友好的方式進行格式化,一般會是HTML。所以,信息需要發(fā)送給一個視圖(view),通常會是 JSP。
控制器所做的最后一件事就是將模型數(shù)據(jù)打包,并且標示出用于渲染輸出的視圖名。它接下來會將 請求連同模型和視圖名發(fā)送回DispatcherServlet 。 這樣,控制器就不會與特定的視圖相耦合,傳遞給DispatcherServlet的視圖名并不直接表示 某個特定的JSP。實際上,它甚至并不能確定視圖就是JSP。相反,它僅僅傳遞了一個邏輯名稱, 這個名字將會用來查找產(chǎn)生結(jié)果的真正視圖。DispatcherServlet將會使用視圖解析器(view resolver) 來將邏輯視圖名匹配為一個特定的視圖實現(xiàn),它可能是也可能不是JSP。 既然DispatcherServlet已經(jīng)知道由哪個視圖渲染結(jié)果,那請求的任務(wù)基本上也就完成了。它 的最后一站是視圖的實現(xiàn)(可能是JSP) ,在這里它交付模型數(shù)據(jù)。請求的任務(wù)就完成了。視圖 將使用模型數(shù)據(jù)渲染輸出,這個輸出會通過響應(yīng)對象傳遞給客戶端(不會像聽上去那樣硬編碼)。
可以看到,請求要經(jīng)過很多的步驟,最終才能形成返回給客戶端的響應(yīng)。