Spring框架概述
Ø 主要內(nèi)容:介紹Spring的歷史,Spring的概論和它的體系結(jié)構(gòu),重點闡述它在J2EE中扮演的角色。
Ø 目 的:全面的了解Spring框架,知道Spring框架所提供的功能,并能將Spring框架和其它框架(WebWork/Struts、hibernate)區(qū)分開來。
Spring是什么?
Spring是一個開源框架,它由Rod Johnson創(chuàng)建。它是為了解決企業(yè)應(yīng)用開發(fā)的復(fù)雜性而創(chuàng)建的。Spring使用基本的JavaBean來完成以前只可能由EJB完成的事情。然而,Spring的用途不僅限于服務(wù)器端的開發(fā)。從簡單性、可測試性和松耦合的角度而言,任何Java應(yīng)用都可以從Spring中受益。
¨ 目的:解決企業(yè)應(yīng)用開發(fā)的復(fù)雜性
¨ 功能:使用基本的JavaBean代替EJB,并提供了更多的企業(yè)應(yīng)用功能
¨ 范圍:任何Java應(yīng)用
簡單來說,Spring是一個輕量級的控制反轉(zhuǎn)(IoC)和面向切面(AOP)的容器框架。
■ 輕量——從大小與開銷兩方面而言Spring都是輕量的。完整的Spring框架可以在一個大小只有1MB多的JAR文件里發(fā)布。并且Spring所需的處理開銷也是微不足道的。此外,Spring是非侵入式的:典型地,Spring應(yīng)用中的對象不依賴于Spring的特定類。
■ 控制反轉(zhuǎn)——Spring通過一種稱作控制反轉(zhuǎn)(IoC)的技術(shù)促進(jìn)了松耦合。當(dāng)應(yīng)用了IoC,一個對象依賴的其它對象會通過被動的方式傳遞進(jìn)來,而不是這個對象自己創(chuàng)建或者查找依賴對象。你可以認(rèn)為IoC與JNDI相反——不是對象從容器中查找依賴,而是容器在對象初始化時不等對象請求就主動將依賴傳遞給它。
■ 面向切面——Spring提供了面向切面編程的豐富支持,允許通過分離應(yīng)用的業(yè)務(wù)邏輯與系統(tǒng)級服務(wù)(例如審計(auditing)和事務(wù)()管理)進(jìn)行內(nèi)聚性的開發(fā)。應(yīng)用對象只實現(xiàn)它們應(yīng)該做的——完成業(yè)務(wù)邏輯——僅此而已。它們并不負(fù)責(zé)(甚至是意識)其它的系統(tǒng)級關(guān)注點,例如日志或事務(wù)支持。
■ 容器——Spring包含并管理應(yīng)用對象的配置和生命周期,在這個意義上它是一種容器,你可以配置你的每個bean如何被創(chuàng)建——基于一個可配置原型(prototype),你的bean可以創(chuàng)建一個單獨的實例或者每次需要時都生成一個新的實例——以及它們是如何相互關(guān)聯(lián)的。然而,Spring不應(yīng)該被混同于傳統(tǒng)的重量級的EJB容器,它們經(jīng)常是龐大與笨重的,難以使用。
■ 框架——Spring可以將簡單的組件配置、組合成為復(fù)雜的應(yīng)用。在Spring中,應(yīng)用對象被聲明式地組合,典型地是在一個XML文件里。Spring也提供了很多基礎(chǔ)功能(事務(wù)管理、持久化框架集成等等),將應(yīng)用邏輯的開發(fā)留給了你。
所有Spring的這些特征使你能夠編寫更干凈、更可管理、并且更易于測試的代碼。它們也為Spring中的各種模塊提供了基礎(chǔ)支持。
Spring的歷史
Spring的基礎(chǔ)架構(gòu)起源于2000年早期,它是Rod Johnson在一些成功的商業(yè)項目中構(gòu)建的基礎(chǔ)設(shè)施。
在2002后期,Rod Johnson發(fā)布了《Expert One-on-One J2EE Design and Development》一書,并隨書提供了一個初步的開發(fā)框架實現(xiàn)——interface21開發(fā)包,interface21就是書中闡述的思想的具體實現(xiàn)。后來,Rod Johnson 在interface21 開發(fā)包的基礎(chǔ)之上,進(jìn)行了進(jìn)一步的改造和擴(kuò)充,使其發(fā)展為一個更加開放、清晰、全面、高效的開發(fā)框架——Spring。
2003年2月Spring框架正式成為一個開源項目,并發(fā)布于SourceForge中。
Spring的使命(Mission Statement)
¨ J2EE應(yīng)該更加容易使用。
¨ 面向?qū)ο蟮脑O(shè)計比任何實現(xiàn)技術(shù)(比如J2EE)都重要。
¨ 面向接口編程,而不是針對類編程。Spring將使用接口的復(fù)雜度降低到零。(面向接口編程有哪些復(fù)雜度?)
¨ 代碼應(yīng)該易于測試。Spring框架會幫助你,使代碼的測試更加簡單。
¨ JavaBean提供了應(yīng)用程序配置的最好方法。
¨ 在Java中,已檢查異常(Checked exception)被過度使用。框架不應(yīng)該迫使你捕獲不能恢復(fù)的異常。
Spring受到的批判
Ø Spring不是一個“標(biāo)準(zhǔn)”。Spring不是J2EE規(guī)范的一部分,沒有通過JCP(Java Community Process)的審核認(rèn)可。
批判來源于EJB的支持者,他們認(rèn)為EJB是一個標(biāo)準(zhǔn),是J2EE規(guī)范的一部分。當(dāng)然,標(biāo)準(zhǔn)最主要的目的是希望在應(yīng)用服務(wù)器之間是可移植的,可是EJB的移植卻并不輕松,不同應(yīng)用服務(wù)器的ejb部署描述文件總是有著差異。而且EJB開發(fā)的類完全依賴于EJB容器。而Spring對其管理的Bean沒有任何形式的侵入,這樣的Bean是普通Java對象(POJO),那么它就遵循Java標(biāo)準(zhǔn),可以到處移植。
Ø Spring是“超重量級”的。
Spring涉及的內(nèi)容確實很多(例如:提供了對jdbc、ORM、遠(yuǎn)程訪問等等的支持),但其本質(zhì)還是Java技術(shù)的龐大。Spring只是為了這些技術(shù)提供更好的使用方案而已。同時,你可以只選取你需要使用的部分。
Spring包含的模塊
Spring框架由七個定義明確的模塊組成(圖1.1)。
(Spring框架概覽圖)
如果作為一個整體,這些模塊為你提供了開發(fā)企業(yè)應(yīng)用所需的一切。但你不必將應(yīng)用完全基于Spring框架。你可以自由地挑選適合你的應(yīng)用的模塊而忽略其余的模塊。
就像你所看到的,所有的Spring模塊都是在核心容器之上構(gòu)建的。容器定義了Bean是如何創(chuàng)建、配置和管理的——更多的Spring細(xì)節(jié)。當(dāng)你配置你的應(yīng)用時,你會潛在地使用這些類。但是作為一名開發(fā)者,你最可能對影響容器所提供的服務(wù)的其它模塊感興趣。這些模塊將會為你提供用于構(gòu)建應(yīng)用服務(wù)的框架,例如AOP和持久性。
核心容器
這是Spring框架最基礎(chǔ)的部分,它提供了依賴注入(Dependency Injection)特征來實現(xiàn)容器對Bean的管理。這里最基本的概念是BeanFactory,它是任何Spring應(yīng)用的核心。BeanFactory是工廠模式的一個實現(xiàn),它使用IoC將應(yīng)用配置和依賴說明從實際的應(yīng)用代碼中分離出來。
應(yīng)用上下文(Context)模塊
核心模塊的BeanFactory使Spring成為一個容器,而上下文模塊使它成為一個框架。這個模塊擴(kuò)展了BeanFactory的概念,增加了對國際化(I18N)消息、事件傳播以及驗證的支持。
另外,這個模塊提供了許多企業(yè)服務(wù),例如電子郵件、JNDI訪問、EJB集成、遠(yuǎn)程以及時序調(diào)度(scheduling)服務(wù)。也包括了對模版框架例如Velocity和FreeMarker集成的支持。
Spring的AOP模塊
Spring在它的AOP模塊中提供了對面向切面編程的豐富支持。這個模塊是在Spring應(yīng)用中實現(xiàn)切面編程的基礎(chǔ)。為了確保Spring與其它AOP框架的互用性, Spring的AOP支持基于AOP聯(lián)盟定義的API。AOP聯(lián)盟是一個開源項目,它的目標(biāo)是通過定義一組共同的接口和組件來促進(jìn)AOP的使用以及不同的AOP實現(xiàn)之間的互用性。通過訪問他們的站點http://aopalliance. sourceforge.net,你可以找到關(guān)于AOP聯(lián)盟的更多內(nèi)容。
Spring的AOP模塊也將元數(shù)據(jù)編程引入了Spring。使用Spring的元數(shù)據(jù)支持,你可以為你的源代碼增加注釋,指示Spring在何處以及如何應(yīng)用切面函數(shù)。
JDBC抽象和DAO模塊
使用JDBC經(jīng)常導(dǎo)致大量的重復(fù)代碼,取得連接、創(chuàng)建語句、處理結(jié)果集,然后關(guān)閉連接。Spring的JDBC和DAO模塊抽取了這些重復(fù)代碼,因此你可以保持你的數(shù)據(jù)庫訪問代碼干凈簡潔,并且可以防止因關(guān)閉數(shù)據(jù)庫資源失敗而引起的問題。
這個模塊還在幾種數(shù)據(jù)庫服務(wù)器給出的錯誤消息之上建立了一個有意義的異常層。使你不用再試圖破譯神秘的私有的SQL錯誤消息!
另外,這個模塊還使用了Spring的AOP模塊為Spring應(yīng)用中的對象提供了事務(wù)管理服務(wù)。
對象/關(guān)系映射集成模塊
對那些更喜歡使用對象/關(guān)系映射工具而不是直接使用JDBC的人,Spring提供了ORM模塊。Spring并不試圖實現(xiàn)它自己的ORM解決方案,而是為幾種流行的ORM框架提供了集成方案,包括Hibernate、JDO和iBATIS SQL映射。Spring的事務(wù)管理支持這些ORM框架中的每一個也包括JDBC。
Spring的Web模塊
Web上下文模塊建立于應(yīng)用上下文模塊之上,提供了一個適合于Web應(yīng)用的上下文。另外,這個模塊還提供了一些面向服務(wù)支持。例如:實現(xiàn)文件上傳的multipart請求,它也提供了Spring和其它Web框架的集成,比如Struts、WebWork。
Spring的MVC框架
Spring為構(gòu)建Web應(yīng)用提供了一個功能全面的MVC框架。雖然Spring可以很容易地與其它MVC框架集成,例如Struts,但Spring的MVC框架使用IoC對控制邏輯和業(yè)務(wù)對象提供了完全的分離。
它也允許你聲明性地將請求參數(shù)綁定到你的業(yè)務(wù)對象中,此外,Spring的MVC框架還可以利用Spring的任何其它服務(wù),例如國際化信息與驗證。
總結(jié)
Spring帶來了復(fù)雜的J2EE開發(fā)的春天。它的核心是輕量級的IoC容器,它的目標(biāo)是為J2EE應(yīng)用提供了全方位的整合框架,在Spring框架下實現(xiàn)多個子框架的組合,這些子框架之間可以彼此獨立,也可以使用其它的框架方案加以代替,Spring希望為企業(yè)應(yīng)用提供一站式(one-stop shop)的解決方案。
Spring的IoC容器
Ø 主要內(nèi)容:從最基本的面向接口編程逐步引入IoC設(shè)計模式(以銀行卡:Card為例,接口-單例-工廠方法-IoC);詳細(xì)介紹IoC的三種實現(xiàn),并對其優(yōu)、缺點進(jìn)行比較;之后開始引入Spring的IoC容器,詳細(xì)介紹如何使用Spring的IoC容器組織業(yè)務(wù)組件。
Ø 目的:使學(xué)員真正理解IoC的概念、優(yōu)點,并掌握Spring IoC容器的使用。
用戶注冊的例子
我們先看看更進(jìn)一步的需求:實現(xiàn)一個用戶注冊信息持久化的類。
功能:
1、 保存用戶注冊的信息;
2、 根據(jù)用戶的名稱獲得該注冊用戶。
雖然功能簡單,但它對持久化方式的要求卻非常的靈活:
1、 在內(nèi)存中持久化,供測試、演示使用。
2、 如果用戶的數(shù)據(jù)很少,將用戶信息持據(jù)化到文本文件中。
3、 如果用戶信息很多,并需要一些靈活的查詢,則需要使用JDBC技術(shù)將用將用戶信息持久化到數(shù)據(jù)庫中。
4、 面對企業(yè)復(fù)雜關(guān)聯(lián)的數(shù)據(jù),甚至需要使用持久層框架來實現(xiàn)用戶信息的持久化,比如:iBATIS、Hibernate等。
如何去設(shè)計、實現(xiàn)我們這個持久化類呢?
我們遵循軟件開發(fā)的原則“首先讓它跑起來,再去優(yōu)化(重構(gòu))它”,我們首先實現(xiàn)最簡單的在內(nèi)存中持久化用戶信息。
既然我們要保存和取得用戶信息,首先應(yīng)該設(shè)計用戶類。代碼如下:
User.java
public class User {
private Long id;
private String name;
private String password;
private String group;
public User(String name,String password){
this.name = name;
this.password = password;
}
//相應(yīng)的get/set方法
………..
}
持久化類有兩個方法,分別在內(nèi)存中保存和獲取User對象。代碼如下:
MemoryUserPersist.java
public class MemoryUserPersist {
private static Map users = new HashMap();
static{
User defaultAdmin = new User("Moxie","pass");
users.put(defaultAdmin.getName(),defaultAdmin);
}
public MemoryUserPersist (){
}
public void saveUser(User user){
users.put(user.getName(),user);
}
public User LoadUser(String userName){
return (User)users.get(userName);
}
}
用戶持久化類完成之后,我們就可以在客戶端UserRegister中使用它了。例如:用戶注冊時,UserRegister代碼片斷如下:
MemoryUserPersist userPersist = new MemoryUserPersist ();
userPersist.saveUser(user);
可是,現(xiàn)在如果要在文本文件中持久化User,又該如何實現(xiàn)呢?實現(xiàn)一個TextUserPersist類,這個并不困難。但客戶端代碼將面臨重大災(zāi)難:找到所有使用過MemoryUserPersist的客戶端類,將他們中的MemoryUserPersist逐個手工修改為 TextUserPersist,并且重新編譯,當(dāng)然以前的測試也必須全部從頭來過!
人生的浩劫只是剛剛開始,因為根據(jù)前面的需求我們至少要分別實現(xiàn)四種持久化方式!這時,你一定和我一樣在期待著救世主的早日降臨——接口(Interface)。
面向接口編程
什么是接口?
¨ 接口定義了行為的協(xié)議,這些行為在繼承接口的類中實現(xiàn)。
¨ 接口定義了很多方法,但是沒有實現(xiàn)它們。類履行接口協(xié)議并實現(xiàn)所有定義在接口中的方法。
¨ 接口是一種只有聲明沒有實現(xiàn)的特殊類。
接口的優(yōu)點:
¨ Client不必知道其使用對象的具體所屬類。
¨ 一個對象可以很容易地被(實現(xiàn)了相同接口的)的另一個對象所替換。
¨ 對象間的連接不必硬綁定(hardwire)到一個具體類的對象上,因此增加了靈活性。
¨ 松散藕合(loosens coupling)。
¨ 增加了重用的可能性。
接口的缺點:
設(shè)計的復(fù)雜性略有增加
(用戶持久化類)重構(gòu)第一步——面向接口編程
1、 設(shè)計用戶持久化類的接口UserDao,代碼如下:
public interface UserDao {
public void save(User user);
public User load(String name);
}
2、 具體的持久化來必須要繼承UserDao接口,并實現(xiàn)它的所有方法。我們還是首先實現(xiàn)內(nèi)存持久化的用戶類:
public class MemoryUserDao implements UserDao{
private static Map users = new HashMap();;
static{
User user = new User("Moxie","pass");
users.put(user.getName(),user);
}
public void save(User user) {
users.put(user.getId(),user);
}
public User load(String name) {
return (User)users.get(name);
}
}
MemoryUserDao的實現(xiàn)代碼和上面的MemoryUserPersist基本相同,唯一區(qū)別是MemoryUserDao類繼承了UserDao接口,它的save()和load()方法是實現(xiàn)接口的方法。
這時,客戶端UserRegister的代碼又該如何實現(xiàn)呢?
UserDao userDao = new MemoryUserDao();
userDao.save(user);
(注:面向?qū)ο?#8220;多態(tài)”的闡述)
如果我們再切換到文本的持久化實現(xiàn)TextUserDao,客戶端代碼仍然需要手工修改。雖然我們已經(jīng)使用了面向?qū)ο蟮亩鄳B(tài)技術(shù),對象userDao方法的執(zhí)行都是針對接口的調(diào)用,但userDao對象的創(chuàng)建卻依賴于具體的實現(xiàn)類,比如上面MemoryUserDao。這樣我們并沒有完全實現(xiàn)前面所說的“Client不必知道其使用對象的具體所屬類”。
如何解決客戶端對象依賴具體實現(xiàn)類的問題呢?
下面該是我們的工廠(Factory)模式出場了!
重構(gòu)第二步——工廠(Factory)模式
我們使用一個工廠類來實現(xiàn)userDao對象的創(chuàng)建,這樣客戶端只要知道這一個工廠類就可以了,不用依賴任何具體的UserDao實現(xiàn)。創(chuàng)建userDao對象的工廠類UserDaoFactory代碼如下:
public class UserDaoFactory {
public static UserDao createUserDao(){
return new MemoryUserDao();
}
}
客戶端UserRegister代碼片斷如下:
UserDao userDao = UserDaoFactory. CreateUserDao();
userDao.save(user);
現(xiàn)在如果再要更換持久化方式,比如使用文本文件持久化用戶信息。就算有再多的客戶代碼調(diào)用了用戶持久化對象我們都不用擔(dān)心了。因為客戶端和用戶持久化對象的具體實現(xiàn)完全解耦。我們唯一要修改的只是一個UserDaoFactory類。
重構(gòu)第三步——工廠(Factory)模式的改進(jìn)
到這里人生的浩劫已經(jīng)得到了拯救。但我們?nèi)圆粷M足,因為假如將內(nèi)存持久化改為文本文件持久化仍然有著硬編碼的存在——UserDaoFactory類的修改。代碼的修改就意味著重新編譯、打包、部署甚至引入新的Bug。所以,我們不滿足,因為它還不夠完美!
如何才是我們心目中的完美方案?至少要消除更換持久化方式時帶來的硬編碼。具體實現(xiàn)類的可配置不正是我們需要的嗎?我們在一個屬性文件中配置UserDao的實現(xiàn)類,例如:
在屬性文件中可以這樣配置:userDao = com.test.MemoryUserDao。UserDao的工廠類將從這個屬性文件中取得UserDao實現(xiàn)類的全名,再通過Class.forName(className).newInstance()語句來自動創(chuàng)建一個UserDao接口的具體實例。UserDaoFactory代碼如下:
public class UserDaoFactory {
public static UserDao createUserDao(){
String className = "";
// ……從屬性文件中取得這個UserDao的實現(xiàn)類全名。
UserDao userDao = null;
try {
userDao = (UserDao)Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return userDao;
}
通過對工廠模式的優(yōu)化,我們的方案已近乎完美。如果現(xiàn)在要更換持久化方式,不需要再做任何的手工編碼,只要修改配置文件中的userDao實現(xiàn)類名,將它設(shè)置為你需要更換的持久化類名即可。
我們終于可以松下一口氣了?不,矛盾仍然存在。我們引入了接口,引入了工廠模式,讓我們的系統(tǒng)高度的靈活和可配置,同時也給開發(fā)帶來了一些復(fù)雜度:1、本來只有一個實現(xiàn)類,后來卻要為這個實現(xiàn)類引入了一個接口。2、引入了一個接口,卻還需要額外開發(fā)一個對應(yīng)的工廠類。3、工廠類過多時,管理、維護(hù)非常困難。比如:當(dāng)UserDao的實現(xiàn)類是JdbcUserDao,它使用JDBC技術(shù)來實現(xiàn)用戶信息從持久化。也許要在取得JdbcUserDao實例時傳入數(shù)據(jù)庫Connection,這是仍少UserDaoFactory的硬編碼。
當(dāng)然,面接口編程是實現(xiàn)軟件的可維護(hù)性和可重用行的重要原則已經(jīng)勿庸置疑。這樣,第一個復(fù)雜度問題是無法避免的,再說一個接口的開發(fā)和維護(hù)的工作量是微不足道的。但后面兩個復(fù)雜度的問題,我們是完全可以解決的:工廠模式的終極方案——IoC模式。
重構(gòu)第四步-IoC容器
使用IoC容器,用戶注冊類UserRegister不用主動創(chuàng)建UserDao實現(xiàn)類的實例。由IoC容器主動創(chuàng)建UserDao實現(xiàn)類的實例,并注入到用戶注冊類中。我們下面將使用Spring提供的IoC容器來管理我們的用戶注冊類。
用戶注冊類UserRegister的部分代碼如下:
public class UserRegister {
private UserDao userDao = null;//由容器注入的實例對象
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
// UserRegister的業(yè)務(wù)方法
}
在其它的UserRegister方法中就可以直接使用userDao對象了,它的實例由Spring容器主動為它創(chuàng)建。但是,如何組裝一個UserDao的實現(xiàn)類到UserRegister中呢?哦,Spring提供了配置文件來組裝我們的組件。Spring的配置文件applicationContext.xml代碼片斷如下:
<bean id="userRegister" class="com.dev.spring.simple.UserRegister">
<property name="userDao"><ref local="userDao"/></property>
</bean>
<bean id="userDao" class="com.dev.spring.simple.MemoryUserDao"/>
控制反轉(zhuǎn)(IoC)/依賴注入(DI)
什么是控制反轉(zhuǎn)/依賴注入?
控制反轉(zhuǎn)(IoC=Inversion of Control)IoC,用白話來講,就是由容器控制程序之間的(依賴)關(guān)系,而非傳統(tǒng)實現(xiàn)中,由程序代碼直接操控。這也就是所謂“控制反轉(zhuǎn)”的概念所在:(依賴)控制權(quán)由應(yīng)用代碼中轉(zhuǎn)到了外部容器,控制權(quán)的轉(zhuǎn)移,是所謂反轉(zhuǎn)。
IoC也稱為好萊塢原則(Hollywood Principle):“Don’t call us, we’ll call you”。即,如果大腕明星想演節(jié)目,不用自己去找好萊塢公司,而是由好萊塢公司主動去找他們(當(dāng)然,之前這些明星必須要在好萊塢登記過)。
正在業(yè)界為IoC爭吵不休時,大師級人物Martin Fowler也站出來發(fā)話,以一篇經(jīng)典文章《Inversion of Control Containers and the Dependency Injection pattern》為IoC正名,至此,IoC又獲得了一個新的名字:“依賴注入 (Dependency Injection)”。
相對IoC 而言,“依賴注入”的確更加準(zhǔn)確的描述了這種古老而又時興的設(shè)計理念。從名字上理解,所謂依賴注入,即組件之間的依賴關(guān)系由容器在運行期決定,形象的來說,即由容器動態(tài)的將某種依賴關(guān)系注入到組件之中。
例如前面用戶注冊的例子。UserRegister依賴于UserDao的實現(xiàn)類,在最后的改進(jìn)中我們使用IoC容器在運行期動態(tài)的為UserRegister注入UserDao的實現(xiàn)類。即UserRegister對UserDao的依賴關(guān)系由容器注入,UserRegister不用關(guān)心UserDao的任何具體實現(xiàn)類。如果要更改用戶的持久化方式,只要修改配置文件applicationContext.xm即可。
依賴注入機(jī)制減輕了組件之間的依賴關(guān)系,同時也大大提高了組件的可移植性,這意味著,組件得到重用的機(jī)會將會更多。
依賴注入的三種實現(xiàn)形式
我們將組件的依賴關(guān)系由容器實現(xiàn),那么容器如何知道一個組件依賴哪些其它的組件呢?例如用戶注冊的例子:容器如何得知UserRegister依賴于UserDao呢。這樣,我們的組件必須提供一系列所謂的回調(diào)方法(這個方法并不是具體的Java類的方法),這些回調(diào)方法會告知容器它所依賴的組件。根據(jù)回調(diào)方法的不同,我們可以將IoC分為三種形式:
Type1-接口注入(Interface Injection)
它是在一個接口中定義需要注入的信息,并通過接口完成注入。Apache Avalon是一個較為典型的Type1型IOC容器,WebWork框架的IoC容器也是Type1型。
當(dāng)然,使用接口注入我們首先要定義一個接口,組件的注入將通過這個接口進(jìn)行。我們還是以用戶注冊為例,我們開發(fā)一個InjectUserDao接口,它的用途是將一個UserDao實例注入到實現(xiàn)該接口的類中。InjectUserDao接口代碼如下:
public interface InjectUserDao {
public void setUserDao(UserDao userDao);
}
UserRegister需要容器為它注入一個UserDao的實例,則它必須實現(xiàn)InjectUserDao接口。UserRegister部分代碼如下:
public class UserRegister implements InjectUserDao{
private UserDao userDao = null;//該對象實例由容器注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
// UserRegister的其它業(yè)務(wù)方法
}
同時,我們需要配置InjectUserDao接口和UserDao的實現(xiàn)類。如果使用WebWork框架則配置文件如下:
<component>
<scope>request</scope>
<class>com.dev.spring.simple.MemoryUserDao</class>
<enabler>com.dev.spring.simple.InjectUserDao</enabler>
</component>
這樣,當(dāng)IoC容器判斷出UserRegister組件實現(xiàn)了InjectUserDao接口時,它就將MemoryUserDao實例注入到UserRegister組件中。
Type2-設(shè)值方法注入(Setter Injection)
在各種類型的依賴注入模式中,設(shè)值注入模式在實際開發(fā)中得到了最廣泛的應(yīng)用(其中很大一部分得力于Spring框架的影響)。
基于設(shè)置模式的依賴注入機(jī)制更加直觀、也更加自然。前面的用戶注冊示例,就是典
型的設(shè)置注入,即通過類的setter方法完成依賴關(guān)系的設(shè)置。
Type3-構(gòu)造子注入(Constructor Injection)
構(gòu)造子注入,即通過構(gòu)造函數(shù)完成依賴關(guān)系的設(shè)定。將用戶注冊示例該為構(gòu)造子注入,UserRegister代碼如下:
public class UserRegister {
private UserDao userDao = null;//由容器通過構(gòu)造函數(shù)注入的實例對象
public UserRegister(UserDao userDao){
this.userDao = userDao;
}
//業(yè)務(wù)方法
}
幾種依賴注入模式的對比總結(jié)
接口注入模式因為歷史較為悠久,在很多容器中都已經(jīng)得到應(yīng)用。但由于其在靈活性、易用性上不如
其他兩種注入模式,因而在IOC的專題世界內(nèi)并不被看好。
Type2和Type3型的依賴注入實現(xiàn)則是目前主流的IOC實現(xiàn)模式。這兩種實現(xiàn)方式各有特點,也各具優(yōu)勢。
Type2 設(shè)值注入的優(yōu)勢
1. 對于習(xí)慣了傳統(tǒng)JavaBean開發(fā)的程序員而言,通過setter方法設(shè)定依賴關(guān)系顯得更加直觀,更加自然。
2. 如果依賴關(guān)系(或繼承關(guān)系)較為復(fù)雜,那么Type3模式的構(gòu)造函數(shù)也會相當(dāng)龐大(我們需要在構(gòu)造函數(shù)中設(shè)定所有依賴關(guān)系),此時Type2模式往往更為簡潔。
3. 對于某些第三方類庫而言,可能要求我們的組件必須提供一個默認(rèn)的構(gòu)造函數(shù)(如Struts中的Action),此時Type3類型的依賴注入機(jī)制就體現(xiàn)出其局限性,難以完成我們期望的功能。
Type3 構(gòu)造子注入的優(yōu)勢:
1. “在構(gòu)造期即創(chuàng)建一個完整、合法的對象”,對于這條Java設(shè)計原則,Type3無疑是最好的響應(yīng)者。
2. 避免了繁瑣的setter方法的編寫,所有依賴關(guān)系均在構(gòu)造函數(shù)中設(shè)定,依賴關(guān)系集中呈現(xiàn),更加易讀。
3. 由于沒有setter方法,依賴關(guān)系在構(gòu)造時由容器一次性設(shè)定,因此組件在被創(chuàng)建之后即處于相對“不變”的穩(wěn)定狀態(tài),無需擔(dān)心上層代碼在調(diào)用過程中執(zhí)行setter方法對組件依賴關(guān)系產(chǎn)生破壞,特別是對于Singleton模式的組件而言,這可能對整個系統(tǒng)產(chǎn)生重大的影響。
4. 同樣,由于關(guān)聯(lián)關(guān)系僅在構(gòu)造函數(shù)中表達(dá),只有組件創(chuàng)建者需要關(guān)心組件內(nèi)部的依賴關(guān)系。對調(diào)用者而言,組件中的依賴關(guān)系處于黑盒之中。對上層屏蔽不必要的信息,也為系統(tǒng)的層次清晰性提供了保證。
5. 通過構(gòu)造子注入,意味著我們可以在構(gòu)造函數(shù)中決定依賴關(guān)系的注入順序,對于一個大量依賴外部服務(wù)的組件而言,依賴關(guān)系的獲得順序可能非常重要,比如某個依賴關(guān)系注入的先決條件是組件的UserDao及相關(guān)資源已經(jīng)被設(shè)定。
可見,Type3和Type2模式各有千秋,而Spring、PicoContainer都對Type3和Type2類型的依賴注入機(jī)制提供了良好支持。這也就為我們提供了更多的選擇余地。理論上,以Type3類型為主,輔之以Type2類型機(jī)制作為補(bǔ)充,可以達(dá)到最好的依賴注入效果,不過對于基于Spring Framework開發(fā)的應(yīng)用而言,Type2使用更加廣泛。
BeanFactory
ApplicationContext |
BeanFactory |
BeanFactory其實是一個接口-org.springframework.beans.factory.BeanFactory,它可以配置和管理幾乎所有的Java類。當(dāng)然,具體的工作是由實現(xiàn)BeanFactory接口的實現(xiàn)類完成。我們最常用的BeanFactory實現(xiàn)是org.springframework.beans.factory.xml.XmlBeanFactory。它從XML文件中讀取Bean的定義信息。當(dāng)BeanFactory被創(chuàng)建時,Spring驗證每個Bean的配置。當(dāng)然,要等Bean創(chuàng)建之后才能設(shè)置Bean的屬性。單例(Singleton)Bean在啟動時就會被BeanFactory實例化,其它的Bean在請求時創(chuàng)建。根據(jù)BeanFactory的Java文檔(Javadocs)介紹,“Bean定義的持久化方式?jīng)]有任何的限制:LDAP、RDBMS、XML、屬性文件,等等”。現(xiàn)在Spring已提供了XML文件和屬性文件的實現(xiàn)。無疑,XML文件是定義Bean的最佳方式。
BeanFactory是初始化Bean和調(diào)用它們生命周期方法的“吃苦耐勞者”。注意,BeanFactory只能管理單例(Singleton)Bean的生命周期。它不能管理原型(prototype,非單例)Bean的生命周期。這是因為原型Bean實例被創(chuàng)建之后便被傳給了客戶端,容器失去了對它們的引用。
BeanFactory管理Bean(組件)的生命周期
下圖描述了Bean的生命周期。它是由IoC容器控制。IoC容器定義Bean操作的規(guī)則,即Bean的定義(BeanDefinition)。Bean的定義包含了BeanFactory在創(chuàng)建Bean實例時需要的所有信息。BeanFactory首先通過構(gòu)造函數(shù)創(chuàng)建一個Bean實例,之后它會執(zhí)行Bean實例的一系列之前初始化動作,初始化結(jié)束Bean將進(jìn)入準(zhǔn)備就緒(ready)狀態(tài),這時應(yīng)用程序就可以獲取這些Bean實例了。最后,當(dāng)你銷毀單例(Singleton)Bean時,它會調(diào)用相應(yīng)的銷毀方法,結(jié)束Bean實例的生命周期。
(圖-Bean的生命周期)
Bean的定義
前面的用戶注冊的例子中,我們已經(jīng)使用Spring定義了一個用戶持久化類:
<bean id="userDao" class="com.dev.spring.simple.MemoryUserDao"/>
這是一個最簡單的Bean定義。它類似于調(diào)用了語句:MemoryUserDao userDao = new MemoryUserDao()。
id屬性必須是一個有效的XML ID,這意味著它在整個XML文檔中必須唯一。它是一個Bean的“終身代號(9527)”。同時你也可以用name屬性為Bean定義一個或多個別名(用逗號或空格分開多個別名)。name屬性允許出現(xiàn)任意非法的XML字母。例如:
<bean id="userDao" name="userDao*_1, userDao*_2"
class="com.dev.spring.simple.MemoryUserDao"/>。
class屬性定義了這個Bean的全限定類名(包名+類名)。Spring能管理幾乎所有的Java類。一般情況,這個Java類會有一個默認(rèn)的構(gòu)造函數(shù),用set方法設(shè)置依賴的屬性。
Bean元素出了上面的兩個屬性之外,還有很多其它屬性。說明如下:
<bean
id="beanId"(1)
name="beanName"(2)
class="beanClass"(3)
parent="parentBean"(4)
abstract="true | false"(5)
singleton="true | false"(6)
lazy-init="true | false | default"(7)
autowire="no | byName | byType | constructor | autodetect | default"(8)
dependency-check = "none | objects | simple | all | default"(9)
depends-on="dependsOnBean"(10)
init-method="method"(11)
destroy-method="method"(12)
factory-method="method"(13)
factory-bean="bean">(14)
</bean>
(1)、id: Bean的唯一標(biāo)識名。它必須是合法的XML ID,在整個XML文檔中唯一。
(2)、name: 用來為id創(chuàng)建一個或多個別名。它可以是任意的字母符合。多個別名之間用逗號或空格分開。
(3)、class: 用來定義類的全限定名(包名+類名)。只有子類Bean不用定義該屬性。
(4)、parent: 子類Bean定義它所引用它的父類Bean。這時前面的class屬性失效。子類Bean會繼承父類Bean的所有屬性,子類Bean也可以覆蓋父類Bean的屬性。注意:子類Bean和父類Bean是同一個Java類。
(5)、abstract(默認(rèn)為”false”):用來定義Bean是否為抽象Bean。它表示這個Bean將不會被實例化,一般用于父類Bean,因為父類Bean主要是供子類Bean繼承使用。
(6)、singleton(默認(rèn)為“true”):定義Bean是否是Singleton(單例)。如果設(shè)為“true”,則在BeanFactory作用范圍內(nèi),只維護(hù)此Bean的一個實例。如果設(shè)為“flase”,Bean將是Prototype(原型)狀態(tài),BeanFactory將為每次Bean請求創(chuàng)建一個新的Bean實例。
(7)、lazy-init(默認(rèn)為“default”):用來定義這個Bean是否實現(xiàn)懶初始化。如果為“true”,它將在BeanFactory啟動時初始化所有的Singleton Bean。反之,如果為“false”,它只在Bean請求時才開始創(chuàng)建Singleton Bean。
(8)、autowire(自動裝配,默認(rèn)為“default”):它定義了Bean的自動裝載方式。
1、“no”:不使用自動裝配功能。
2、“byName”:通過Bean的屬性名實現(xiàn)自動裝配。
3、“byType”:通過Bean的類型實現(xiàn)自動裝配。
4、“constructor”:類似于byType,但它是用于構(gòu)造函數(shù)的參數(shù)的自動組裝。
5、“autodetect”:通過Bean類的反省機(jī)制(introspection)決定是使用“constructor”還是使用“byType”。
(9)、dependency-check(依賴檢查,默認(rèn)為“default”):它用來確保Bean組件通過JavaBean描述的所以依賴關(guān)系都得到滿足。在與自動裝配功能一起使用時,它特別有用。
1、none:不進(jìn)行依賴檢查。
2、objects:只做對象間依賴的檢查。
3、simple:只做原始類型和String類型依賴的檢查
4、all:對所有類型的依賴進(jìn)行檢查。它包括了前面的objects和simple。
(10)、depends-on(依賴對象):這個Bean在初始化時依賴的對象,這個對象會在這個Bean初始化之前創(chuàng)建。
(11)、init-method:用來定義Bean的初始化方法,它會在Bean組裝之后調(diào)用。它必須是一個無參數(shù)的方法。
(12)、destroy-method:用來定義Bean的銷毀方法,它在BeanFactory關(guān)閉時調(diào)用。同樣,它也必須是一個無參數(shù)的方法。它只能應(yīng)用于singleton Bean。
(13)、factory-method:定義創(chuàng)建該Bean對象的工廠方法。它用于下面的“factory-bean”,表示這個Bean是通過工廠方法創(chuàng)建。此時,“class”屬性失效。
(14)、factory-bean:定義創(chuàng)建該Bean對象的工廠類。如果使用了“factory-bean”則“class”屬性失效。
配置Bean的屬性值和Bean對象的組裝
我們可以在Spring的配置文件中直接設(shè)置Bean的屬性值。例如:你的Bean有一個“maxSize”屬性,它表示每頁顯示數(shù)據(jù)的最大值,它有一個set方法。代碼如下:
private int maxSize;
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
這樣,你可以在Bean定義時設(shè)置這個屬性的值:
<property name="maxSize"><value>20</value></property>
前面介紹了Bean原始類型的屬性設(shè)置。這種方式已經(jīng)可以非常有效而便利的參數(shù)化應(yīng)用對象。然而,Bean工廠的真正威力在于:它可以根據(jù)bean屬性中描述的對象依賴來組裝(wire)bean實例。例如:userDao對象的一個屬性“sessionFactory”引用了另外一個Bean對象,即userDao對象實例依賴于sessionFactory對象:
<bean id="userDao" class="com.dev.spring.simple.HibernateUserDao">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
…..
</bean>
在這個簡單的例子中,使用<ref>元素引用了一個sessionFactory實例。在ref標(biāo)簽中,我們使用了一個“local”屬性指定它所引用的Bean對象。除了local屬性之外,還有一些其它的屬性可以用來指定引用對象。下面列出<ref>元素的所有可用的指定方式:
bean:可以在當(dāng)前文件中查找依賴對象,也可以在應(yīng)用上下文(ApplicationContext)中查找其它配置文件的對象。
local:只在當(dāng)前文件中查找依賴對象。這個屬性是一個XML IDREF,所以它指定的對象必須存在,否則它的驗證檢查會報錯。
external:在其它文件中查找依賴對象,而不在當(dāng)前文件中查找。
總的來說,<ref bean="..."/>和<ref local="..."/>大部分的時候可以通用。“bean”是最靈活的方式,它允許你在多個文件之間共享Bean。而“local”則提供了便利的XML驗證。
復(fù)雜的屬性值
Spring的bean工廠不僅允許用String值和其他bean的引用作為bean組件的屬性值,還支持更復(fù)雜的值,例如數(shù)組、java.util.List、java.util.Map和java.util.Properties。數(shù)組、set、list和map中的值不僅可以是String類型,也可以是其他bean的引用;map中的鍵、Properties的鍵和值都必須是String類型的;map中的值可以是set、list或者map類型。
例如:
Null:
<property name=“bar”><null/></property>
List和數(shù)組:
<property name=“bar”>
<list>
<value>ABC</value>
<value>123</value>
</list>
</property>
Map:
<property name=“bar”>
<map>
<entry key=“key1”><value>ABC</value></entry>
<entry key=“key2”><value>123</value></entry>
</set>
</property>
Bean的之前初始化
Bean工廠使用Bean的構(gòu)造函數(shù)創(chuàng)建Bean對象之后,緊接著它會做一件非常重要的工作——Bean的初始化。它會根據(jù)配置信息設(shè)置Bean的屬性和依賴對象,執(zhí)行相應(yīng)的初始化方法。
自動裝配
一般不推薦在大型的應(yīng)用系統(tǒng)中使用自動裝配。當(dāng)然,它可以很好的用于小型應(yīng)用系統(tǒng)。如果一個bean聲明被標(biāo)志為“autowire(自動裝配)”,bean工廠會自動將其他的受管對象與其要求的依賴關(guān)系進(jìn)行匹配,從而完成對象的裝配——當(dāng)然,只有當(dāng)對象關(guān)系無歧義時才能完成自動裝配。因為不需要明確指定某個協(xié)作對象,所以可以帶來很多的便利性。
舉個例子,如果在Bean工廠中有一個SessionFactory類型的實例,HibernateUserDao的sessionFactory屬性就可以獲得這個實例。這里可以使用<bean>的autowire屬性,就象這樣:
<bean id="userDao" class="com.dev.spring.simple.HibernateUserDao" autowire=”byType”>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
…..
</bean>
注意,在userDao的定義中并沒有明確引用sessionFactory。由于它的“autowire”被設(shè)置為“byType”,所有只要userDao有一個類型為SessionFactory的屬性并有一個set方法,Bean工廠就會自動將sessionFactory組裝到userDao中。
如果一個應(yīng)用有兩個數(shù)據(jù)庫,這時就對應(yīng)有兩個SessionFactory。這時autowire="byType"就無法使用了。我們可以使用另外一種自動裝配方式“byName”。它將根據(jù)屬性的名稱來匹配依賴對象,這樣如果你的配置文件中可以同時存在多個類型相同的SessionFactory,只要他們定義的名稱不同就可以了。這種方式的缺點是:需要精確匹配Bean的名稱,即必須要保證屬性的名稱和它所依賴的Bean的名稱相等,這樣比較容易出錯。
(舉例:Aa對象依賴Bb對象。)
注意:我們還是強(qiáng)烈推薦手工指定Bean之間的依賴關(guān)系。這種用法最強(qiáng)大,因為它允許按名稱引用特定的Bean實例,即使多個Bean具有相同類型也不會混淆。同時,你可以清楚的知道一個Bean到底依賴哪些其它的Bean。如果使用自動裝載,你只能去Bean的代碼中了解。甚至,Bean工廠也許會自動裝載一些你根本不想依賴的對象。
依賴檢查
如果你希望Bean嚴(yán)格的設(shè)置所有的屬性,“dependency-check”(依賴檢查)屬性將會非常有用。它默認(rèn)為“none”,不進(jìn)行依賴檢查。“simple”會核對所有的原始類型和String類型的屬性。“objects”只做對象間的關(guān)聯(lián)檢查(包括集合)。“all”會檢查所有的屬性,包括“simple”和“objects”。
舉個例子:一個Bean有如下的一個屬性:
private int intVar = 0;
public void setIntVar(int intVar) {
this.intVar = intVar;
}
這個Bean的配置文件設(shè)置了“dependency-check=”simple””。如果這個Bean的配置中沒有定義這個屬性“intVar”,則在進(jìn)行這個Bean的依賴檢查時就會拋出異常:org.springframework.beans.factory.UnsatisfiedDependencyException。
setXXX()
set方法非常簡單,它會給class注入所有依賴的屬性。這些屬性都必須是在配置文件中使用<property>元素定義,它們可以是原始類型,對象類型(Integer,Long),null值,集合,其它對象的引用。
afterPropertiesSet()
有兩種方法可以實現(xiàn)Bean的之前初始化方法。1、使用“init-method”屬性,在Spring的配置文件中定義回調(diào)方法。下面將會具體描述。2、實現(xiàn)接口InitializingBean并實現(xiàn)它的afterPropertiesSet()方法。接口InitializingBean的代碼如下:
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
在JavaBean的所有屬性設(shè)置完成以后,容器會調(diào)用afterPropertiesSet()方法,應(yīng)用對象可以在這里執(zhí)行任何定制的初始化操作。這個方法允許拋出最基本的Exception異常,這樣可以簡化編程模型。
在Spring框架內(nèi)部,很多bean組件都實現(xiàn)了這些回調(diào)接口。但我們的Bean組件最好不要通過這種方式實現(xiàn)生命周期的回調(diào),因為它依賴于Spring的API。無疑,第一種方法是我們的最佳選擇。
init-method
init-method的功能和InitializingBean接口一樣。它定義了一個Bean的初始化方法,在Bean的所有屬性設(shè)置完成之后自動調(diào)用。這個初始化方法不用依賴于Spring的任何API。它必須是一個無參數(shù)的方法,可以拋出Exception。
例如:我們的Bean組件UserManger中定義一個初始化方法init()。這樣,我們就可以在Bean定義時指定這個初始化方法:
<bean id=”userManger” class=”com.dev.spring.um.DefaultUserManager”
init-method=”init”>
……
</bean>
setBeanFactory()
Bean的準(zhǔn)備就緒(Ready)狀態(tài)
Bean完成所有的之前初始化之后,就進(jìn)入了準(zhǔn)備就緒(Ready)狀態(tài)。這就意味著你的應(yīng)用程序可以取得這些Bean,并根據(jù)需要使用他們。
Bean的銷毀
在你關(guān)閉(或重啟)應(yīng)用程序時,單例(Singleton)Bean可以再次獲得生命周期的回調(diào),你可以在這時銷毀Bean的一些資源。第一種方法是實現(xiàn)DisposableBean接口并實現(xiàn)它的destroy()方法。更好的方法是用“destroy-method”在Bean的定義時指定銷毀方法。