shenang博客技術(shù)文檔


          理論不懂就實踐,實踐不會就學(xué)理論!

          posts - 35,comments - 55,trackbacks - 0
           

          Spring框架概述

          Ø         主要內(nèi)容:介紹Spring的歷史,Spring的概論和它的體系結(jié)構(gòu),重點闡述它在J2EE中扮演的角色。

          Ø         目 的:全面的了解Spring框架,知道Spring框架所提供的功能,并能將Spring框架和其它框架(WebWork/Strutshibernate)區(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)為IoCJNDI相反——不是對象從容器中查找依賴,而是容器在對象初始化時不等對象請求就主動將依賴傳遞給它。

               面向切面——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

          20032Spring框架正式成為一個開源項目,并發(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ī)范的一部分,沒有通過JCPJava 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)容確實很多(例如:提供了對jdbcORM、遠(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ù)。也包括了對模版框架例如VelocityFreeMarker集成的支持。

          SpringAOP模塊

              Spring在它的AOP模塊中提供了對面向切面編程的豐富支持。這個模塊是在Spring應(yīng)用中實現(xiàn)切面編程的基礎(chǔ)。為了確保Spring與其它AOP框架的互用性, SpringAOP支持基于AOP聯(lián)盟定義的APIAOP聯(lián)盟是一個開源項目,它的目標(biāo)是通過定義一組共同的接口和組件來促進(jìn)AOP的使用以及不同的AOP實現(xiàn)之間的互用性。通過訪問他們的站點http://aopalliance. sourceforge.net,你可以找到關(guān)于AOP聯(lián)盟的更多內(nèi)容。

          SpringAOP模塊也將元數(shù)據(jù)編程引入了Spring。使用Spring的元數(shù)據(jù)支持,你可以為你的源代碼增加注釋,指示Spring在何處以及如何應(yīng)用切面函數(shù)。

          JDBC抽象和DAO模塊

          使用JDBC經(jīng)常導(dǎo)致大量的重復(fù)代碼,取得連接、創(chuàng)建語句、處理結(jié)果集,然后關(guān)閉連接。SpringJDBCDAO模塊抽取了這些重復(fù)代碼,因此你可以保持你的數(shù)據(jù)庫訪問代碼干凈簡潔,并且可以防止因關(guān)閉數(shù)據(jù)庫資源失敗而引起的問題。

          這個模塊還在幾種數(shù)據(jù)庫服務(wù)器給出的錯誤消息之上建立了一個有意義的異常層。使你不用再試圖破譯神秘的私有的SQL錯誤消息!

          另外,這個模塊還使用了SpringAOP模塊為Spring應(yīng)用中的對象提供了事務(wù)管理服務(wù)。

          對象/關(guān)系映射集成模塊

          對那些更喜歡使用對象/關(guān)系映射工具而不是直接使用JDBC的人,Spring提供了ORM模塊。Spring并不試圖實現(xiàn)它自己的ORM解決方案,而是為幾種流行的ORM框架提供了集成方案,包括HibernateJDOiBATIS SQL映射。Spring的事務(wù)管理支持這些ORM框架中的每一個也包括JDBC

          SpringWeb模塊

          Web上下文模塊建立于應(yīng)用上下文模塊之上,提供了一個適合于Web應(yīng)用的上下文。另外,這個模塊還提供了一些面向服務(wù)支持。例如:實現(xiàn)文件上傳的multipart請求,它也提供了Spring和其它Web框架的集成,比如StrutsWebWork

          SpringMVC框架

          Spring為構(gòu)建Web應(yīng)用提供了一個功能全面的MVC框架。雖然Spring可以很容易地與其它MVC框架集成,例如Struts,但SpringMVC框架使用IoC對控制邏輯和業(yè)務(wù)對象提供了完全的分離。

          它也允許你聲明性地將請求參數(shù)綁定到你的業(yè)務(wù)對象中,此外,SpringMVC框架還可以利用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)行比較;之后開始引入SpringIoC容器,詳細(xì)介紹如何使用SpringIoC容器組織業(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)用戶信息的持久化,比如:iBATISHibernate等。

          如何去設(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.MemoryUserDaoUserDao的工廠類將從這個屬性文件中取得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 ControlIoC,用白話來講,就是由容器控制程序之間的(依賴)關(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)類。即UserRegisterUserDao的依賴關(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是一個較為典型的Type1IOC容器,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)并不被看好。

          Type2Type3型的依賴注入實現(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è)定。

          可見,Type3Type2模式各有千秋,而SpringPicoContainer都對Type3Type2類型的依賴注入機(jī)制提供了良好支持。這也就為我們提供了更多的選擇余地。理論上,以Type3類型為主,輔之以Type2類型機(jī)制作為補(bǔ)充,可以達(dá)到最好的依賴注入效果,不過對于基于Spring Framework開發(fā)的應(yīng)用而言,Type2使用更加廣泛。

          BeanFactory

          ApplicationContext

          BeanFactorySpring的“心臟”。它就是Spring IoC容器的真面目。Spring使用BeanFactory來實例化、配置和管理Bean。但是,在大多數(shù)情況我們并不直接使用BeanFactory,而是使用ApplicationContext。它也是BeanFactory的一個實現(xiàn),但是它添加了一系列“框架”的特征,比如:國際化支持、資源訪問、事件傳播等。ApplicationContext我們將在后面章節(jié)中介紹。

          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ù)BeanFactoryJava文檔(Javadocs)介紹,“Bean定義的持久化方式?jīng)]有任何的限制:LDAPRDBMSXML、屬性文件,等等”。現(xiàn)在Spring已提供了XML文件和屬性文件的實現(xiàn)。無疑,XML文件是定義Bean的最佳方式。

          BeanFactory是初始化Bean和調(diào)用它們生命周期方法的“吃苦耐勞者”。注意,BeanFactory只能管理單例(SingletonBean的生命周期。它不能管理原型(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)你銷毀單例(SingletonBean時,它會調(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)行檢查。它包括了前面的objectssimple

          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屬性中描述的對象依賴來組裝wirebean實例。例如: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ù)雜的屬性值

          Springbean工廠不僅允許用String值和其他bean的引用作為bean組件的屬性值,還支持更復(fù)雜的值,例如數(shù)組、java.util.Listjava.util.Mapjava.util.Properties。數(shù)組、setlistmap中的值不僅可以是String類型,也可以是其他bean的引用;map中的鍵、Properties的鍵和值都必須是String類型的;map中的值可以是setlist或者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類型的實例,HibernateUserDaosessionFactory屬性就可以獲得這個實例。這里可以使用<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),因為它依賴于SpringAPI。無疑,第一種方法是我們的最佳選擇。

          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)用程序時,單例(SingletonBean可以再次獲得生命周期的回調(diào),你可以在這時銷毀Bean的一些資源。第一種方法是實現(xiàn)DisposableBean接口并實現(xiàn)它的destroy()方法。更好的方法是用“destroy-method”在Bean的定義時指定銷毀方法。

          ApplicationContext

          posted on 2009-03-20 15:40 重慶理工小子 閱讀(1022) 評論(2)  編輯  收藏 所屬分類: Spring2

          FeedBack:
          # re: Sping入門(轉(zhuǎn))
          2011-02-28 22:28 | spare_H
          好軟文~~  回復(fù)  更多評論
            
          # re: Sping入門(轉(zhuǎn))
          2011-03-01 20:02 | 初學(xué)SPRING
          絕對經(jīng)典,頂  回復(fù)  更多評論
            
          主站蜘蛛池模板: 曲阳县| 湖北省| 汕尾市| 靖安县| 石门县| 静安区| 台东县| 陆丰市| 墨竹工卡县| 德保县| 廉江市| 兴宁市| 监利县| 邹平县| 永城市| 中江县| 壤塘县| 北宁市| 卓资县| 塔河县| 丰原市| 永仁县| 云浮市| 象山县| 松桃| 淅川县| 嵊泗县| 稻城县| 常山县| 黔西县| 长宁区| 信宜市| 邻水| 休宁县| 嘉鱼县| 宣武区| 阿拉善左旗| 桦川县| 泸定县| 永善县| 简阳市|