Spring如何實現IOC與AOP的

轉載地址:http://blog.csdn.net/runming56/article/details/7241417

1、Spring IOC
    IoC 與 DI
   首先想說說 IoC ( Inversion of Control ,控制倒轉)。這是 spring 的核心,貫穿始終。所謂 IoC ,對於 spring 框架來說,就是由 spring 來負責控制對象的生命週期和 對象間的關係。這是什麼意思呢,舉個簡單的例子,我們是如何找女朋友的?常見的情況是,我們到處去看哪裏有長得漂亮身材又好的 mm ,然後打聽她們的興趣愛 好、 qq 號、電話號、 ip 號、 iq 號 ……… ,想辦法認識她們,投其所好送其所要,然後嘿嘿 …… 這個過程是複雜深奧的,我們必須自己設計和麪對每個環節。傳 統的程序開發也是如此,在一個對象中,如果要使用另外的對象,就必須得到它(自己 new 一個,或者從 JNDI 中查詢一個),使用完之後還要將對象銷燬(比 如Connection 等),對象始終會和其他的接口或類藕合起來。
  那麼 IoC 是如何做的呢?有點像通過婚介找女朋友,在我和女朋友之間引入了一個第三者:婚姻介紹所。婚介管理了很多男男女女的資料,我可以向婚 介提出一個列表,告訴它我想找個什麼樣的女朋友,比如長得像李嘉欣,身材像林熙雷,唱歌像周杰倫,速度像卡洛斯,技術像齊達內之類的,然後婚介就會按照我 們的要求,提供一個 mm ,我們只需要去和她談戀愛、結婚就行了。簡單明瞭,如果婚介給我們的人選不符合要求,我們就會拋出異常。整個過程不再由我自己控 制,而是有婚介這樣一個類似容器的機構來控制。 Spring所倡導的開發方式就是如此,所有的類都會在 spring 容器中登記,告訴 spring 你是個什 麼東西,你需要什麼東西,然後 spring 會在系統運行到適當的時候,把你要的東西主動給你,同時也把你交給其他需要你的東西。所有的類的創建、銷燬都由 spring 來控制,也就是說控制對象生存週期的不再是引用它的對象,而是 spring 。對於某個具體的對象而言,以前是它控制其他對象,現在是所有對象 都被 spring 控制,所以這叫控制反轉。如果你還不明白的話,我決定放棄。
IoC 的一個重點是在系統運行中,動態的向某個對象提供它所需要的其他對象。這一點是通過 DI ( Dependency Injection ,依賴注入)來實現的。比如對象 A 需要操作數據庫,以前我們總是要在 A 中自己編寫代碼來獲得一個 Connection 對象,有了 spring 我們就只需要告訴 spring , A 中需要一個 Connection ,至於這個 Connection 怎麼構造,何時構造, A 不需要知道。在系統 運行時,spring 會在適當的時候製造一個 Connection ,然後像打針一樣,注射到 A 當中,這樣就完成了對各個對象之間關係的控制。 A 需要依賴 Connection 才能正常運行,而這個 Connection 是由 spring 注入到 A 中的,依賴注入的名字就這麼來的。那麼 DI 是如何實現的呢? Java 1.3 之後一個重要特徵是反射( reflection ),它允許程序在運行的時候動態的生成對象、執行對象的方法、改變對象的屬性, spring 就是通過反射來實現注入的。關於反射的相關資料請查閱 java doc 。
 
IoC 是一個很大的概念,可以用不同的方式來實現。主要的實現形式有兩種 :
依賴查找:容器提供回調接口和上下文環境給組件。 EJB 和 Apache Avalon 都是使用這種方式。
依賴注入:組件不做定位查詢,只是提供普通的 Java 方法讓容器去決定依賴關係。容器全權負責組件的裝配,它會把符合依賴關係的對象通過 JavaBean 屬性或者構造子傳遞給需要的對象。通過 JavaBean 屬性注射依賴關係的做法稱爲設值方法注入( Setter Injection );將依賴關係作爲構造子參數傳入的做法稱爲構造子注入( Constructor Injection )。
附圖說明:
 

   
到這裏,大家應該對 IoC 與 DI 都有了初步的認識了。其實就 Spring 來說,就是 JavaBean 由 Spring 來管理組裝,表面上看就少了幾個 new 字,其實就是爲了降低耦合度,這也是我們做軟件的目標之一。
至於 Spring 是怎樣實現 IoC 的, 《 expert one-on-one J2EE Development without EJB 中文版》第七章“ Spring 框架介紹”很詳細的列舉了多種方法。說實在,一下子看這麼多,我真有點糊塗了。我還是自己寫個 Demo 熟悉一下大名鼎鼎的 Spring 吧。
首先得下載 Spring 。 Spring 網上有兩種 Spring 包一種是 spring-framework-1.2.6-with-dependencies.zip ,另一種是spring-framework-1.2.6.zip 。當然最好是下載 spring-framework-1.2.6-with-dependencies.zip 形式的,因爲裏面包括了更多的東東。 spring-framework-1.2.6-with-dependencies.zip 的下載地址是:http://prdownloads.sourceforge.net/springframework/spring-framework-1.2.6-with-dependencies.zip 。
下載下來,解壓後,你會發現裏面有很多 jar 文件。因爲剛剛接觸 Spring ,因此我只需要 spring-core.jar ( spring-framework-1.2.6\dist ),將其導入 eclipse 的構建路徑中。另外, log 日誌是需要的,這也是爲了養成良好的編程習慣。將 log4j-1.2.9.jar ( spring-framework-1.2.6\lib\log4j )和 commons-logging.jar ( spring-framework-1.2.6\lib\jakarta-commons )導入到構建路徑中。
準備就緒,開始寫 Demo 了。
我的工程的結構是:
   
<!-- [if !supportLists]-->1、 <!-- [endif]-->log4j.properties 代碼:
<!-- <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->log4j.rootLogger = Debug, stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern =% c{ 1 }  -   % m % n
 
如何使用 Log4j ,請看我的另一篇轉貼的文章: 如何使用 Log4J 。
 
<!-- [if !supportLists]-->2、 <!-- [endif]-->HelloBean 的代碼:
<!-- <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->package  com;

public   class  HelloBean {
     private  String helloworld = " Hello!World! " ;
    
     public  String getHelloworld() {
         return  helloworld;
    }
    
     public   void  setHelloworld(String helloworld) {
         this .helloworld  =  helloworld;
    }
}

 
這是一個簡單的 JavaBean ,有個 String 類型的 helloworld 屬性,初始值是 "Hello!World!" 。  
<!-- [if !supportLists]-->3、 <!-- [endif]-->Bean.xml 代碼:
<!-- <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><? xml version = " 1.0 "  encoding = " GBK " ?> 
<! DOCTYPE beans PUBLIC  " -//SPRING/DTD BEAN/EN "  
     " http://www.springframework.org/dtd/spring-beans.dtd " >

< beans > 
    < bean id = " helloBean "   class = " com.HelloBean " > 
         < property name = " helloworld " > 
             < value > Hello ! Rick </ value > 
         </ property > 
    </ bean > 
</ beans >
 
        Spirng重點之一就是配置文件,上面是個相當簡單的配置文件,我想大家都應該看得懂。最後就是寫應用程序了。
4、 <!-- [endif]-->Test 的代碼:
 
<!-- <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->package  com;

import  org.springframework.beans.factory. * ;
import  org.springframework.beans.factory.xml.XmlBeanFactory;
import  org.springframework.core.io.ClassPathResource;
import  org.springframework.core.io.Resource;

public   class  Test {

     public   static   void  main(String[] args) {
         // 實例化JavaBean,主要是爲了比較new對象和依賴注入兩者的區別 
        HelloBean hellobean = new  HelloBean();
        System.out.println(hellobean.getHelloworld());
        
         // 通過Spring訪問JavaBean組件 
        Resource resource = new  ClassPathResource( " com/bean.xml " );
        BeanFactory factory = new  XmlBeanFactory(resource);
        hellobean = (HelloBean)factory.getBean( " helloBean " );
        System.out.println(hellobean.getHelloworld());
    }
}
    這個Demo很好的闡述了Spring的Ioc,其實就Spring而言,就是通過配置文件,讓Spring如同一個管家一樣來管理所有的Bean類。

    Spring的依賴注入相對複雜一點,主要是明白調用別的Bean,不是通過實例化對象來調用,而是告訴Spring,我需要什麼Bean,然後Spring再向你的Bean裏面注入你所需要的Bean對象。
    接下來說說代碼實現,我只是在剛剛的例子上再添加一點東東。
    首先要增加一個HelloImp的接口,這是問什麼呢,那你得問Spring,它定的規矩:JavaBean的實現要有兩個部分,一個接口,一個默認實現。你不照做就不行。
    HelloImp代碼:
     
<!-- <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->package  com;

public   interface  HelloImp {
     public   void  getName();

    
    實現HelloImp的Hello代碼:
  
<!-- <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->package  com;

public   class  Hello  implements  HelloImp {
     public   void  getName(){
        System.out.println( " Jack " );
    }
}
          
    接着就是在 HelloBean 中調用 Hello 了。 Spring 的不同之處也在這體現出來。
<!-- <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->package  com;

public   class  HelloBean {
     private  String helloworld = " Hello!World! " ;
     private  HelloImp hello;    // 注意這個私有對象是藉口

     public  String getHelloworld() {
         return  helloworld;
    }
    
     public   void  setHelloworld(String helloworld) {
         this .helloworld  =  helloworld;
    }
    
     public   void  setHello(HelloImp hello) {
         this .hello  =  hello;
    }
    
   public   void  get(){
         this .hello.getName();
   } 
}
    注意字體加粗的地方。

    配置文件也需要增加一點東西:
   
<!-- <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><? xml version = " 1.0 "  encoding = " GBK " ?> 
<! DOCTYPE beans PUBLIC  " -//SPRING/DTD BEAN/EN "  
     " http://www.springframework.org/dtd/spring-beans.dtd " >

< beans > 
<! —注意引用的類是具體的類Hello --> 
    < bean id = " myHello "   class = " com.Hello " > 
    </ bean > 
    < bean id = " helloBean "   class = " com.HelloBean " > 
         < property name = " helloworld " > 
             < value > Hello ! Rick </ value > 
         </ property > 
        < property name = " hello " > 
            < ref bean = " myHello " ></ ref > 
        </ property > 
    </ bean > 
</ beans >

    注意字體加粗的部分。

    最後在 Test 中添加一句 hellobean.get(); 就可以看到結果了。

<!-- <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->package  com;

import  org.springframework.beans.factory. * ;
import  org.springframework.beans.factory.xml.XmlBeanFactory;
import  org.springframework.core.io.ClassPathResource;
import  org.springframework.core.io.Resource;

public   class  Test {

     public   static   void  main(String[] args) {
        HelloBean hellobean = new  HelloBean();
        System.out.println(hellobean.getHelloworld());
        
        Resource resource = new  ClassPathResource( " com/bean.xml " );
        BeanFactory factory = new  XmlBeanFactory(resource);
        
        hellobean = (HelloBean)factory.getBean( " helloBean " );
        System.out.println(hellobean.getHelloworld());
        hellobean.get(); 
    }
}

到這,Spring的IoC和DI總算有了一定的認識,也算是敲開了Spring的大門了。
 
 
2、Spring AOP
  2.1 面向切面編程基礎
  通常,系統由很多組件組成,每個組件負責一部分功能,然而,這些組件也經常帶有一些除了 核心功能之外的附帶功能 。系統服務如日誌、事務管理和安全經常融入到一些其他功能模塊中。這些系統服務通常叫做交叉業務,這是因爲它們總是分佈在系統的很多組件中。通過將這些業 務分佈在多個組件中,給我們的代碼引入了雙重複雜性。
  (1) 實現系統級業務的代碼在多個組件中複製。這意味着如果你要改變這些業務邏輯,你就必須到各個模塊去修改。就算把這些業務抽象成一個獨立模塊,其它模塊只是調用它的一個方法,但是這個方法調用也還是分佈在很多地方。
  (2) 組件會因爲那些與自己核心業務無關的代碼變得雜亂。一個向地址錄中添加條目的方法應該只關心如何添加地址,而不是關心它是不是安全或支持事務的。
  此時,我們該怎麼辦呢?這正是AOP用得着的地方。AOP幫助我們將這些服務模塊化,並把它們聲明式地應用在需要它們的地方,使得這些組件更加專注於自身業務,完全不知道其它涉及到的系統服務。
  這裏的概念切面,就是我們要實現的交叉功能,是應用系統模塊化的一個方面或領域。切面的 最常見例子就是日誌記錄。日誌記錄在系統中到處需要用到,利用繼承來重用日誌模塊是不合適的,這樣,就可以創建一個日誌記錄切面,並且使用AOP在系統中 應用。下圖展示了切面應用方式
 
圖表 1 應用切面
  其中,通知Advice是切面的實際實現。連接點Joinpoint是應用程序執行過程 中插入切面的地點,這個地點可以是方法調用,異常拋出,甚至可以是要修改的字段,切面代碼在這些地方插入到你的應用流程中,添加新的行爲。切入點 Pointcut定義了Advice應該應用在那些連接點,通常通過指定類名和方法名,或者匹配類名和方法名式樣的正則表達式來指定切入點。
  2.2 AOP在Spring中的實現
  基於AOP,業界 存在各種各樣的AOP實現,比如,JBoss AOP、Spring AOP、ASP ectJ、 Aspect Werkz等。各自實現的功能也不一樣。AOP實現的強弱在很大程度上取決於連接點模型。目前,Spring只支持方法級的連接點。這和一些其他AOP框 架不一樣,如AspectJ和JBoss,它們還提供了屬性接入點,這樣可以防止你創建特別細緻的通知,如對更新對象屬性值進行攔截。然而,由於 Spring關注於提供一個實現J2EE 服務的框架,所以方法攔截可以滿足大部分要求,而且Spring的觀點是屬性攔截破壞了封裝,讓Advice觸發在屬性值改變而不是方法調用上無疑是破壞了這個概念。
  Spring的AOP框架的關鍵點如下:
  (1)Spring實現了AOP聯盟接口。在Spring AOP中,存在如下幾種通知(Advice)類型
  Before通知:在目標方法被調用前調用,涉及接口org.springFramework .aop.MethodBeforeAdvice;
  After通知:在目標方法被調用後調用,涉及接口爲org.springframework.aop.AfterReturningAdvice;
  Throws通知:目標方法拋出異常時調用,涉及接口org.springframework.aop.MethodBeforeAdvice;
  Around通知:攔截對目標對象方法調用,涉及接口爲org.aopalliance.intercept.MethodInterceptor。
  (2)用Java 編寫Spring通知,並在Spring的配置文件中,定義在什麼地方應用通知的切入點。
  (3)Spring的運行時通知對象。代理Bean只有在第一次被應用系統需要的時候才 被創建。如果你使用的是ApplicationContext,代理對象在BeanFactory載入所有Bean的時候被創建。Spring有兩種代理 創建方式。如果目標對象實現了一個或多個接口暴露的方法,Spring將使用JDK 的 java.lang.reflect.Proxy類創建代理。這個類讓Spring動態產生一個新的類,它實現所需的接口,織入了通知,並且代理對目標對 象的所有請求。如果目標對象沒有實現任何接口,Spring使用CGLIB庫生成目標對象的子類。在創建這個子類的時候,Spring將通知織入,並且將 對目標對象的調用委託給這個子類。此時,需要將Spring發行包lib/cglib目錄下的JAR文件發佈到應用系統中。
  2.3 Spring AOP的優勢
  藉助於Spring AOP,Spring IoC能夠很方便的使用到非常健壯、靈活的企業級服務,是因爲Spring AOP能夠提供如下幾方面的優勢:
  (1)允許開發者使用聲明式企業服務,比如事務服務、安全性服務;EJB 開發者都知道,EJB組件能夠使用J2EE容器提供的聲明式服務,但是這些服務要藉助於EJB容器,而Spring AOP卻不需要EJB容器,藉助於Spring的事務抽象框架就可以這些服務。
  (2)開發者可以開發滿足業務需求的自定義切面;
  (3)開發Spring AOP Advice很方便。因爲這些AOP Advice僅是POJO類,藉助於Spring提供的ProxyFactoryBean,能夠快速的搭建Spring AOP Advice。
  3、結語
  本文詳細闡述了Spring背後的IoC原理和AOP技術,以實際成功項目爲背景,抽取 簡短片斷,展示了Spring架構J2EE應用系統的原理。Spring IoC藉助於依賴注入機制,減輕了組件之間的依賴關係,同時也大大提高了組件的可移植性,組件得到了更多的重用機會。藉助於Spring AOP,使得開發者能聲明式、基於元數據訪問企業級服務,AOP合理補充了OOP技術,Spring AOP合理地補充了Spring IoC容器。Spring IoC與Spring AOP組合,使得Spring成爲成功的J2EE架構框架,並能與標準的EJB等標準對抗,EJB不再是必需品。Spring已經衝入了J2EE的核心, 將引領整個J2EE開發、架構的方向。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章