Spring如何實現IOC與AOP的
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 的下載地址是:
下載下來,解壓後,你會發現裡面有很多 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開發、架構的方向。