簡潔的Spring
為了降低Java開發(fā)的復(fù)雜性,Spring采取了以下4種關(guān)鍵策略:
- 基于POJO的輕量級和最小侵入性編程;
- 通過依賴注入和面向接口實現(xiàn)松耦合;
- 基于切面和慣例進行聲明式編程;
- 通過切面和模板減少樣板式代碼。
激發(fā)POJO的潛能
相對于EJB的臃腫,Spring盡量避免因自身的api而弄亂用戶的應(yīng)用代碼,Spring不會強迫用戶實現(xiàn)Spring規(guī)范的接口或繼承Spring規(guī)范的類,相反,在基于Spring構(gòu)建的應(yīng)用中,它的類通常沒有任何痕跡表明你使用了Spring。最壞的場景是,一個類或許會使用Spring注解,但它依舊是POJO。
Spring賦予POJO魔力的方式之一就是通過依賴注入
來裝載它們。
依賴注入
任何一個有意義的應(yīng)用一般都需要多個組件,這些組件之間必定需要進行相互協(xié)作才能完成特定的業(yè)務(wù),從而導致組件之間的緊耦合,牽一發(fā)而動全身。
代碼示例:
package com.springinaction.knights;
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest;
public DamselRescuingKnight() {
quest = new RescueDamselQuest();// 與RescueDamselQuest緊耦合
}
@Override
public void embarhOnQuest() throws QuestException {
quest.embark();
}
}
正如你所見,DamselRescuingKnight 在它的構(gòu)造函數(shù)中自行創(chuàng)建了RescueDamselQuest,這使得DamselRescuingKnight和RescueDamselQuest緊密地耦合到了一起,因此極大地限制了這個騎士的執(zhí)行能力。如果一個少女需要救援,這個騎士能夠召之即來。但是如果一條惡龍需要殺掉,那么這個騎士只能愛莫能助了。
另一方面,可以通過依賴注入
的方式來完成對象之間的依賴關(guān)系,對象不再需要自行管理它們的依賴關(guān)系,而是通過依賴注入自動地注入到對象中去。
代碼示例:
package com.springinaction.knights;
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest;// quest被注入到對象中
}
@Override
public void embarhOnQuest() throws QuestException {
quest.embark();
}
}
不同于之前的DamselRescuingKnight,BraveKnight沒有自行創(chuàng)建探險任務(wù),而是在構(gòu)造器中把探險任務(wù)作為參數(shù)注入,這也是依賴注入的一種方式,即構(gòu)造器注入。
更為重要的是,BraveKnight中注入的探險類型是Quest,Quest只是一個探險任務(wù)所必須實現(xiàn)的接口。因此,BraveKnight能夠響RescueDamselQuest、SlayDraonQuest等任意一種Quest實現(xiàn),這正是多態(tài)的體現(xiàn)。
這里的要點是BraveKnight沒有與任何特定的Quest實現(xiàn)發(fā)生耦合。對它來說,被要求挑戰(zhàn)的探險任務(wù)只要實現(xiàn)了Quest接口,那么具體是哪一類型的探險就無關(guān)緊要了。這就是依賴注入最大的好處--松耦合。如果一個對象只通過接口(而不是具體實現(xiàn)或初始化的過程)來表明依賴關(guān)系,那么這種依賴就能夠在對象本身毫不知情的情況下,用不同的具體實現(xiàn)進行替換。
注入一個Quest到Knight
創(chuàng)建應(yīng)用組件之間協(xié)作關(guān)系的行為稱為裝配,Spring有多種裝配Bean的方式,其中最常用的就是通過XML配置文件的方式裝配。
示例代碼:使用Spring將SlayDragonQuest注入到BraveKnight中。
<?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="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest"></constructor-arg>
</bean>
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest"></bean>
</beans>
Spring是如何注入的?
Spring通過應(yīng)用上下文(ApplicationContext
)來裝載Bean,ApplicationContext
全權(quán)負責對象的創(chuàng)建和組裝。
Spring自帶了多種ApplicationContext來加載配置,比如,Spring可以使用ClassPathXmlApplicationContext
來裝載XML文件中的Bean對象。
package com.springinaction.knights;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class KnightMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("knights.xml");// 加載Spring上下文
Knight knight = (Knight) context.getBean("knight");// 獲取knight Bean
knight.embarhOnQuest();// 使用knight
}
}
這個示例代碼中,Spring上下文加載了knights.xml
文件,隨后獲取了一個ID為knight的Bean的實例,得到該對象實例后,就可以進行正常的使用了。需要注意的是,這個類中完全不知道是由哪個Knight來執(zhí)行何種Quest任務(wù),只有knights.xml
文件知道。
應(yīng)用切面
通常情況下,系統(tǒng)由許多不同組件組成,其中的每一個組件分別負責一塊特定功能。除了實現(xiàn)自身核心的功能之外,這些組件還經(jīng)常承擔著額外的職責,諸如日志、事務(wù)管理和安全等,此類的系統(tǒng)服務(wù)經(jīng)常融入到有自身核心業(yè)務(wù)邏輯的組件中去,這些系統(tǒng)服務(wù)通常被稱為橫切關(guān)注點,因為它們總是跨越系統(tǒng)的多個組件,如下圖所示。

AOP可以使得這些服務(wù)模塊化,并以聲明的方式將它們應(yīng)用到相應(yīng)的組件中去,這樣,這些組件就具有更高內(nèi)聚性以及更加關(guān)注自身業(yè)務(wù),完全不需要了解可能涉及的系統(tǒng)服務(wù)的復(fù)雜性。總之,AOP確保POJO保持簡單。

如圖所示,我們可以把切面想象為覆蓋在很多組件之上的一個外殼。利用AOP,你可以使用各種功能層去包裹核心業(yè)務(wù)層。這些層以聲明的方式靈活應(yīng)用到你的系統(tǒng)中,甚至你的核心應(yīng)用根本不知道它們的存在。
AOP應(yīng)用
接上面騎士的故事,現(xiàn)在需要一個詩人來歌頌騎士的勇敢事跡,代碼如下「Minstrel是中世紀的音樂記錄器」:
package com.springinaction.knights;
public class Minstrel {
public void singBeforeQuest() { // 探險之前調(diào)用
System.out.println("Fa la la; The knight is so brave!");
}
public void singAfterQuest() { // 探險之后調(diào)用
System.out.println("Tee hee he; The brave knight did embark on a quest!");
}
}
如代碼中所示,詩人會在騎士每次執(zhí)行探險前和結(jié)束時被調(diào)用,完成騎士事跡的歌頌。騎士必須調(diào)用詩人的方法完成歌頌:
package com.springinaction.knights;
public class BraveKnight implements Knight {
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest) {
this.quest = quest;// quest被注入到對象中
}
public BraveKnight(Quest quest, Minstrel minstrel) {
this.quest = quest;// quest被注入到對象中
this.minstrel = minstrel;
}
@Override
public void embarhOnQuest() throws QuestException {
minstrel.singAfterQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
但是,感覺是騎士在路邊抓了一個詩人為自己「歌功頌德」,而不是詩人主動地為其傳揚事跡。簡單的BraveKnight類開始變得復(fù)雜,如果騎士不需要詩人,那么代碼將會更加復(fù)雜。
但是有了AOP,騎士就不再需要自己調(diào)用詩人的方法為自己服務(wù)了,這就需要把Minstrel聲明為一個切面:
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest"></constructor-arg>
</bean>
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest"></bean>
<!-- 聲明詩人Minstrel,待切入的對象(刀) -->
<bean id="minstrel" class="com.springinaction.knights.Minstrel"></bean>
<aop:config>
<aop:aspect ref="minstrel">
<!-- 定義切面,即定義從哪里切入 -->
<aop:pointcut expression="execution(* *.embarkOnQuest(..))"
id="embark" />
<!-- 聲明前置通知,在切入點之前執(zhí)行的方法 -->
<aop:before method="singBeforeQuest" pointcut-ref="embark" />
<!-- 聲明后置通知,在切入點之后執(zhí)行的方法 -->
<aop:after method="singAfterQuest" pointcut-ref="embark" />
</aop:aspect>
</aop:config>
</beans>
通過運行結(jié)果可以發(fā)現(xiàn),在沒有改動BraveKnight的代碼的情況下,就完成了Minstrel對其的歌頌,而且BraveKnight并不知道Minstrel的存在。
使用Spring模版
使用Spring模版可以消除很多樣板式代碼,比如JDBC、JMS、JNDI、REST等。
容納Bean
在Spring中,應(yīng)用對象生存于Spring容器中,如圖所示,Spring容器可以創(chuàng)建、裝載、配置這些Bean,并且可以管理它們的生命周期。

Spring的容器實現(xiàn)
- Bean工廠(
org.springframework.beans.factory.BeanFactory
):最簡單的容器,提供基本的DI支持; - 應(yīng)用上下文(
org.springframework.context.ApplicationContext
):基于BeanFactory之上構(gòu)建,提供面向應(yīng)用的服務(wù)。
常用的幾種應(yīng)用上下文
- ClassPathXmlApplicationContext:從類路徑中的XML配置文件加載上下文,會在所有的類路徑(包括jar文件)下查找;
- FileSystemXmlApplicationContext:從文件系統(tǒng)中讀取XML配置文件并加載上下文,在指定的文件系統(tǒng)路徑下查找;
- XmlWebApplicationContext:讀取Web應(yīng)用下的XML配置文件并加載上下文;
Bean的生命周期

- Spring對Bean進行實例化;
- Spring將值和Bean的引用注入進Bean對應(yīng)的屬性中;
- 如果Bean實現(xiàn)了
BeanNameAware
接口,Spring將Bean的ID傳遞給setBeanName()
接口方法; - 如果Bean實現(xiàn)了
BeanFactoryAware
接口,Spring將調(diào)setBeanFactory()
接口方法,將BeanFactory容器實例傳入; - 如果Bean實現(xiàn)了
ApplicationContextAware
接口,Spring將調(diào)用setApplicationContext()
接口方法,將應(yīng)用上下文的引用傳入; - 如果Bean實現(xiàn)了
BeanPostProcessor
接口,Spring將調(diào)用postProcessBeforeInitialization()
接口方法; - 如果Bean實現(xiàn)了
InitializationBean
接口,Spring將調(diào)用afterPropertiesSet()
方法。類似的如果Bean使用了init-method
聲明了初始化方法,該方法也會被調(diào)用; - 如果Bean實現(xiàn)了
BeanPostProcessor
接口,Spring將調(diào)用ProcessAfterInitialization()
方法; - 此時此刻,Bean已經(jīng)準備就緒,可以被應(yīng)用程序使用了,它們將一直
駐留在應(yīng)用上下文中
,直到該應(yīng)用上下文被銷毀; - 如果Bean實現(xiàn)了
DisposableBean
接口,Spring將調(diào)用destory()
方法,同樣的,如果Bean中使用了destroy-method
聲明了銷毀方法,也會調(diào)用該方法;
縱觀Spring
Spring模塊

核心Spring容器
容器是Spring框架最核心的部分,它負責Spring應(yīng)用中Bean的創(chuàng)建、配置和管理。Spring模塊都構(gòu)建與核心容器之上,當配置應(yīng)用時,其實都隱式地使用了相關(guān)的核心容器類。另外,該模塊還提供了許多企業(yè)級服務(wù),如郵件、JNDI訪問、EJB集成和調(diào)度等。
AOP
AOP是Spring應(yīng)用系統(tǒng)開發(fā)切面的基礎(chǔ),與依賴注入一樣,可以幫助應(yīng)用對象解耦
。借助于AOP,可以將遍布于應(yīng)用的關(guān)注點(如事務(wù)和安全等)從所應(yīng)用的對象中解耦出來。
數(shù)據(jù)訪問與集成
Spring的JDBC和DAO模塊封裝了大量的樣板代碼,這樣可以使得在數(shù)據(jù)庫代碼變得簡潔,也可以更專注于我們的業(yè)務(wù),還可以避免數(shù)據(jù)庫資源釋放失敗而引發(fā)的問題。另外,Spring AOP為數(shù)據(jù)訪問提供了事務(wù)管理服務(wù)。同時,Spring還與流程的ORM(Object-Relational Mapping)進行了集成,如Hibernate、MyBatis等。
Web和遠程調(diào)用
Spring提供了兩種Web層框架:面向傳統(tǒng)Web應(yīng)用的基于Servlet的框架和面向使用Java Portlet API的基于Portlet的應(yīng)用。Spring遠程調(diào)用服務(wù)集成了RMI、Hessian、Burlap、JAX-WS等。
測試
Spring提供了測試模塊來測試Spring應(yīng)用。
如果覺得有用,歡迎關(guān)注我的微信,有問題可以直接交流:
