?
AOP和AspectJ板橋里人http://www.jdon.com 2004/01/10 需求和問(wèn)題 以上篇《AOP是什么》中并發(fā)訪問(wèn)應(yīng)用為例子: 多個(gè)訪問(wèn)類同時(shí)訪問(wèn)一個(gè)共享數(shù)據(jù)對(duì)象時(shí),每個(gè)訪問(wèn)類在訪問(wèn)這個(gè)數(shù)據(jù)對(duì)象時(shí),需要將數(shù)據(jù)對(duì)象上鎖,訪問(wèn)完成后,再實(shí)行解鎖,供其它并發(fā)線程訪問(wèn),這是我們處理并發(fā)訪問(wèn)資源的方式。 為了實(shí)現(xiàn)這個(gè)需求,先實(shí)現(xiàn)傳統(tǒng)的編程,這里我們假定有一個(gè)寫鎖,對(duì)數(shù)據(jù)對(duì)象實(shí)行寫之前,首先對(duì)這個(gè)對(duì)象進(jìn)行上寫鎖,寫操作完畢后,必須釋放寫鎖。 首先,我們需要一個(gè)鎖,這個(gè)鎖可以是數(shù)據(jù)對(duì)象中一個(gè)字段或其它,這里使用Doug Lea的ReentrantWriterPreferenceReadWriteLock作為我們的鎖資源。 import EDU.oswego.cs.dl.util.concurrent.*; public class Worker extends Thread { Data data;
ReentrantWriterPreferenceReadWriteLock rwl = new ReentrantWriterPreferenceReadWriteLock();
public boolean createData() { try { rwl.writeLock().acquire(); //上鎖
//對(duì)data實(shí)行寫邏輯操作 }catch() { return false; }finally{ rwl.writeLock().release(); //解鎖 } return true; }
public boolean updateData() { try { rwl.writeLock().acquire();//上鎖
//對(duì)data實(shí)行寫邏輯操作 }catch() { return false; }finally{ rwl.writeLock().release(); //解鎖 } return true; }
public void run() { //執(zhí)行createData()或updateData() } } |
假設(shè)可能存在另外一個(gè)訪問(wèn)類,也將對(duì)數(shù)據(jù)對(duì)象實(shí)現(xiàn)寫操作,代碼如下: import EDU.oswego.cs.dl.util.concurrent.*; public class AnotherWorker extends Thread { Data data;
ReentrantWriterPreferenceReadWriteLock rwl = new ReentrantWriterPreferenceReadWriteLock(); public boolean updateData() { try { rwl.writeLock().acquire();//上鎖
//對(duì)data實(shí)行寫邏輯操作 }catch() { return false; }finally{ rwl.writeLock().release(); //解鎖 } return true; }
public void run() { //執(zhí)行updateData() } } |
以上是Java傳統(tǒng)編程的實(shí)現(xiàn),這種鎖的實(shí)現(xiàn)方式是在每個(gè)具體類中實(shí)現(xiàn),如下圖: 
這種實(shí)現(xiàn)方式的缺點(diǎn)很多: - 冗余:有很多重復(fù)的編碼,如rwl.writeLock().acquire()等;
- 減少重用:worker的updateData()方法重用性幾乎為零。
- "數(shù)據(jù)對(duì)象寫操作必須使用鎖控制這個(gè)設(shè)計(jì)目的"不容易顯現(xiàn),如果更換了一個(gè)新的程序員,他可能編寫一段不使用鎖機(jī)制就對(duì)這個(gè)數(shù)據(jù)對(duì)象寫操作的代碼。
- 如果上述代碼有讀功能,那么我們需要在代碼中實(shí)現(xiàn)先上讀鎖,當(dāng)需要寫時(shí),解讀鎖,再上寫鎖等等,如果稍微不小心,上鎖解鎖次序搞錯(cuò),系統(tǒng)就隱含大的BUG,這種可能性會(huì)隨著這個(gè)數(shù)據(jù)對(duì)象永遠(yuǎn)存在下去,系統(tǒng)設(shè)計(jì)大大的隱患啊!
那么我們使用AOP概念來(lái)重新實(shí)現(xiàn)上述需求,AOP并沒有什么新花招,只是提供了觀察問(wèn)題的一個(gè)新視角度。 這里我們可以拋開新技術(shù)迷人霧障,真正核心還是新思維、新視點(diǎn),人類很多問(wèn)題如果換一個(gè)腦筋看待理解,也許結(jié)果真的是翻天覆地不一樣啊,所以,作為人自身,首先要重視和你世界觀和思維方式不一樣的人進(jìn)行交流和溝通。 現(xiàn)實(shí)生活中有很多"不公平",例如某個(gè)小學(xué)畢業(yè)生成了千萬(wàn)富翁,你就懷疑知識(shí)無(wú)用,也許你認(rèn)為他的機(jī)會(huì)好,其實(shí)你可能不知道,他的觀察問(wèn)題的視角比你獨(dú)特,或者他可能會(huì)經(jīng)常換不同的角度來(lái)看待問(wèn)題和解決問(wèn)題,而你由于過(guò)分陷入一個(gè)視角的具體實(shí)現(xiàn)細(xì)節(jié)中,迷失了真正的方向,要不說(shuō)是讀書人腦子僵化呢? 言歸正傳,我們看看AOP是如何從一個(gè)新的視角解決上述問(wèn)題的。 如果說(shuō)上面代碼在每個(gè)類中實(shí)現(xiàn)上鎖或解鎖,類似橫向解決方式,那么AOP是從縱向方面來(lái)解決上述問(wèn)題,縱向解決之道示意圖如下: 
AOP把這個(gè)縱向切面cross-cuts稱為Aspect(方面),其實(shí)我認(rèn)為AOP翻譯成面向切面編程比較好,不知哪個(gè)糊涂者因?yàn)橄刃幸徊剑g成“面向方面編程”如此抽象,故弄玄虛。 AspectJ實(shí)現(xiàn) 下面我們使用AOP的實(shí)現(xiàn)之一AspectJ來(lái)對(duì)上述需求改寫。AspectJ是AOP最早成熟的Java實(shí)現(xiàn),它稍微擴(kuò)展了一下Java語(yǔ)言,增加了一些Keyword等,pointcut的語(yǔ)法如下: public pointcut 方法名:call(XXXX) AspectJ增加了pointcut, call是pointcut類型,有關(guān)AspectJ更多基本語(yǔ)法見這里。因?yàn)锳spectJ使用了一些特別語(yǔ)法,所以Java編譯器就不能用SUN公司提供javac了,必須使用其專門的編譯器,也許SUN在以后JDK版本中會(huì)引入AOP。 使用AspectJ如何實(shí)現(xiàn)上圖所謂切面式的編程呢?首先,我們將上圖縱向切面稱為Aspect,那么我們建立一個(gè)類似Class的Aspect,Java中建立一個(gè)Class代碼如下: public class MyClass{ //屬性和方法 ... } 同樣,建立一個(gè)Aspect的代碼如下: public aspect MyAspect{ //屬性和方法 ... } 建立一個(gè)Aspect名為L(zhǎng)ock,代碼如下: import EDU.oswego.cs.dl.util.concurrent.*; public aspect Lock {
...... ReentrantWriterPreferenceReadWriteLock rwl = new ReentrantWriterPreferenceReadWriteLock(); public pointcutwriteOperations(): execution(public boolean Worker.createData()) || execution(public boolean Worker.updateData()) || execution(public boolean AnotherWorker.updateData()) ;
before() : writeOperations() { rwl.writeLock().acquire();//上鎖 advice body } after() : writeOperations() { rwl.writeLock().release(); //解鎖 advice body } ...... } |
上述代碼關(guān)鍵點(diǎn)是pointcut,意味切入點(diǎn)或觸發(fā)點(diǎn),那么在那些條件下該點(diǎn)會(huì)觸發(fā)呢?是后面紅字標(biāo)識(shí)的一些情況,在執(zhí)行Worker的createData()方法,Worker的update方法等時(shí)觸發(fā)。 before代表觸發(fā)之前做什么事情? 答案是上鎖。 after代表觸發(fā)之后做什么事情? 答案是上鎖。 通過(guò)引入上述aspect,那么Worker代碼可以清潔如下: public class Worker extends Thread { Data data;
public boolean createData() { try { //對(duì)data實(shí)行寫邏輯操作 }catch() { return false; } return true; }
public boolean updateData() { try { //對(duì)data實(shí)行寫邏輯操作 }catch() { return false; }finally{ } return true; }
public void run() { //執(zhí)行createData()或updateData() } } |
Worker中關(guān)于“鎖”的代碼都不見了,純粹變成了數(shù)據(jù)操作的主要方法。 AOP術(shù)語(yǔ) 通過(guò)上例已經(jīng)知道AspectJ如何從切面crosscutting來(lái)解決并發(fā)訪問(wèn)應(yīng)用需求的,其中最重要的是引入了一套類似事件觸發(fā)機(jī)制。 Pointcut類似觸發(fā)器,是事件Event發(fā)生源,一旦pointcut被觸發(fā),將會(huì)產(chǎn)生相應(yīng)的動(dòng)作Action,這部分Action稱為Advice。 Advice在AspectJ有三種:before、 after、Around之分,上述aspect Lock代碼中使用了Advice的兩種before和after。 所以AOP有兩個(gè)基本的術(shù)語(yǔ):Pointcut和Advice。你可以用事件機(jī)制的Event和Action來(lái)類比理解它們。上述并發(fā)訪問(wèn)應(yīng)用中pointcut和advice如下圖所示: 
小結(jié)如下: advice - 真正的執(zhí)行代碼,或者說(shuō)關(guān)注的實(shí)現(xiàn)。 類似Action。 join point - 代碼中激活advice被執(zhí)行的觸發(fā)點(diǎn)。 pointcut - 一系列的join point稱為pointcut,pointcut有時(shí)代指join point 其中advice部分又有: Interceptor - 解釋器并沒有在AspectJ出現(xiàn),在使用JDK動(dòng)態(tài)代理API實(shí)現(xiàn)的AOP框架中使用,解釋有方法調(diào)用或?qū)ο髽?gòu)造或者字段訪問(wèn)等事件,是調(diào)用者和被調(diào)用者之間的紐帶,綜合了Decorator/代理模式甚至職責(zé)鏈等模式。 Introduction - 修改一個(gè)類,以增加字段、方法或構(gòu)造或者執(zhí)行新的接口,包括Mixin實(shí)現(xiàn)。 例如上述并發(fā)訪問(wèn)應(yīng)用中,如果想為每個(gè)Data對(duì)象生成相應(yīng)的aspect Lock,那么可以在aspect Lock中人為數(shù)據(jù)對(duì)象增加一個(gè)字段lock,如下: aspect Lock {
Data sharedDataInstance; Lock( Data d ) { sharedDataInstance = d; }
introduce Lock Data.lock; //修改Data類,增加一字段lock
advise Data() { //Data構(gòu)造時(shí)觸發(fā) static after { //當(dāng)Data對(duì)象生成時(shí),將Data中l(wèi)ock字段賦值為aspect Lock //為每個(gè)Data對(duì)象生成相應(yīng)的aspect Lock thisObject.lock = new Lock( thisObject ); } } .... } |
上述代碼等于在Data類中加入一行: public class Data{ ...... Lock lock = new Lock(); ...... } |
還有其它兩個(gè)涉及AOP代碼運(yùn)行方式: weaving - 將aspect代碼插入到相應(yīng)代碼中的過(guò)程,一般是編譯完成或在運(yùn)行時(shí)動(dòng)態(tài)完成。取決于具體AOP產(chǎn)品,例如AspectJ是使用特殊編譯器在編譯完成weaving,而nanning、JBoss AOP是使用動(dòng)態(tài)代理API,因此在運(yùn)行時(shí)動(dòng)態(tài)完成weaving的。 instrumentor - 用來(lái)實(shí)現(xiàn)weaving功能的工具。 |