面向切面編程(AOP)初探
面向?qū)ο缶幊掏ㄟ^(guò)設(shè)計(jì)和語(yǔ)言本身提供的模塊化、封裝、繼承、多態(tài)來(lái)實(shí)現(xiàn)軟件復(fù)用。盡管OOP在建模以及實(shí)現(xiàn)復(fù)雜軟件方面非常成功,它仍然有一些問(wèn)題。面向切面編程(AOP)被認(rèn)為是一項(xiàng)有前途的新技術(shù),它通過(guò)對(duì)交叉業(yè)務(wù)的分隔來(lái)實(shí)現(xiàn),而這在面向?qū)ο缶幊汤锖茈y做到。本文通過(guò)一個(gè)新的范例介紹AOP的基本概念。
面向?qū)ο缶幊?Object Oriented Programming
今天,面向?qū)ο缶幊桃呀?jīng)成為主流的編程模式,在這里,現(xiàn)實(shí)問(wèn)題被分解為一個(gè)個(gè)的包含數(shù)據(jù)和行為的對(duì)象。
在大型工程實(shí)踐中,程序員發(fā)現(xiàn)在模塊中越來(lái)越難以分離交叉業(yè)務(wù),他們的代碼也變得更加難維護(hù)。對(duì)程序設(shè)計(jì)的一絲改動(dòng)都會(huì)引發(fā)大量不相關(guān)模塊的改動(dòng)。
交叉業(yè)務(wù) Crosscutting Concerns
一個(gè)交叉業(yè)務(wù)的例子是“日志”,日志在分布式系統(tǒng)中經(jīng)常被用來(lái)記錄方法調(diào)用,以輔助調(diào)試。假設(shè)我們?cè)诿總€(gè)函數(shù)開始前和結(jié)束后都寫日志,這會(huì)使我們對(duì)所有包含方法的類做“橫切”(crosscutting)。其他典型的交叉業(yè)務(wù)包括:上下文敏感的錯(cuò)誤處理,性能優(yōu)化,以及設(shè)計(jì)模式。
交叉業(yè)務(wù)可能出現(xiàn)在某些程序中,尤其是那些大型程序中。然而另一方面,對(duì)系統(tǒng)的重新設(shè)計(jì)可以將交叉業(yè)務(wù)轉(zhuǎn)換成對(duì)象。AOP假定交叉業(yè)務(wù)會(huì)出現(xiàn)在程序中,并無(wú)法從重構(gòu)中被剔除出去。
面向切面編程 Aspect Oriented Programming
面向切面的編程AOP是一項(xiàng)新的技術(shù),它將交叉業(yè)務(wù)分離出來(lái),作為獨(dú)立單元——切面——處理。切面即是交叉業(yè)務(wù)的模塊化實(shí)現(xiàn),它封裝了對(duì)各個(gè)類都有影響的行為,作為新的可重用的模塊。利用AOP,我們可以用OO編程語(yǔ)言(如Java)開始項(xiàng)目,然后我們單獨(dú)使用切面處理交叉業(yè)務(wù)。最后,代碼和切面一起通過(guò)編織器(aspect weaver)組織成最終可執(zhí)行文件。圖1說(shuō)明了"編織器"工作過(guò)程。注意,原始的代碼不需要知道切面的任何功能;只要除去切面代碼并重新編譯,就能得到初始代碼的功能。
AOP是一種編程概念,因此它并未綁定到任何特定的語(yǔ)言。事實(shí)上,它對(duì)所有單獨(dú)的、垂直分解式(譯注:AOP通常被認(rèn)為是橫向分解)的語(yǔ)言(不僅是OO語(yǔ)言)都有幫助。AOP在不同語(yǔ)言都有實(shí)現(xiàn)(如 C++, Smalltalk, C#, C, Java).
當(dāng)然,受益最大的還是Java語(yǔ)言。下面是一些支持Java AOP的工具:
◆AspectJ
◆AspectWerkz
◆Hyper/J
◆JAC
◆JMangler
◆MixJuice
◆PROSE
◆ArchJava
由Xerox PARC所創(chuàng)建的AspectJ被認(rèn)為是Java語(yǔ)言在AOP方面的一個(gè)擴(kuò)展,是專門為面向切面的編程而生的。本文下面部分主要涉及AspectJ.#p#
連接點(diǎn),切入點(diǎn),通知和引入 Join points, Pointcut, Advice, and Introduction
就如OOP的概念包含繼承、封裝、多態(tài)一樣,組成AOP的概念是連接點(diǎn),切入點(diǎn),通知和引入(Join points, Pointcut, Advice, and Introduction)。為更好的理解這些術(shù)語(yǔ),我們看一下下面的例子。
- public class TestClass {
- public void sayHello () {
- System.out.println ("Hello, AOP");
- }
- public void sayAnyThing (String s) {
- System.out.println (s);
- }
- public static void main (String[] args) { sayHello ();
- sayAnyThing ("ok");
- }
- }
我們的Java代碼保存在TestClass.java,假設(shè)我們想用切面做如下修改:
在對(duì)TestClass.sayHello()方法調(diào)用之前和之后,都打印一行信息;檢查TestClass.sayAnyThing() 方法的參數(shù),至少3個(gè)字符才能執(zhí)行
下面就是AspectJ 的實(shí)現(xiàn)。
- public aspect MyAspect {
- public pointcut sayMethodCall (): call (public void
- TestClass.say*() );
- public pointcut sayMethodCallArg (String str): call
- (public void TestClass.sayAnyThing (String))
- && args(str);
- before(): sayMethodCall() {
- System.out.println("\n TestClass." +
- thisJoinPointStaticPart.getSignature().getName() +
- "start..." );
- }
- after(): sayMethodCall() {
- System.out.println("\n TestClass." +
- thisJoinPointStaticPart.getSignature().getName() +
- " end...");
- }
- before(String str): sayMethodCallArg(str) {
- if (str .length() < 3) {
- System.out.println ("Error: I can't say words less than 3
- characters");
- return;
- }
- }
- }
Line 1 定義了一個(gè)aspect,就像我們定義Java 類。跟任何Java類一樣,aspect也可以擁有成員變量和方法,另外它還可以包含切入點(diǎn)(pointcuts),通知(advices)和引入(introductions).
Lines 2和Line 3指定我們的修改在TestClass什么地方起作用。按AspectJ術(shù)語(yǔ),我們定義了2個(gè)切入點(diǎn)(pointcuts)。為了弄清楚切入點(diǎn)(pointcut)是什么意思,我們需要先定義連接點(diǎn)(join points).
連接點(diǎn)(join points)表示在程序執(zhí)行過(guò)程中預(yù)先定義的“點(diǎn)”,AspectJ 中典型的連接點(diǎn)包括:方法或構(gòu)造器的調(diào)用,方法或構(gòu)造器的執(zhí)行,字段的讀取,異常處理,以及靜態(tài)或動(dòng)態(tài)的初始化。本文例子中,我們定義了2處連接點(diǎn):對(duì)TestClass.sayHello方法的調(diào)用及對(duì)TestClass.sayAnyThing方法的調(diào)用。
切入點(diǎn)(Pointcut)是符合預(yù)定義規(guī)范的連接點(diǎn)(a set of join points)的集合,這是一個(gè)語(yǔ)言上的構(gòu)造概念。 規(guī)范可以是明確的的函數(shù)名,也可以是包含通配符的函數(shù)名。
public pointcut sayMethodCall (): call (public void
TestClass.say*() );
上面一行,我們定義了一個(gè)切入點(diǎn)(pointcut),叫做 sayMethodCall,它會(huì)檢查所有對(duì)TestClass.sayHello方法的調(diào)用。另外,它同樣會(huì)檢查TestClass 類里所有以"say"開頭,參數(shù)為空的公共方法(舉個(gè)例子:TestClass.sayBye).
切入點(diǎn)(Pointcuts)用來(lái)定義“通知” (advice). AspectJ 的advice用來(lái)定義在連接點(diǎn)執(zhí)行之前、之中、之后的額外代碼。在我們的例子中,line 4-6 和line7-9 分別定義了對(duì)第一個(gè)切入點(diǎn)執(zhí)行之前和之后的通知。Lines10-15定義了對(duì)第二個(gè)切入點(diǎn)的通知,即設(shè)置TestClass.sayAnyThing 方法執(zhí)行的一個(gè)前置條件。
切入點(diǎn)pointcuts和通知advice能讓你影響程序的動(dòng)態(tài)執(zhí)行部分,與此不同,引入(introduction)允許切面修改程序中靜態(tài)的部分。通過(guò)引入(introduction), 切面可以為類添加新的方法及變量,聲明類實(shí)現(xiàn)的接口,或?qū)⒉东@的異常轉(zhuǎn)為未捕獲的異常。 Introduction和一個(gè)更為實(shí)用的AOP的例子是我未來(lái)一篇文章的主題。
AspectJ 編譯器
回到開頭,你需要從AspectJ 的官方網(wǎng)站上下載它的最新版本并安裝它(免費(fèi)的),編譯和運(yùn)行我們的例子非常簡(jiǎn)單:
ajc MyAspect.aj TestClass.java
java TestClass
值得注意的是,Java源代碼TestClass.java 沒有任何改動(dòng)。你只要使用Java編譯器重新編譯它就能得到最初的原始程序功能。
【編輯推薦】