文章作者:Tyan
博客:noahsnail.com
2.Spring框架介紹
Spring框架是一個為支持開發Java應用提供全面基礎架構的Java平臺。Spring處理基礎架構,因此你可以集中精力在你有應用上。
Spring使你能創建簡單Java對象(POJO)并能非侵入式的將企業服務應用到簡單Java對象(POJO)上。
作為一個應用開發者,下面是一些你能從Spring平臺受益的例子:
- 在一個數據庫業務中執行一個Java方法而不必處理業務APIs
- 使一個本地的Java方法可以遠程調用而不必處理遠程APIs
- 使一個本地Java方法變為管理操作而不必處理JMX APIs
- 使一個本地Java方法變為消息處理器而不必處理JMS APIs
2.1依賴注入和控制反轉
Java應用——一個不精確的術語,既可以表示受限制的嵌入式應用又可以表示N層服務端的企業級應用——通常由許多對象構成,這些對象協作形成完整的應用程序。因此一個應用程序中的對象是相互***依賴***的。
盡管Java平臺提供了大量的應用開發功能,但是它缺少把這些基本構建模塊組織成一個連貫整體的方法,并把組織基本構建模塊的任務留給了架構師和開發者。雖然你可以使用設計模式例如***工廠模式、抽象工廠模式、生成器模式、裝飾模式、服務定位模式***來創建構成應用的各種類和對象實例,但這些設計模式很簡單:命名的最佳方法、模式的作用描述、應用模式的位置、模式解決的問題等等。模式使最佳實踐形式化了,這意味著你必須在你的應用中***自己實現它***。
Spring框架中的***控制反轉***(IoC)組件通過提供一種形式化方法解決了這個問題,這個形式化方法將不同的組件創建到一個隨時可用的完整的工作應用中。Spring框架將形式化的設計模式編碼成了你可以集成到你自己的應用中的最好對象。許多組織和機構用這種方式應用Spring框架來構建魯棒的、***可維護***的應用。
背景
"問題是什么是控制反轉?" 2004年Martin Fowler在他的網站上提出了這個關于控制反轉(IoC)問題。Fowler建議重新命名這個原理使它更一目了然并且提出了依賴注入。
2.2 模塊
Spring框架包含的功能大約由20個模塊組成。這些模塊按組可分為核心容器、數據訪問/集成,Web,AOP(面向切面編程)、設備、消息和測試,如下圖所示。
圖2.1 Spring框架概述
接下來的章節列出了每個功能可用的模塊、它們的工件名字以及它們包含的主題。工件名字與依賴管理工具中使用的***artifact IDs***有關。
2.2.1 核心容器
核心容器功能包括`spring-core`, `spring-beans`, `spring-context`, `spring-context-support`, and `spring-expression`(Spring表現語言)模塊。
`spring-core`和`spring-beans`模塊提供了框架的基礎結構部分,包含控制反轉(IoC)和依賴注入(DI)功能。`BeanFactory`是工廠模式的高級實現。它去掉了程序單例模式的需求并且允許你從實際的程序邏輯中解耦配置和依賴的指定。
上下文(`spring-context`)模塊建立在由Core模塊和Beans模塊提供的堅實基礎上:它是在類似于JNDI注冊表式的框架風格模式中訪問對象的一種方法。上下文模塊繼承了Beans模塊的功能,并添加了對國際化(例如使用資源捆綁)、事件傳播、資源加載和上下文透明創建(例如通過Servlet容器)的支持。上下文模塊也支持Java EE功能例如EJB,JMX和基本的遠程。`ApplicationContext`接口是上下文模塊的焦點。`spring-context-support`支持將第三方庫集成進Spring應用程序上下文中,特別是緩存(EhCache, JCache)和定時執行(CommonJ, Quartz)。
2.2.2 面向切面編程(AOP)和設備(Instrumentation)
`spring-aop`模塊提供了***AOP*** Alliance-compliant(AOP聯盟)面向切面編程的實現,例如允許你自定義方法攔截器和切入點來清晰的解耦功能實現上應該分開的代碼。使用源碼級的元數據功能,你也可以將行為信息合并到你的代碼中,在某種程度上這類似于.NET的屬性值。
獨立的`spring-aspects`模塊提供了與AspectJ的集成。
`spring-instrument`模塊提供了類設備支持和類加載器的實現,它們可以在某些應用服務器中使用。`spring-instrument-tomcat`模塊包含了Tomcat的Spring設備代理。
2.2.3 消息
Spring 4框架中包含了`spring-messaging`模塊,它對***Spring集成***項目例如`Message`, `MessageChannel`, `MessageHandler`和其它作為消息應用服務基礎的項目進行了重要的抽象。這個模塊也包含了一系列將消息映射到方法上的注解,這個注解與基于編程模型Spring MVC注解類似。
2.2.4 數據訪問/集成
***數據訪問/集成***層包括JDBC,ORM,OXM,JMS和事務模塊。
`spring-jdbc`模塊提供了JDBC抽象層,不需要再編寫單調的JDBC代碼,解析數據庫提供商指定的錯誤編碼。
`spring-tx`模塊為實現指定接口和所有的簡單Java對象(POJOs)的類提供編程式(programmatic)和聲明式(declarative)的業務管理。
`spring-orm`模塊提供流行的對象關系映射APIs的集成層,包括JPA和Hibernate。在使用`spring-orm`模塊時,你可以將Spring的其它功能與這些O/R-mapping框架結合起來使用,例如前面提到的簡單聲明式業務管理的功能。
`spring-oxm`模塊提供對Object/XML映射實現例如JAXB,Castor,JiBx和XStream的抽象層。
`spring-jms`模塊(Java消息服務)包含產生和處理小心的功能。從Spring 4.1框架開始它提供了與`spring-messaging`的集成。
2.2.5 網絡
網絡層包含`spring-web`, `spring-webmvc`和`spring-websocket`模塊。
`spring-web`模塊提供基本的面向網絡集成功能,例如multipart文件上傳功能,使用Servlet監聽器來初始化Ioc容器和面向網絡的應用程序上下文。它也包含了HTTP客戶端和Spring遠程支持中網絡相關的部分。
`spring-webmvc`模塊(也被稱為***Web-Servlet***模塊)包含了Spring的model-view-controller(MVC)和REST Web Services的網絡應用實現。Spring的MVC框架提供了對域模型代碼,web表單,Spring框架其他功能的完全分離。
2.2.6 測試
`spring-test`模塊支持單元測試,Spring組件和JUnit或TestNG的集成測試。它提供了Spring的`ApplicationContexts`加載和這些上下文緩存的一致。它也提供了可以單獨測試代碼的模擬對象。
2.3 使用場景
前面描述的構建模塊使Spring在許多場景中都有一個合理選擇,從運行在資源受限的嵌入式應用到全面成熟的企業級應用都在使用Spring的業務管理功能和網絡框架集成。
圖2.2標準成熟的Spring web應用
Spring的聲明式業務管理功能使web應用全面的事務化,如果你用過EJB容器管理業務的話你會發現它們基本一樣。你所有自定義的業務邏輯都可以用POJOs實現并通過Spring的IoC容器管理。附加業務包括支持郵件發送和驗證,這個是獨立于web層之外的,你可以自由選擇驗證規則執行的位置。Spring對ORM的支持與JPA和Hibernate進行了集成;例如,當你使用Hibernate時,你可以繼續使用你現有的映射文件和標準的Hibernate `SessionFactory`配置。表單控制器被無縫的將web層和領域模型進行了集成,對于你的領域模型來講不再需要`ActionForms`或其它的將HTTP參數轉換成值的類。
圖2.3. 使用第三方web框架的Spring中間層
有時候環境不允許你完全轉成一個不同的框架。Spring框架***不***強迫你都采用它內部的東西;它不是一個***要么全有要么全無***的解決方案。現有的采用Struts,Tapestry,JSF或其它UI框架構建的前端可以與基于Spring的中間層進行集成,這可以讓你使用Spring的業務功能。你只需要簡單的用`ApplicationContext`和`WebApplicationContext`綁定你的業務邏輯然后集成到web層即可。
圖2.4. 遠程應用場景
當你需要通過web服務訪問現有代碼時,你可以使用Spring的`Hessian-`, `Rmi-` 或 `HttpInvokerProxyFactoryBean`類。這能讓遠程訪問現有應用變得很容易。
Figure 2.5. EJBs-包裝現有的POJOs
Spring框架也為企業JavaBeans提供了訪問和抽象層,使你能重用你現有的POJOs,為了可擴展使用可以將它們包裝成無狀態的session beans,自動防故障的web應用可能需要聲明安全。
2.3.1 依賴管理和命名約定
依賴管理和依賴注入是完全不同的兩件事。為了能你的應用中使用Spring的優秀特性(像依賴注入),你需要收集所有必要的庫(jar文件)并在運行時將它們添加到classpath中,有可能在編譯時就需要添加。這些依賴不是要被注入的虛擬組建,而是文件系統中的物理資源(通常情況下)。這些依賴管理的過程包括資源的定位、存儲和添加到classpath中。依賴可以是直接的(例如:我的應用在運行時依賴Spring),或間接的(例如:我的應用依賴`commons-dbcp`,而它依賴`commons-pool`)。間接依賴也被稱為"傳遞式"的,這些依賴也是最難識別和管理的。
如果你想使用Spring,你需要有包含你需要的Spirng功能的jar庫副本。為了使這個更容易,Spring被打包成了一系列盡可能將依賴分離開的模塊,例如你不想寫web應用那你就不需要spring-web模塊。為了在本指南中談及Spring的庫模塊,我們使用了一個簡寫命名約定`spring-*`或`spring-*.jar`,`*`表示模塊的簡寫名字(例如`spring-core`, `spring-webmvc`, `spring-jms`等等)。實際中你使用的jar文件名字通常是模塊名加上版本號(例如`spring-core-5.0.0.BUILD-SNAPSHOT.jar`)。
Spring框架的每次發布都會下面的地方公布artifacts:
Maven Central,Maven查詢的默認倉庫,使用時不需要任何特定的配置。Spring依賴的許多共通庫也可以從Maven Central獲得,Spring社區的很大一部分都在使用Maven進行依賴管理,因此這對他們來說是很方便的。jar包的命名形式是
spring-*-<version>.jar
,Maven GroupId是org.springframework
。-
由Spring掌管的公開Maven庫。除了最終的GA release(公開可獲得的版本)之外,這個倉庫也有開發版本的快照和milestone版本。jar包的命名形式和Maven Central一樣,這是一個可以使用Spring開發版本有用地方,而其它的庫部署在Maven Central。這個庫也包含的捆綁分布的zip文件,這個zip文件中所有的Spring jar包被捆綁到一起很容易下載。
你將在下面找到Spring artifacts列表。想要每個模塊更全面的描述,請看2.2小節。
表 2.1. Spring Framework Artifacts
Spring依賴和依賴Spring
雖然Spring提供集成并支持大范圍內的企業和其它外部工具,但它有意使它的強制性依賴到一個絕對最小化的程度:對于簡單的用例你不應該為了使用Spring而定位和下載(即使是自動的)許多jar庫。對于基本的依賴注入僅有一個強制性的外部依賴,那個依賴是關于日志的(在下面可以看到日志選擇更詳細的描述)。
接下來我們概述配置一個依賴于Spring的應用需要的基本步驟,首先Maven的,其次是Gradle的,最后是Ivy的。在所有的案例中,如果有任何不清楚的地方,請參考你的依賴管理系統的文檔,或者看一些示例代碼——Spring本身構建時使用Gradle來管理依賴,我們例子中大多數是使用Gradle和Maven的。
Maven依賴管理
如果你正在使用Maven來進行依賴管理,那你不必顯式的提供日志依賴。例如,為了創建一個應用上下文,使用依賴注入來配置一個應用,你的Maven依賴看上去是這樣的:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.0.BUILD-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
</dependencies>
就是它。注意如果你不需要編譯Spring APIs,scope可以被聲明成rumtime,這是典型的基本依賴注入的情況。
上面的例子是采用Maven中心倉庫的。為了使用Spring Maven倉庫(例如:使用milestone版本或snapshot版本),你需要在Maven配置中指定倉庫的位置,完整的版本:
<repositories>
<repository>
<id>io.spring.repo.maven.release</id>
<url>http://repo.spring.io/release/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
對于milestone版本:
<repositories>
<repository>
<id>io.spring.repo.maven.milestone</id>
<url>http://repo.spring.io/milestone/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
對于snapshot版本:
<repositories>
<repository>
<id>io.spring.repo.maven.snapshot</id>
<url>http://repo.spring.io/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
Maven "材料清單" 依賴
在使用Maven時,有可能會偶然的將不同版本的Spring JARs混合起來。例如,你可能找到一個第三方庫,或另一個Spring項目,通過傳遞依賴進入了一個更舊的版本。如果你忘了自己顯式的聲明一個直接依賴,會產生各種意想不到的問題。
為了解決這種問題,Maven支持"材料清單"(BOM)依賴的概念。你可以在你的`dependencyManagement`部分導入`spring-framework-bom`來確保所有的Spring依賴(直接和傳遞的)都是同一個版本。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.0.0.BUILD-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
使用BOM的額外好處是當依賴Spring框架的artifacts時你不再需要指定`<version>`屬性:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependencies>
Gradle依賴管理
為了在Gradle構建系統中使用Spring倉庫,在`repositories`部分需要包含合適的URL:
repositories {
mavenCentral()
// and optionally...
maven { url "http://repo.spring.io/release" }
}
當合適的時候你可以修改`repositories`的URL從`/release`到`/milestone`或`/snapshot`。一旦一個倉庫被配置了,你可以用通常的Gradle方式聲明依賴:
dependencies {
compile("org.springframework:spring-context:5.0.0.BUILD-SNAPSHOT")
testCompile("org.springframework:spring-test:5.0.0.BUILD-SNAPSHOT")
}
Ivy依賴管理
如果你更喜歡使用Ivy來管理依賴,這有類似的配置選擇。
為了配置Ivy指定Spring倉庫,添加下面的解析器到你的`ivysettings.xml`:
<resolvers>
<ibiblio name="io.spring.repo.maven.release"
m2compatible="true"
root="http://repo.spring.io/release/"/>
</resolvers>
當合適的時候你可以更改根URL從`repositories`的URL從`/release`到`/milestone`或`/snapshot`。
一旦配置了,你可以通過一般的方式添加依賴。例如(在`ivy.xml`):
<dependency org="org.springframework"
name="spring-core" rev="5.0.0.BUILD-SNAPSHOT" conf="compile->runtime"/>
發行版Zip文件
盡管使用一個支持依賴管理的構建系統是獲得Spring框架的推薦方式,但仍然可以下載發行版的Zip文件。
發行版的zips是被發布到Spring Maven倉庫(這只是為了我們的方便,為了下載它們你不需要Maven或任何其它的構建系統)。
為了下載發行版zip,打開瀏覽器輸入[http://repo.spring.io/release/org/springframework/spring](http://repo.spring.io/release/org/springframework/spring),然后選擇你想要的版本的合適子文件夾。發行版文件以`-dist.zip`結尾,例如spring-framework-{spring-version}-RELEASE-dist.zip。發行版也可以公布milestone版本或snapshots版本。
2.3.2 日志
日志對于Spring來說是一個非常重要的依賴,因為:*a)*它是唯一的強制性外部依賴,*b)*每個人都喜歡從他們使用的工具中看到一些輸出,*c)*Spring集成了許多其它的工具,這些工具也選擇了日志依賴。應用開發者的一個目標就是對于整個應用來講,經常要有一個中心地方來進行日志的統一配置,包括所有的外部組件。比它更困難的可能是有太多的日志框架去選擇。
Spring中的強制日志依賴是Jakarta Commons Logging API (JCL)。我們編譯JCL并使JCL`log`對象對類是可見的,這擴展了Spring框架。所有版本的Spring采用同一個日志庫:移植是容易的,因為即使應用擴展了Spring但保留了向后兼容性,這一點對用戶來說很重要。我們實現這個的方式是讓Spring的模塊之一顯式的依賴`commons-logging`(JCL的標準實現),然后使其它模塊在編譯時依賴這個模塊。例如如果你在使用Maven,想找出依賴于`commons-logging`的依賴在哪,它在Spring中,更確切的說它是在Spring的中心模塊`spring-core`中。
關于`commons-logging`的一件好事是要使你的應用工作你不需要任何其它的東西。它有一個運行時發現算法,這個算法能尋找其它的日志框架在知名的classpath中,并使用一個它認為是合適的(或者你告訴它你想用哪個如果你需要的話)。如果找不到任何別的你可以從JDK中找到一個非常美好漂亮的日志(java.util.logging或縮寫為JUL)。在大多數環境中你可以發現你的Spring應用恰當地運行并輸出日志到控制臺輸出框中,那是很重要的。
不使用Commons Logging
不幸的是, 雖然`commons-logging`的運行時發現算法對于終端用戶是方便的,但它是有問題的。如果我們將時鐘回撥,把Spring作為一個新項目重新開始,將會選擇一個不同的日志依賴。第一個選擇可能是Simple Logging Facade for Java(SLF4J),應用內部使用Spring的人使用的許多其它工具也用了SLF4J。
這兒有兩種方式關掉`commons-logging`:
從
spring-core
模塊排除依賴(因為它是唯一的顯式依賴)commons-logging
的模塊-
依賴于一個特定的
commons-logging
依賴,用一個空jar替換這個依賴(更多細節可以在SLF4J FAQ中找到)。為了排除`commons-logging`,把下面的內容加入到你的`dependencyManagement`部分:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.BUILD-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
現在這個應用可能是壞了的,因為在classpath中沒有JCL API的實現,為了解決這個問題必須提供一個新的實現。在接下來的部分我們將向你展示怎樣提供一個JCL替代實現,使用SLF4J就是一個例子。
使用SLF4J
SLF4J是一個更純凈的依賴并且在運行時比`commons-logging`更有效,因為它使用編譯時綁定來代替運行時查找集成的其它日志框架。這也意味著你必須更清楚你想要運行時發生什么,然后相應的聲明它或配置它。SLF4J提供跟許多常用日志框架的綁定,因此你通常可以選擇一個你正在使用的日志框架,然后綁定到配置和管理上。
SLF4J提供跟許多常用日志框架的綁定,包括JCL,它做的恰恰相反,建立其它日志框架和它自己的紐帶。因此為了在Spring中使用SLF4J,你需要用SLF4J-JCL連接器取替換`commons-logging`依賴。一旦你在Spring內部使用了日志調用,Spring會將日志調用變為調用SLF4J API,如果你應用中其它的庫調用了那個API,你將有一個單獨的地方配置和管理日志。
一個常用的選擇連接Spring和SLF4J,然后提供SLF4J到Log4J的顯式綁定。你需要提供四個依賴(排除現有的`commons-logging`):連接、SLF4J API、到Log4J的綁定、Log4J本身的實現。在Maven中你可能這么做:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.BUILD-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
</dependencies>
這可能看起來為了得到一些日志需要很多依賴。還好它是可選的,比起`commons-logging`的關于類加載器的問題,尤其是你在一個像OSGi平臺那樣嚴格的容器中的時候,它應該更好操作。據說這兒也有一個性能提升,因為綁定是在編譯時而不是在運行時。
在SLF4J用戶中,一個更通用的選擇是直接綁定到[Logback](http://logback.qos.ch/),這樣使用步驟更少且依賴也更少。這去除了外部綁定步驟,因為Logback直接實現了SLF4J,因此你僅需要依賴兩個庫而不是四個(`jcl-over-slf4j`和`logback`)。如果你這樣做的話你可能也需要從其它的外部應用中(不是從Spring)排除slf4j-api依賴,因為你在classpath中僅需要一個版本的API。
使用Log4J
許多人使用Log4j作為配置和管理的日志框架。它有效且完善的,當我們構建和測試Spring時,實際上這就是在運行時我們使用的東西。Spring也提供一些配置和初始化Log4j的工具,因此在某些模塊有可選的Log4j的編譯時依賴。
為了使Log4j能與默認的JCL依賴(`commons-logging`)一起工作,所有你需要做的是把Log4j放到classpath中,并提供一個配置文件(`log4j.properties`或`log4j.xml`在classpath的根目錄)。對于Maven用戶依賴聲明如下:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
</dependencies>
下面是一個log4j.properties輸出日志到控制臺的樣本:
運行時容器使用本地化JCL
許多人在容器中運行他們的Spring應用,容器本身提供了一個JCL實現。IBM Websphere Application Server (WAS) 是原型。這經常會引起問題,不幸的是沒有一勞永逸的解決方案;在大多數環境下簡單的執行`commons-logging`是不夠的。
為了使這個更清楚:報告的問題本質上一般不是關于JCL的,或關于`commons-logging`的:而是他們去綁定`commons-logging`到其它的框架上(通常是Log4j)。這可能會失敗因為`commons-logging`在一些容器的舊版本(1.0)和大多數人使用的現代版本(1.1)中改變了運行時發現方式。Spring不使用JCL API的和任何不常用的部分,因此不會有問題出現,但是一旦Spring或你的應用試圖去輸出日志,你可能發現到Log4j的綁定是不起作用的。
在這種情況下使用WAS最容易做的事是逆轉類加載層(IBM稱為"parent last"),為的是應用能控制依賴,而不是容器。雖然這種選擇并非總是公開的,但在公共領域對于替代方法有許多其它的建議,你的解決這個問題花的時間可能是不同的,這取決于確定的版本和容器集合的特性。
Part II. 核心技術
這部分參考文檔包含了所有完全集成到Spring框架中的那些技術。
在這些中最重要的是Spring框架的控制反轉(IoC)容器。對Spring框架IoC容器的徹底處理是緊隨其后的Spring面向切面編程(AOP)技術的全面覆蓋。Spring框架有它自己的AOP框架,這在概念上很容易理解,在Java企業級開發中成功了解決了AOP需求中80%的關鍵點。
Spring也提供了AspectJ的全面集成(目前是最豐富的-考慮到功能-并且確定在Java企業中是最成熟的AOP實現)。
- Chapter 3, The IoC container
- Chapter 4, Resources
- Chapter 5, Validation, Data Binding, and Type Conversion
- Chapter 6, Spring Expression Language (SpEL)
- Chapter 7, Aspect Oriented Programming with Spring
- Chapter 8, Spring AOP APIs