簡(jiǎn)潔的Spring
為了降低Java開發(fā)的復(fù)雜性,Spring采取了以下4種關(guān)鍵策略:
- 基于POJO的輕量級(jí)和最小侵入性編程瓜贾;
- 通過依賴注入和面向接口實(shí)現(xiàn)松耦合龟劲;
- 基于切面和慣例進(jìn)行聲明式編程答恶;
- 通過切面和模板減少樣板式代碼裕坊。
激發(fā)POJO的潛能
相對(duì)于EJB的臃腫堰氓,Spring盡量避免因自身的api而弄亂用戶的應(yīng)用代碼,Spring不會(huì)強(qiáng)迫用戶實(shí)現(xiàn)Spring規(guī)范的接口或繼承Spring規(guī)范的類漓骚,相反,在基于Spring構(gòu)建的應(yīng)用中,它的類通常沒有任何痕跡表明你使用了Spring。最壞的場(chǎng)景是跪另,一個(gè)類或許會(huì)使用Spring注解,但它依舊是POJO誊垢。
Spring賦予POJO魔力的方式之一就是通過依賴注入
來裝載它們乎芳。
依賴注入
任何一個(gè)有意義的應(yīng)用一般都需要多個(gè)組件帮孔,這些組件之間必定需要進(jìn)行相互協(xié)作才能完成特定的業(yè)務(wù),從而導(dǎo)致組件之間的緊耦合击喂,牽一發(fā)而動(dòng)全身。
代碼示例:
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緊密地耦合到了一起催式,因此極大地限制了這個(gè)騎士的執(zhí)行能力。如果一個(gè)少女需要救援奸柬,這個(gè)騎士能夠召之即來授段。但是如果一條惡龍需要?dú)⒌粞珉剩敲催@個(gè)騎士只能愛莫能助了。
另一方面拷肌,可以通過依賴注入
的方式來完成對(duì)象之間的依賴關(guān)系拴清,對(duì)象不再需要自行管理它們的依賴關(guān)系木张,而是通過依賴注入自動(dòng)地注入到對(duì)象中去育拨。
代碼示例:
package com.springinaction.knights;
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest;// quest被注入到對(duì)象中
}
@Override
public void embarhOnQuest() throws QuestException {
quest.embark();
}
}
不同于之前的DamselRescuingKnight,BraveKnight沒有自行創(chuàng)建探險(xiǎn)任務(wù),而是在構(gòu)造器中把探險(xiǎn)任務(wù)作為參數(shù)注入失尖,這也是依賴注入的一種方式薯鼠,即構(gòu)造器注入暇仲。
更為重要的是,BraveKnight中注入的探險(xiǎn)類型是Quest茬贵,Quest只是一個(gè)探險(xiǎn)任務(wù)所必須實(shí)現(xiàn)的接口巷嚣。因此,BraveKnight能夠響RescueDamselQuest厨喂、SlayDraonQuest等任意一種Quest實(shí)現(xiàn)绿贞,這正是多態(tài)的體現(xiàn)耻警。
這里的要點(diǎn)是BraveKnight沒有與任何特定的Quest實(shí)現(xiàn)發(fā)生耦合荡含。對(duì)它來說误债,被要求挑戰(zhàn)的探險(xiǎn)任務(wù)只要實(shí)現(xiàn)了Quest接口箫老,那么具體是哪一類型的探險(xiǎn)就無關(guān)緊要了界斜。這就是依賴注入最大的好處--松耦合。如果一個(gè)對(duì)象只通過接口(而不是具體實(shí)現(xiàn)或初始化的過程)來表明依賴關(guān)系完残,那么這種依賴就能夠在對(duì)象本身毫不知情的情況下,用不同的具體實(shí)現(xiàn)進(jìn)行替換琴锭。
注入一個(gè)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)負(fù)責(zé)對(duì)象的創(chuàng)建和組裝扁远。
Spring自帶了多種ApplicationContext來加載配置,比如刻像,Spring可以使用ClassPathXmlApplicationContext
來裝載XML文件中的Bean對(duì)象畅买。
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
}
}
這個(gè)示例代碼中,Spring上下文加載了knights.xml
文件细睡,隨后獲取了一個(gè)ID為knight的Bean的實(shí)例谷羞,得到該對(duì)象實(shí)例后,就可以進(jìn)行正常的使用了溜徙。需要注意的是湃缎,這個(gè)類中完全不知道是由哪個(gè)Knight來執(zhí)行何種Quest任務(wù)犀填,只有knights.xml
文件知道。
應(yīng)用切面
通常情況下嗓违,系統(tǒng)由許多不同組件組成九巡,其中的每一個(gè)組件分別負(fù)責(zé)一塊特定功能。除了實(shí)現(xiàn)自身核心的功能之外蹂季,這些組件還經(jīng)常承擔(dān)著額外的職責(zé)比庄,諸如日志、事務(wù)管理和安全等乏盐,此類的系統(tǒng)服務(wù)經(jīng)常融入到有自身核心業(yè)務(wù)邏輯的組件中去佳窑,這些系統(tǒng)服務(wù)通常被稱為橫切關(guān)注點(diǎn),因?yàn)樗鼈兛偸强缭较到y(tǒng)的多個(gè)組件父能,如下圖所示神凑。
AOP可以使得這些服務(wù)模塊化溉委,并以聲明的方式將它們應(yīng)用到相應(yīng)的組件中去,這樣爱榕,這些組件就具有更高內(nèi)聚性以及更加關(guān)注自身業(yè)務(wù)瓣喊,完全不需要了解可能涉及的系統(tǒng)服務(wù)的復(fù)雜性∏郑總之藻三,AOP確保POJO保持簡(jiǎn)單。
如圖所示忘衍,我們可以把切面想象為覆蓋在很多組件之上的一個(gè)外殼逾苫。利用AOP,你可以使用各種功能層去包裹核心業(yè)務(wù)層枚钓。這些層以聲明的方式靈活應(yīng)用到你的系統(tǒng)中铅搓,甚至你的核心應(yīng)用根本不知道它們的存在。
AOP應(yīng)用
接上面騎士的故事秘噪,現(xiàn)在需要一個(gè)詩人來歌頌騎士的勇敢事跡狸吞,代碼如下「Minstrel是中世紀(jì)的音樂記錄器」:
package com.springinaction.knights;
public class Minstrel {
public void singBeforeQuest() { // 探險(xiǎn)之前調(diào)用
System.out.println("Fa la la; The knight is so brave!");
}
public void singAfterQuest() { // 探險(xiǎn)之后調(diào)用
System.out.println("Tee hee he; The brave knight did embark on a quest!");
}
}
如代碼中所示,詩人會(huì)在騎士每次執(zhí)行探險(xiǎn)前和結(jié)束時(shí)被調(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被注入到對(duì)象中
}
public BraveKnight(Quest quest, Minstrel minstrel) {
this.quest = quest;// quest被注入到對(duì)象中
this.minstrel = minstrel;
}
@Override
public void embarhOnQuest() throws QuestException {
minstrel.singAfterQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
但是便斥,感覺是騎士在路邊抓了一個(gè)詩人為自己「歌功頌德」,而不是詩人主動(dòng)地為其傳揚(yáng)事跡威始。簡(jiǎn)單的BraveKnight類開始變得復(fù)雜枢纠,如果騎士不需要詩人,那么代碼將會(huì)更加復(fù)雜黎棠。
但是有了AOP晋渺,騎士就不再需要自己調(diào)用詩人的方法為自己服務(wù)了,這就需要把Minstrel聲明為一個(gè)切面:
<?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脓斩,待切入的對(duì)象(刀) -->
<bean id="minstrel" class="com.springinaction.knights.Minstrel"></bean>
<aop:config>
<aop:aspect ref="minstrel">
<!-- 定義切面木西,即定義從哪里切入 -->
<aop:pointcut expression="execution(* *.embarkOnQuest(..))"
id="embark" />
<!-- 聲明前置通知,在切入點(diǎn)之前執(zhí)行的方法 -->
<aop:before method="singBeforeQuest" pointcut-ref="embark" />
<!-- 聲明后置通知随静,在切入點(diǎn)之后執(zhí)行的方法 -->
<aop:after method="singAfterQuest" pointcut-ref="embark" />
</aop:aspect>
</aop:config>
</beans>
通過運(yùn)行結(jié)果可以發(fā)現(xiàn)八千,在沒有改動(dòng)BraveKnight的代碼的情況下,就完成了Minstrel對(duì)其的歌頌燎猛,而且BraveKnight并不知道Minstrel的存在恋捆。
使用Spring模版
使用Spring模版可以消除很多樣板式代碼,比如JDBC重绷、JMS沸停、JNDI、REST等昭卓。
容納Bean
在Spring中愤钾,應(yīng)用對(duì)象生存于Spring容器中,如圖所示葬凳,Spring容器可以創(chuàng)建绰垂、裝載室奏、配置這些Bean火焰,并且可以管理它們的生命周期。
Spring的容器實(shí)現(xiàn)
- Bean工廠(
org.springframework.beans.factory.BeanFactory
):最簡(jiǎn)單的容器南蹂,提供基本的DI支持犬金; - 應(yīng)用上下文(
org.springframework.context.ApplicationContext
):基于BeanFactory之上構(gòu)建,提供面向應(yīng)用的服務(wù)。
常用的幾種應(yīng)用上下文
- ClassPathXmlApplicationContext:從類路徑中的XML配置文件加載上下文晚顷,會(huì)在所有的類路徑(包括jar文件)下查找峰伙;
- FileSystemXmlApplicationContext:從文件系統(tǒng)中讀取XML配置文件并加載上下文,在指定的文件系統(tǒng)路徑下查找该默;
- XmlWebApplicationContext:讀取Web應(yīng)用下的XML配置文件并加載上下文瞳氓;
Bean的生命周期
- Spring對(duì)Bean進(jìn)行實(shí)例化;
- Spring將值和Bean的引用注入進(jìn)Bean對(duì)應(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()
接口方法囊咏,將應(yīng)用上下文的引用傳入; - 如果Bean實(shí)現(xiàn)了
BeanPostProcessor
接口塔橡,Spring將調(diào)用postProcessBeforeInitialization()
接口方法梅割; - 如果Bean實(shí)現(xiàn)了
InitializationBean
接口,Spring將調(diào)用afterPropertiesSet()
方法葛家。類似的如果Bean使用了init-method
聲明了初始化方法户辞,該方法也會(huì)被調(diào)用; - 如果Bean實(shí)現(xiàn)了
BeanPostProcessor
接口癞谒,Spring將調(diào)用ProcessAfterInitialization()
方法底燎; - 此時(shí)此刻,Bean已經(jīng)準(zhǔn)備就緒弹砚,可以被應(yīng)用程序使用了双仍,它們將一直
駐留在應(yīng)用上下文中
,直到該應(yīng)用上下文被銷毀桌吃; - 如果Bean實(shí)現(xiàn)了
DisposableBean
接口朱沃,Spring將調(diào)用destory()
方法,同樣的茅诱,如果Bean中使用了destroy-method
聲明了銷毀方法逗物,也會(huì)調(diào)用該方法;
縱觀Spring
Spring模塊
核心Spring容器
容器是Spring框架最核心的部分瑟俭,它負(fù)責(zé)Spring應(yīng)用中Bean的創(chuàng)建翎卓、配置和管理。Spring模塊都構(gòu)建與核心容器之上摆寄,當(dāng)配置應(yīng)用時(shí)失暴,其實(shí)都隱式地使用了相關(guān)的核心容器類坯门。另外,該模塊還提供了許多企業(yè)級(jí)服務(wù)逗扒,如郵件田盈、JNDI訪問、EJB集成和調(diào)度等缴阎。
AOP
AOP是Spring應(yīng)用系統(tǒng)開發(fā)切面的基礎(chǔ)允瞧,與依賴注入一樣,可以幫助應(yīng)用對(duì)象解耦
蛮拔。借助于AOP述暂,可以將遍布于應(yīng)用的關(guān)注點(diǎn)(如事務(wù)和安全等)從所應(yīng)用的對(duì)象中解耦出來。
數(shù)據(jù)訪問與集成
Spring的JDBC和DAO模塊封裝了大量的樣板代碼建炫,這樣可以使得在數(shù)據(jù)庫代碼變得簡(jiǎn)潔畦韭,也可以更專注于我們的業(yè)務(wù),還可以避免數(shù)據(jù)庫資源釋放失敗而引發(fā)的問題肛跌。另外艺配,Spring AOP為數(shù)據(jù)訪問提供了事務(wù)管理服務(wù)。同時(shí)衍慎,Spring還與流程的ORM(Object-Relational Mapping)進(jìn)行了集成转唉,如Hibernate、MyBatis等稳捆。
Web和遠(yuǎn)程調(diào)用
Spring提供了兩種Web層框架:面向傳統(tǒng)Web應(yīng)用的基于Servlet的框架和面向使用Java Portlet API的基于Portlet的應(yīng)用赠法。Spring遠(yuǎn)程調(diào)用服務(wù)集成了RMI、Hessian乔夯、Burlap砖织、JAX-WS等。
測(cè)試
Spring提供了測(cè)試模塊來測(cè)試Spring應(yīng)用末荐。
如果覺得有用侧纯,歡迎關(guān)注我的微信,有問題可以直接交流: