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