依賴注入
按照傳統(tǒng)的做法,每個對象負(fù)責(zé)管理與自己相互協(xié)作的對象的引用,這將會導(dǎo)致高度耦合和難以測試的代碼症概。例如:
package sia.knights;
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest;
public DamselRescuingKnight() {
this.quest = new RescueDamselQuest();
}
public void embarkOnQuest() {
quest.embark();
}
}
DamselRescuingKnight在其構(gòu)造函數(shù)中自行創(chuàng)建了Rescue DamselQuest咒吐。這使DamselRescuingKnight和Rescue DamselQuest緊密地耦合到了一起。
通過DI娜谊,對象的依賴關(guān)系將由系統(tǒng)中負(fù)責(zé)協(xié)調(diào)各對象的第三方組件在創(chuàng)建對象的時(shí)候進(jìn)行設(shè)定确买。對象無需自行創(chuàng)建或管理它們的依賴關(guān)系。這樣代碼將會變得更加靈活:
package sia.knights;
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest;
}
public void embarkOnQuest() {
quest.embark();
}
}
BraveKnight足夠靈活可以接受任何賦予他的Quest纱皆⊥逯海可以看到不同于之前的DamselRescuingKnight,BraveKnight沒有自行創(chuàng)建探險(xiǎn)任務(wù)派草,而是在構(gòu)造的時(shí)候把探險(xiǎn)任務(wù)作為構(gòu)造器參數(shù)傳入搀缠。這是依賴注入的方式之一,即構(gòu)造器注入近迁。
由于傳入的探險(xiǎn)類型是Quest艺普,BraveKnight沒有與任何特定的Quest實(shí)現(xiàn)相耦合。而Quest是所有探險(xiǎn)任務(wù)都必須實(shí)現(xiàn)的一個接口鉴竭。所以BraveKnight能夠響應(yīng)任意的Quest實(shí)現(xiàn)類歧譬。這就是DI帶來的收益——松耦合。如果一個應(yīng)用只通過接口而不是具體實(shí)現(xiàn)或初始化過程來表明依賴關(guān)系拓瞪,那么這種依賴就能夠在對象本身毫不知情的情況下缴罗,用不同的具體實(shí)現(xiàn)進(jìn)行替換。
在測試的時(shí)候祭埂,對于緊耦合的DamselRescuingKnight無法進(jìn)行充分測試面氓,但是BraveKnight卻可以,因?yàn)橹恍枰o它一個Quest的mock實(shí)現(xiàn)即可蛆橡。
package sia.knights;
import static org.mockito.Mockito.*;
import org.junit.Test;
import sia.knights.BraveKnight;
import sia.knights.Quest;
public class BraveKnightTest {
@Test
public void knightShouldEmbarkOnQuest() {
Quest mockQuest = mock(Quest.class);
BraveKnight knight = new BraveKnight(mockQuest);
knight.embarkOnQuest();
verify(mockQuest, times(1)).embark();
}
}
上面的代碼使用Mock框架Mockito創(chuàng)建了一個Quest的mock實(shí)現(xiàn)舌界。通過這個mock對象,就可以創(chuàng)建一個新的BraveKnight實(shí)例泰演,并通過構(gòu)造器注入這個mock Quest呻拌。通過使用verify方法來驗(yàn)證mock實(shí)現(xiàn)的embark()方法被調(diào)用了多少次。
將Quest注入到Knight中
BraveKnight類可以接受傳遞的任意一種Quest實(shí)現(xiàn)睦焕。問題是怎樣把特定的Query實(shí)現(xiàn)傳給它藐握?例如下面的SlayDragonQuest:
package sia.knights;
import java.io.PrintStream;
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!");
}
}
上面代碼沒有使用System.out.println()靴拱,而是在構(gòu)造方法中請求一個更為通用的PrintStream。
現(xiàn)在最大的問題在于如何將SlayDragonQuest傳遞給BraveKnight猾普,又如何把PrintStream交給SlayDragonQuest袜炕?
創(chuàng)建應(yīng)用組件之間的協(xié)作行為通常被稱為裝配(wiring)。Spring提供了多種裝配bean的方式, 常用的裝配方式是采用XML。
下面是一個簡單的Spring配置文件 knights.xml鲜屏,該配置文件將BraveKnight瞬铸、SlayDragonQuest和PrintStream裝配到了一起:
<?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="knight" class="sia.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="sia.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}" />
</bean>
</beans>
BraveKnight和SlayDragon被聲明為Spring中的Bean。BraveKnight bean在構(gòu)造時(shí)傳入了對SlayDragonQuest的引用,將其作為構(gòu)造器參數(shù)。同時(shí),SlayDragonQuest使用Spring表達(dá)式語言將System.out(這是一個PrintStream)傳入到SlayDragonQuest的構(gòu)造器中仆葡。
同時(shí)SPring還支持使用Java來描述配置:
package sia.knights.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sia.knights.BraveKnight;
import sia.knights.Knight;
import sia.knights.Quest;
import sia.knights.SlayDragonQuest;
@Configuration
public class KnightConfig {
@Bean
public Knight knight() {
return new BraveKnight(quest());
}
@Bean
public Quest quest() {
return new SlayDragonQuest(System.out);
}
}
盡管BraveKnight依賴于Quest,但是它并不需要知道傳遞的是什么Quest培遵,SlayDragonQuest 也不需要知道PrintStream是什么樣子浙芙。只需要讓Spring通過配置文件了解這些組成部分要如何裝配起來。這樣就可以在不改變所依賴的類的情況下籽腕,修改依賴關(guān)系嗡呼。接下來只需要裝載XMl配置并把應(yīng)用啟動起來就可以了。
Spring裝配的過程:
Spring通過應(yīng)用上下文裝載bean并把它們組裝起來皇耗。Spring應(yīng)用上下文全權(quán)負(fù)責(zé)對象的創(chuàng)建和組裝南窗。Spring提供了多種應(yīng)用上下文的實(shí)現(xiàn),這些應(yīng)用上下文的區(qū)別僅僅在于如何加載配置郎楼。
對于使用XML文件配置的knights.xml万伤,選擇ClassPathXmlApplicationContext
作為應(yīng)用上下文。該類會加載位于應(yīng)用程序路徑下的一個或多個XML配置文件呜袁。
下面的程序中的main()方法調(diào)用ClassPathXmlApplicationContext加載knights.xml并獲得Knight對象的引用:
package sia.knights;
import org.springframework.context.support.
ClassPathXmlApplicationContext;
public class KnightMain {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext(
"META-INF/spring/knight.xml");
Knight knight = context.getBean(Knight.class);
knight.embarkOnQuest();
context.close();
}
}
這里的main()方法基于knights.xml文件創(chuàng)建了Spring應(yīng)用上下文敌买。隨后調(diào)用應(yīng)用上下文的getBean方法來獲得ID為knight的bean。得到Knight對象的引用后阶界,只需要調(diào)用embarkOnQuest()方法就可以執(zhí)行所賦予的探險(xiǎn)任務(wù)虹钮。
這個類完全不知道是哪個騎士接受哪種探險(xiǎn)任務(wù),只有knights.xml文件知道是哪個騎士執(zhí)行哪種探險(xiǎn)任務(wù)膘融。
應(yīng)用切面
DI使相互協(xié)作的軟件組件保持松散耦合芙粱,而面向切面編程AOP實(shí)現(xiàn)了把遍布應(yīng)用各處的功能分離出來形成可重用的組件氧映。
面向切面編程促使軟件系統(tǒng)實(shí)現(xiàn)關(guān)注點(diǎn)的分離。系統(tǒng)由許多不同的組件組成,每一個組件負(fù)責(zé)特定的服務(wù)振峻。日志、事務(wù)管理和安全之類的系統(tǒng)服務(wù)經(jīng)常融入到自身具有核心業(yè)務(wù)邏輯的組件中去择份,這些系統(tǒng)服務(wù)通常被稱為橫切關(guān)注點(diǎn)。因?yàn)樗鼤缭较到y(tǒng)的多個組件缓淹。
這些關(guān)注點(diǎn)如果被分散到多個組件里塔逃,會帶來雙重的復(fù)雜性:
實(shí)現(xiàn)系統(tǒng)關(guān)注點(diǎn)功能的代碼會重復(fù)出現(xiàn)在多個組件中讯壶。意味著如果要改變這些關(guān)注點(diǎn)的邏輯,必須同時(shí)修改各個模塊中的相關(guān)實(shí)現(xiàn)湾盗。即使把這些關(guān)注點(diǎn)抽象為一個獨(dú)立的模塊,其他模塊調(diào)用它的方法躏吊,但方法的調(diào)用還是會重復(fù)出現(xiàn)在各個模塊中帐萎。
組件會因?yàn)槟切┡c自己核心業(yè)務(wù)無關(guān)的代碼變得混亂。
下圖展示了這種復(fù)雜性赁项,左邊的業(yè)務(wù)對象與系統(tǒng)級服務(wù)結(jié)合得過于緊密澈段。每個對象要記日記、安全控制败富、參與事務(wù)兽叮,還要親自執(zhí)行這些服務(wù):
AOP能夠使這些服務(wù)模塊化,并以聲明的方式將它們應(yīng)用到它們需要影響的組件中去琴儿。這些組件會具有更高的內(nèi)聚性,并且更加關(guān)乎自身的業(yè)務(wù)而不需要去了解設(shè)計(jì)系統(tǒng)服所帶來的復(fù)雜性造成。
可以把切面想象成覆蓋在很多組件上的一個外殼晒屎。實(shí)現(xiàn)各自業(yè)務(wù)功能的模塊組成整個應(yīng)用。借助AOP鼓鲁,可以使用各種功能層去包裹核心業(yè)務(wù)層。這些層以聲明的方式靈活地應(yīng)用到系統(tǒng)中骇吭。核心應(yīng)用甚至不需要知道其它功能層的存在。這樣可以將安全棘脐、事務(wù)和日志關(guān)注點(diǎn)與核心業(yè)務(wù)邏輯分離龙致。
現(xiàn)在回到騎士的例子上,給它添加一個切面内斯。
AOP應(yīng)用
現(xiàn)在創(chuàng)建一個吟游詩人Minstrel類來記載騎士所做的事跡:
package sia.knights;
import java.io.PrintStream;
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!");
}
}
MineStrel是一個只有兩個方法的簡單類像啼,在騎士執(zhí)行每一個任務(wù)之前,singBeforeQuest()會被調(diào)用真朗;在騎士完成探險(xiǎn)任務(wù)之后僧诚,singAfterQuest()方法會被調(diào)用。在這兩種情況旗扑,Minestrel都會通過一個PrintStream類來歌頌騎士的事跡慈省,這個類是通過構(gòu)造器注入的。
現(xiàn)在嘗試做一下調(diào)整讓BraveKnight可以使用Minstrel:
package sia.knights;
public class BraveKnight implements Knight {
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest) {
this.quest = quest;
}
public void embarkOnQuest() {
minestrel.singBeforeQuest();
quest.embark();
minestrel.singAfterQuest();
}
}
這樣有個問題袱衷,管理吟游詩人的行為是騎士的工作嗎?此外因?yàn)槠鋵?shí)需要知道吟游詩人致燥,這樣必須要把吟游詩人注入到BraveKnight類中嫌蚤。但如果一個騎士不需要吟游詩人,這就會發(fā)生錯誤荷腊。
簡單的Knight類變得更復(fù)雜了急凰。利用AOP可以聲明吟游詩人必須歌頌騎士的探險(xiǎn)事跡猜年,而騎士本身不用自己去訪問Minestrel的方法。
要將Minestrel抽象為一個切面床三,則需要在Spring配置文件中聲明它:
<?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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="sia.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="sia.knights.SlayDragonQuest">
<constructor-arg ref="fakePrintStream" />
</bean>
<bean id="minstrel" class="sia.knights.Minstrel">
<constructor-arg ref="fakePrintStream" />
</bean>
<bean id="fakePrintStream" class="sia.knights.FakePrintStream" />
<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>
</beans>
上面使用了Spring的AOP配置命名空間把Minestrel bean聲明為一個切面撇簿。
- 首先差购,需要把Minestrel聲明為一個bean,然后在<aop:aspect>元素中引用該bean找蜜。
- 然后進(jìn)一步定義切面稳析,使用<aop:before>在embarkOnQuest()方法執(zhí)行前調(diào)用Minestrel的singBeforeQuest()方法。這種方式被稱為前置通知诚纸。
- 使用<aop:after>在embarkOnQuest()執(zhí)行后調(diào)用singAfterQuest()方法陈惰。這種方式被稱為后置通知。
在這兩種方式中旧烧,pointcut-ref屬性都引用了一個名字為embark的切入點(diǎn)。該切入點(diǎn)是在<aop:pointcut>元素中定義的平委,并配置的expression屬性來選擇所引用的通知夺谁。表達(dá)式的語法采用的是AspectJ的切點(diǎn)表達(dá)式語言。
詳細(xì)的在后面的章節(jié)會討論蜡塌,現(xiàn)在只需要知道Spring在騎士執(zhí)行任務(wù)前后會調(diào)用Minestrel的singBeforeQuest()和singAfterQuest()方法就足夠了勿负。
通過少量的XML配置就可以把Minestrel聲明為一個Spring切面奴愉。Minestrel仍然是一個POJO,沒有任何代碼表明它要被作為一個切面使用房资。Minestrel可以被應(yīng)用到BraveKnight中檀头,而BraveKnight不需要顯示地調(diào)用它。甚至完全不知道Minestrel的存在搭独。
使用模板消除樣板式代碼
有時(shí)候?yàn)榱藢?shí)現(xiàn)通用的和簡單的任務(wù)廊镜,你不得不一遍遍地重復(fù)編寫樣板式代碼。
一個常見范例是使用JDBC訪問數(shù)據(jù)庫查詢數(shù)據(jù)惊奇,少量的查詢員工的代碼被淹沒在一堆的JDBC樣板式代碼中播赁。首先需要創(chuàng)建一個數(shù)據(jù)庫連接容为,然后創(chuàng)建一個語句對象寺酪,最后才能進(jìn)行查詢替劈。還要捕捉SQLException。
最后還要關(guān)閉數(shù)據(jù)庫連接盒犹、語句和結(jié)果集眨业。同樣要捕捉SQLException龄捡。
在許多編程場景中往往都會導(dǎo)致類似的樣板式代碼。Sping旨在通過模板封裝來消除樣板式代碼晨雳。Spring的JdbcTemplate使執(zhí)行數(shù)據(jù)庫操作時(shí)奸腺,避免傳統(tǒng)的JDBC樣板代碼成為了可能。
XML配置文件是如何加載的呢?它們被加載到了哪里去了洋魂?接下來了解一下Spring容器副砍,這是應(yīng)用中的所有bean駐留的地方。
容納你的Bean
在基于Spring的引用中角骤,應(yīng)用對象生存于Spring容器(container)中心剥。如下圖所示:
Spring容器負(fù)責(zé)創(chuàng)建對象又沾,裝配它們,配置它們并管理它們的整個生命周期励饵。
容器是Spring框架的核心滑燃,Spring容器使用DI管理構(gòu)成應(yīng)用的組件,它會創(chuàng)建相互協(xié)作的組件之間的關(guān)聯(lián)禾嫉。
Spring自帶了多個容器實(shí)現(xiàn)蚊丐,可以歸為兩種不同的類型:
- bean工廠
- 應(yīng)用上下文
bean工廠是最簡單的容器麦备,提供基本的DI支持。應(yīng)用上下文基于BeanFactory構(gòu)建凛篙,并提供應(yīng)用框架級別的服務(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配置文件中加載上下文定義脸哀。
先簡單地使用FileSystemXmlApplicationContext從文件系統(tǒng)中加載應(yīng)用上下文或者使用ClassPathXmlApplicationContext從類路徑下加載應(yīng)用上下文。無論是從文件系統(tǒng)還是從類路徑下裝載應(yīng)用上下文白筹,將bean加載到bean工廠的過程都是類似的。
如下代碼展示了如何加載一個FileSystemXmlApplicationContext:
ApplicationContext context = new
FileSystemXmlApplicationContext("c:/knight.xml");
類似地系馆,可以使用ClassPathXmlApplicationContext從類路徑下加載應(yīng)用上下文:
ApplicationContext context = new
ClassPathXmlApplicationContext("knight.xml");
如果想從Java配置中加載應(yīng)用上下文由蘑,可以使用AnnotationConfigApplicationContext:
ApplicationContext context = new AnnotationConfigApplicationContext(com.springinaciton.knights.config.KnightConfig.class);
應(yīng)用上下文準(zhǔn)備就緒之后代兵,就可以調(diào)用上下文的getBean()方法從Spring容器中獲取bean。
bean的生命周期
在傳統(tǒng)的Java應(yīng)用中裳擎,bean的生命周期很簡單思币。使用Java關(guān)鍵字new進(jìn)行實(shí)例化谷饿,然后該bean就可以使用了。一旦該bean不再被使用博投,則由Java自動進(jìn)行垃圾回收毅哗。
Spring容器中的bean的生命周期要相對復(fù)雜的多。下圖展示了bean裝載到Spring應(yīng)用上下文中的一個典型的生命周期過程叉跛。
在bean準(zhǔn)備就緒之前宏所,bean工廠執(zhí)行了若干啟動步驟:
Spring對Bean進(jìn)行實(shí)例化摊溶;
Spring將值和bean引用注入到bean對應(yīng)的屬性中莫换;
如果bean實(shí)現(xiàn)了BeanNameAware接口骤铃,Spring將bean的ID傳遞給setBeanName()方法坷剧;
如果bean實(shí)現(xiàn)了BeanFactoryAware接口,Spring將調(diào)用setBeanFactory()方法撕瞧,將BeanFactory容器實(shí)例傳入丛版;
如果bean實(shí)現(xiàn)了ApplicationContextAware接口偏序,Spring將調(diào)用setApplicationContext()方法,將bean所在的應(yīng)用上下文引用傳入進(jìn)來寇漫;
如果bean實(shí)現(xiàn)了BeanPostProcessor接口殉摔,Spring將調(diào)用它們的postProcessAfterInitialization()方法逸月;
如果bean實(shí)現(xiàn)了InitializingBean接口,Spring將調(diào)用它們的afterPropertiesSet()方法瓤湘。類似地恩尾,如果bean使用initmethod聲明了初始化方法,該方法也會被調(diào)用木人。
如果bean實(shí)現(xiàn)了BeanPostProcessor接口冀偶,Spring將調(diào)用它們的postProcessAfterInitialization()方法进鸠;
此時(shí),bean已經(jīng)準(zhǔn)備就緒霞幅,可以被應(yīng)用程序使用,它們將一直駐留在應(yīng)用上下文中侥猩,知道該應(yīng)用上下文被銷毀抵赢;
如果bean實(shí)現(xiàn)了DisposableBean接口,Spring將調(diào)用它的destroy()接口方法划提。同樣邢享,如果bean使用destroymethod聲明了銷毀方法骇塘,該方法也會被調(diào)用。
俯瞰Spring
Spring模塊
Spring模塊為開發(fā)企業(yè)級應(yīng)用提供了所需的一切唐瀑。這些模塊根據(jù)所屬的功能可以劃分為6類不同的功能插爹。
也可以不將應(yīng)用建立在整個Spring框架之上赠尾,可以自由地選擇適合自身應(yīng)用需求的Spring模塊;Spring甚至提供了與其他第三方框架和類庫的集成點(diǎn)当窗。
現(xiàn)在我們逐一瀏覽Spring的模塊寸宵,看看它們是如何構(gòu)建起整個Spring藍(lán)圖的邓馒。
Spring核心容器
Spring框架最核心的部分蛾坯,它管理者Spring應(yīng)用中bean的創(chuàng)建、應(yīng)用和管理救军。該模塊中包括了Spring bean工廠唱遭,它為Spring提供了DI的功能。還有多種基于bean工廠的Spring應(yīng)用上下文的實(shí)現(xiàn)疫鹊,每一種提供了配置Spring的不同方式司致。
Spring核心容器除了bean工廠和應(yīng)用上下文,也提供了許多企業(yè)服務(wù)枣耀,例如Email庭再、JNDI訪問拄轻、EJB集成和調(diào)度。
所有的Spring模塊都構(gòu)建于核心容器之上谷浅。
Spring的AOP模塊
在AOP模塊中奶卓,Spring對面向切面編程提供了豐富的支持夺姑。這個模塊是Spring應(yīng)用系統(tǒng)中開發(fā)切面的基礎(chǔ)。與DI一樣眉睹,AOP可以幫助應(yīng)用對象解耦废膘。將遍布系統(tǒng)的關(guān)注點(diǎn)(例如事務(wù)和安全)從它們所應(yīng)用的對象中解耦出來丐黄。
數(shù)據(jù)訪問與集成
使用JDBC編寫代碼通常會導(dǎo)致大量的樣板式代碼,Spring的JDBC和DAO模塊抽象了這些樣板式代碼艰争,使數(shù)據(jù)庫代碼變得簡單明了,還可以避免因?yàn)殛P(guān)閉數(shù)據(jù)庫資源失敗而引發(fā)的問題鸠匀。該模塊在多種數(shù)據(jù)庫服務(wù)的錯誤信息之上構(gòu)建了一個語義豐富的異常層逾柿。
對于更喜歡ORM工具而不愿意直接使用JDBC的開發(fā)者机错,Spring提供了ORM模塊。Spring的ORM模塊建立在對DAO的支持之上坦敌,并為多個ORM框架提供了一種構(gòu)建DAO的簡便方式痢法。Spring對許多流行的ORM框架進(jìn)行了繼承。蘸炸。Spring的事務(wù)管理支持所有的ORM框架以及JDBC搭儒。
本模塊同樣包含了在JMS之上構(gòu)建的Spring抽象層提茁,使消息以異步的方式與其他應(yīng)用集成。
除此之外铃岔,本模塊會使用Spring AOP模塊為Spring應(yīng)用中的對象提供事務(wù)管理服務(wù)毁习。
Web與遠(yuǎn)程調(diào)用
Spring雖然能夠與多種流行的MVC框架集成卖丸,但它的Web和遠(yuǎn)程調(diào)用模塊自帶了一個強(qiáng)大的MVC框架稍浆,有助于在Web層提升應(yīng)用的松耦合水平猜嘱。
除了面向用戶的Web應(yīng)用恐仑,該模塊還提供了多種構(gòu)建與其他應(yīng)用交互的遠(yuǎn)程調(diào)用方案裳仆。
Instrumentation
Spring的Instrumentation模塊提供了為JVM添加代理的功能孤钦。
為Tomcat提供了一個織入代理偏形,能夠?yàn)門omcat傳遞類文件,就像是這些文件被類加載器加載一樣队橙。
Instrumentation的使用場景非常有限萨惑,不會具體介紹庸蔼。
測試
Spring提供了測試模塊以致力于Spring應(yīng)用的測試。
Spring為使用JNDI花枫、Sevlet和Portlet編寫單元測試提供了一系列的mock對象實(shí)現(xiàn)掏膏。對于集成測試馒疹,測試模塊為加載Spring應(yīng)用上下文的bean集合以及與Spring上下文中的bean進(jìn)行交互提供了支持。
依賴注入和AOP是Spring框架最核心的部分溺蕉,只有理解了如何應(yīng)用Spring最關(guān)鍵的功能悼做,才有能力使用Spring框架的其他功能肛走。