天天看點

Spring中的AOP(一)——AspectJ的基本使用

    aop(aspect orient programming),也就是面向切面程式設計,作為面向對象程式設計的一種補充,目前已經成為一種比較成熟的程式設計思想,其實aop問世的時間并不長,甚至在國内的翻譯還不太統一(另有人翻譯為“面向方面程式設計”)。aop和oop(object orient programming,面向對象程式設計)互為補充,oop将程式分解成各個層次的對象,而aop則将程式運作過程分解成各個切面。可以這樣了解:oop是從靜态角度考慮程式結構,而aop則從動态角度考慮程式運作過程。

為什麼需要aop

    在傳統oop變成立,以對象為核心,整個軟體系統由系列互相依賴的對象組成,而這些對象被抽象成一個一個的類,并允許使用類繼承來管理類與類之間從一般到特殊的關系。随着軟體規模的增大,應用的逐漸更新,慢慢出現了一些oop很難解決的問題。

    我們可以通過分析、抽象出一系列具有一定屬性與行為的對象,并通過這些對象之間的協作來形成一個完整的軟體功能。由于對象可以繼承,是以我們可以把具有相同功能或相同特性的屬性抽象到一個層次分明的類結構體系中。随着軟體規模的不斷擴大,專業化分工越來越系列,以及oop應用實踐的不斷增多,随之也暴露出一些oop無法很好解決的問題。

    假設系統中有3段完全相似的代碼,這些代碼通常會采用“複制”、“粘貼”方式來完成,通過這種複制和粘貼完成的代碼在後期将很難維護:想想一下,如果有一天,這些被複制和粘貼的代碼需要修改,那麼,是不是會修改這3處呢?如果這段代碼被複制和粘貼了100遍呢,1000遍呢,如何維護?大多數人會想到将這段代碼抽取出來,作為一個公共的方法,在需要使用這段代碼的地方,調用這個方法即可。這樣如果這段代碼需要修改,隻需要修改這個公共的方法即可。但實際的情況是:即使将公共的部分抽取出來了,每個地方還是需要去顯式調用這個方法,這能夠解決大部分問題。但是對于一些更加特殊的情況:應用需要将公共的部分與調用的地方徹底分離,那又應該如何解決呢?

    因為軟體系統需求變更時很頻繁的事情,假設系統前期設計方法1、2、3時隻實作了核心業務,一段時間之後,我們需要對這些方法都進行事務控制;又過了一段時間,客戶提出這些方法需要進行合法的使用者驗證,隻有合法的使用者才能調用這些方法;又過了一段時間,客戶又提出這些方法需要增加日志記錄;又過了一段時間……面對這種問題,我們應該怎麼辦呢?是不是每次先定義一個新的方法,然後再去修改方法1、2、3增加調用新的方法的代碼塊呢?這樣做的工作量也不小啊!

    我們希望有一種特殊的方法:我們隻要定義該方法,無需在方法1、2、3中顯式調用它,系統會“自動”調用該方法。

    注意:上面的自動被加上了引号,是因為在程式設計過程中,沒有所謂的自動的事情,在程式的世界裡,任何事情都是由代碼驅動的。這裡的自動是指,無需開發者關心,由系統來驅動。

    上面的想法聽起來很神奇,甚至有些不切實際,但其實是完全可以實作的,實作這個需求的技術就是aop。aop專門用于處理系統中分布于各個子產品(不同方法)中的交叉關注點的問題,在javaee應用中,常常通過aop來處理一些具有橫切性質的系統級服務,例如事務管理、安全檢查、緩存、對象池等等,aop已經成為一種非常常用的方案。

使用aspectj實作aop

    aspectj是一個基于java語言的aop架構,提供了強大的aop功能,其他很多aop架構都借鑒或采納其中的一些思想。由于spring 3.0的aop與aspectj進行了很好的內建,是以掌握aspectj是學習spring aop的基礎。

    aspectj是java語言的一個aop實作,其主要包括兩個部分:第一個部分定義了如何表達、定義aop程式設計中的文法規範,通過這個規範,我們可以友善的用aop來解決java語言中存在的交叉關注點的問題;另一個部分是工具部分,包括編譯器、調試工具等。

    aspectj是最早、功能比較強大的aop實作之一,對嵌套aop機制都有較好的實作,很多其他語言的aop實作,也借鑒或采納了aspectj中的很多設計。在java領域,aspectj中的很多文法結構基本上已成為aop領域的标準。

    從spring 2.0開始,spring aop已經引入了對aspectj的支援,并允許直接使用aspectj進行aop程式設計,而spring自身的aop api也努力與aspectj保持一緻。是以,學習springaop就必然需要從aspectj開始,因為它是java領域最流行的aop解決方案,我們甚至可以直接使用aspectj進行aop程式設計。

aspectj的下載下傳和安裝(安裝aspectj之前,請確定系統已經安裝了jdk)。

Spring中的AOP(一)——AspectJ的基本使用

    2. 打開指令行,cd到該jar包所在的檔案夾,運作java -jar aspectj-1.7.4.jar指令,打開aspectj的安裝界面。第一個界面是歡迎界面,直接next。

    3. 第二個界面中,選擇jre的安裝路徑,next。

Spring中的AOP(一)——AspectJ的基本使用

    4. 第三個界面中,選擇aspectj的安裝路徑,next。因為安裝過程的實質是解壓一個壓縮包,并不需要太多的依賴于系統,是以路徑可以任意選擇,這裡我選擇和java安裝在一起。

Spring中的AOP(一)——AspectJ的基本使用

    5. 安裝完成後,按照界面提示,需要配置classpath和path,這裡不做介紹。

Spring中的AOP(一)——AspectJ的基本使用

     至此,aspectj安裝完成。

    aspectj提供了編譯、運作aspectj的一些工具指令,這些指令放在aspectj的bin目錄下,而lib路徑下的aspectjrt.jar則是aspectj的運作時環境,是以我們需要分别添加這兩個環境變量。

aspectj的使用入門

    成功安裝aspectj後,在其安裝目錄下有如下結構:

    bin:該路徑下存放了aspectj的常用指令,其中ajc最為常用,其作用類似于javac,用于對普通java類進行編譯時增強

    docs:該路徑下存放了aspectj的使用說明、參考手冊和api等文檔

    lib:該路徑下的4個jar檔案是aspectj運作的核心類庫

    license和readme檔案

    這裡要提到的是,一些文檔、aspectj的入門書籍,一談到使用aspectj,就認為必須使用eclipse工具,似乎離開了該工具就不能使用aspectj了。實際上,雖然aspectj是eclipse基金組織的開源項目,而且提供了eclipse的ajdt(aspectj development tools)插件來開發aspectj應用,但aspectj絕對無需依賴于eclipse工具。

    aspectj的用法很簡單,就像我們使用jdk編譯、運作java程式一樣。下面通過一個簡單的程式來示範aspectj的用法:

<a href="http://my.oschina.net/itblog/blog/208067#">?</a>

1

2

3

4

5

6

7

8

9

<code>public</code> <code>class</code> <code>helloworld {</code>

<code>    </code><code>public</code> <code>void</code> <code>sayhello(){</code>

<code>        </code><code>system.out.println(</code><code>"hello aspectj!"</code><code>);</code>

<code>    </code><code>}</code>

<code>    </code><code>public</code> <code>static</code> <code>void</code> <code>main(string args[]) {</code>

<code>        </code><code>helloworld h = </code><code>new</code> <code>helloworld();</code>

<code>        </code><code>h.sayhello();</code>

<code>}</code>

    毫無疑問,結果将輸出"hello aspectj!"字元串。假設現在客戶需要在執行sayhello方法前啟動事務,當該方法結束時關閉事務,在傳統程式設計模式下,我們必須手動修改sayhello方法——如果改為使用aspectj,則可以無需修改上面的sayhello方法。下面我們定義一個特殊的“類”:

<code>public</code> <code>aspect txaspect {</code>

<code>    </code><code>void</code> <code>around():call(</code><code>void</code> <code>sayhello()) {</code>

<code>        </code><code>system.out.println(</code><code>"transaction begin"</code><code>);</code>

<code>        </code><code>proceed();</code>

<code>        </code><code>system.out.println(</code><code>"transaction end"</code><code>);</code>

    可能有人已經發現,上面的類檔案中不是使用class、interface或者enum來定義java類,而是使用aspect——難道java語言又增加關鍵字了?no!上面的txaspect根本不是一個java類,是以aspect也不是java支援的關鍵字,它隻是aspectj才認識的關鍵字。

    上面"void around"中的内容也不是方法,它隻是指定當程式執行helloworld對象的sayhello方法時,執行這個代碼塊,其中proceed表示調用原來的sayhello方法。正如前面提到的,java無法識别txaspect.java檔案中的内容是以我們需要使用ajc.exe來執行編譯:

<code>ajc helloworld.java txaspect.java</code>

    我們可以把ajc指令了解為javac指令,都用于編譯java程式,差別是ajc指令可以識别aspectj的文法。從這個角度看,我們可以将ajc指令當成一個增強版的javac指令。

    運作該helloworld類依然無需任何改變:

<code>java helloworld</code>

    其結果将是:

Spring中的AOP(一)——AspectJ的基本使用

    從上面的運作結果來看,我們可以完全不對helloworld.java檔案做修改,也不用對執行helloworld的指令做修改,就可以實作上文中的實作事務管理的需求。上面的“transaction begin”和“transaction end”僅僅是模拟事務的事件,實際開發中,用代碼替換掉這段輸出即可實作事務管理。

    如果客戶又提出了為方法增加日志的需求,那也很簡單,我們可以再定義一個logaspect類,如下:

<code>//同樣使用aspect作為“關鍵字”</code>

<code>public</code> <code>aspect logaspect {</code>

<code>    </code><code>//定義一個名為logpointcut的pointcut,對應于helloworld對象的sayhello方法</code>

<code>    </code><code>pointcut logpointcut():execution(</code><code>void</code> <code>helloworld.sayhello());</code>

<code>    </code><code>//在logpointcut之後指定下面的代碼</code>

<code>    </code><code>after():logpointcut() {</code>

<code>        </code><code>system.out.println(</code><code>"log recoding"</code><code>);</code>

    上面的代碼中定義了一個pointcut——logpointcut,等同于執行helloworld對象的sayhello方法,并指定在logpointcut之後執行簡單的代碼塊,也就是說,在sayhello方法結束之後執行輸出語句。使用如下指令編譯這幾個java檔案:

<code>ajc *.java</code>

    再次運作helloworld類,将輸出以下結果:

Spring中的AOP(一)——AspectJ的基本使用

    由此可見,通過使用aspectj提供的aop支援,我們可以為sayhello方法不斷增加新功能。

    實際上,aspectj允許同時為多個方法添加新功能,隻要我們定義pointcut時指定比對更多的方法即可,下面是一個代碼片段:

<code>pointcut xxxpointcut:execution(</code><code>void</code> <code>h*.say*());</code>

    上面的程式中的xxxpointcut将可以比對所有以h開頭的類,以say開頭的方法名和傳回值為void類型的所有方法。aspectj甚至允許下面的形式:

<code>pointcut xxxpointcut:execution(* h*.say*());</code>

    如果裝有java反編譯工具,可以将helloworld.class進行反編譯,我們将發現該helloworld.class檔案不是由helloworld.java檔案編譯得到的,helloworld.class裡面增加了很多新的内容——這表明aspectj在編譯時已經增強了helloworld.class的功能,是以aspectj通常被稱為編譯時增強的aop架構。

    拓展:與aspectj相對的還有另外一種aop架構,它們不需要在編譯的時候對目标類進行增強,而是運作時生成目标類的代理類,該代理類要麼實作了目标類實作的相同接口,要麼是目标類的子類。總之,代理類都對目标類進行了增強處理,前者是jdk動态代理的處理政策,後者是cglib代理的處理政策。spring aop以建立動态代理的方式來生成代理類,底層既可使用jdk動态代理,也可以采用cglib代理。一般來說,編譯時增強的aop架構在性能上更有優勢——因為運作時動态增強的aop架構需要每次運作時都進行動态增強。

繼續閱讀