1. 程式人生 > >AOP和AspectJ-掃盲(轉)

AOP和AspectJ-掃盲(轉)

需求和問題

  以上篇《AOP是什麼》中併發訪問應用為例子:

  多個訪問類同時訪問一個共享資料物件時,每個訪問類在訪問這個資料物件時,需要將資料物件上鎖,訪問完成後,再實行解鎖,供其它併發執行緒訪問,這是我們處理併發訪問資源的方式。

  為了實現這個需求,先實現傳統的程式設計,這裡我們假定有一個寫鎖,對資料物件實行寫之前,首先對這個物件進行上寫鎖,寫操作完畢後,必須釋放寫鎖。

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(); //上鎖

    //對data實行寫邏輯操作 
       
   }catch() {
     return false;
   }finally{
     rwl.writeLock().release();  //解鎖
   }
   return true;
  }

  public boolean updateData() {
   try {
    rwl.writeLock().acquire();//上鎖

    //對data實行寫邏輯操作 
       
   }catch() {
     return false;
   }finally{
     rwl.writeLock().release(); //解鎖
   }
   return true;
  }

  public void run() {
    //執行createData()或updateData()
  }
}

假設可能存在另外一個訪問類,也將對資料物件實現寫操作,程式碼如下:

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();//上鎖

    //對data實行寫邏輯操作 
       
   }catch() {
     return false;
   }finally{
     rwl.writeLock().release(); //解鎖
   }
   return true;
  }

  public void run() {
    //執行updateData()
  }
}

  以上是Java傳統程式設計的實現,這種鎖的實現方式是在每個具體類中實現,如下圖:

這種實現方式的缺點很多:

  • 冗餘:有很多重複的編碼,如rwl.writeLock().acquire()等;
  • 減少重用:worker的updateData()方法重用性幾乎為零。
  • "資料物件寫操作必須使用鎖控制這個設計目的"不容易顯現,如果更換了一個新的程式設計師,他可能編寫一段不使用鎖機制就對這個資料物件寫操作的程式碼。
  • 如果上述程式碼有讀功能,那麼我們需要在程式碼中實現先上讀鎖,當需要寫時,解讀鎖,再上寫鎖等等,如果稍微不小心,上鎖解鎖次序搞錯,系統就隱含大的BUG,這種可能性會隨著這個資料物件永遠存在下去,系統設計大大的隱患啊!

  那麼我們使用AOP概念來重新實現上述需求,AOP並沒有什麼新花招,只是提供了觀察問題的一個新視角度。

  這裡我們可以拋開新技術迷人霧障,真正核心還是新思維、新視點,人類很多問題如果換一個腦筋看待理解,也許結果真的是翻天覆地不一樣啊,所以,作為人自身,首先要重視和你世界觀和思維方式不一樣的人進行交流和溝通。

  現實生活中有很多"不公平",例如某個小學畢業生成了千萬富翁,你就懷疑知識無用,也許你認為他的機會好,其實你可能不知道,他的觀察問題的視角比你獨特,或者他可能會經常換不同的角度來看待問題和解決問題,而你由於過分陷入一個視角的具體實現細節中,迷失了真正的方向,要不說是讀書人腦子僵化呢?

  言歸正傳,我們看看AOP是如何從一個新的視角解決上述問題的。

  如果說上面程式碼在每個類中實現上鎖或解鎖,類似橫向解決方式,那麼AOP是從縱向方面來解決上述問題,縱向解決之道示意圖如下:

  AOP把這個縱向切面cross-cuts稱為Aspect(方面),其實我認為AOP翻譯成面向切面程式設計比較好,不知哪個糊塗者因為先行一步,翻譯成“面向方面程式設計”如此抽象,故弄玄虛。

AspectJ實現

  下面我們使用AOP的實現之一AspectJ來對上述需求改寫。AspectJ是AOP最早成熟的Java實現,它稍微擴充套件了一下Java語言,增加了一些Keyword等,pointcut的語法如下:

public pointcut 方法名:call(XXXX)

  AspectJ增加了pointcut, call是pointcut型別,有關AspectJ更多基本語法見這裡。因為AspectJ使用了一些特別語法,所以Java編譯器就不能用SUN公司提供javac了,必須使用其專門的編譯器,也許SUN在以後JDK版本中會引入AOP。

  使用AspectJ如何實現上圖所謂切面式的程式設計呢?首先,我們將上圖縱向切面稱為Aspect,那麼我們建立一個類似Class的Aspect,Java中建立一個Class程式碼如下:

public class MyClass{
  //屬性和方法 ...
}

  同樣,建立一個Aspect的程式碼如下:

public aspect MyAspect{
  //屬性和方法 ...
}

建立一個Aspect名為Lock,程式碼如下:

import EDU.oswego.cs.dl.util.concurrent.*;

public aspect Lock {

  ......
  ReentrantWriterPreferenceReadWriteLock rwl = 
    new ReentrantWriterPreferenceReadWriteLock();

  public pointcut writeOperations():
    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
  }

  ......
}

  上述程式碼關鍵點是pointcut,意味切入點或觸發點,那麼在那些條件下該點會觸發呢?是後面紅字標識的一些情況,在執行Worker的createData()方法,Worker的update方法等時觸發。

  before代表觸發之前做什麼事情?
  答案是上鎖。

  after代表觸發之後做什麼事情?
  答案是上鎖。

  通過引入上述aspect,那麼Worker程式碼可以清潔如下:

public class Worker extends Thread {

  Data data;

  public boolean createData() {
   try {
    //對data實行寫邏輯操作        
   }catch() {
     return false;
   }
   return true;
  }

  public boolean updateData() {
   try {
    //對data實行寫邏輯操作        
   }catch() {
     return false;
   }finally{
   }
   return true;
  }

  public void run() {
    //執行createData()或updateData()
  }
}

  Worker中關於“鎖”的程式碼都不見了,純粹變成了資料操作的主要方法。

AOP術語

  通過上例已經知道AspectJ如何從切面crosscutting來解決併發訪問應用需求的,其中最重要的是引入了一套類似事件觸發機制。

  Pointcut類似觸發器,是事件Event發生源,一旦pointcut被觸發,將會產生相應的動作Action,這部分Action稱為Advice。

  Advice在AspectJ有三種:before、 after、Around之分,上述aspect Lock程式碼中使用了Advice的兩種before和after。

  所以AOP有兩個基本的術語:Pointcut和Advice。你可以用事件機制的Event和Action來類比理解它們。上述併發訪問應用中pointcut和advice如下圖所示:

小結如下:
advice - 真正的執行程式碼,或者說關注的實現。 類似Action。
join point - 程式碼中啟用advice被執行的觸發點。
pointcut - 一系列的join point稱為pointcut,pointcut有時代指join point

其中advice部分又有:
Interceptor - 直譯器並沒有在AspectJ出現,在使用JDK動態代理API實現的AOP框架中使用,解釋有方法呼叫或物件構造或者欄位訪問等事件,是呼叫者和被呼叫者之間的紐帶,綜合了Decorator/代理模式甚至職責鏈等模式。

Introduction - 修改一個類,以增加欄位、方法或構造或者執行新的介面,包括Mixin實現。

例如上述併發訪問應用中,如果想為每個Data物件生成相應的aspect Lock,那麼可以在aspect Lock中人為資料物件增加一個欄位lock,如下:

aspect Lock {

  Data sharedDataInstance;
  Lock( Data d ) {
     sharedDataInstance = d;
  }

  introduce Lock Data.lock; //修改Data類,增加一欄位lock

  advise Data() { //Data構造時觸發
     static after {
       //當Data物件生成時,將Data中lock欄位賦值為aspect Lock
       //為每個Data物件生成相應的aspect Lock
       thisObject.lock = new Lock( thisObject );
     }
   }
  ....

}

上述程式碼等於在Data類中加入一行:

public class Data{
  ......
  Lock lock = new Lock();
  ......
}

還有其它兩個涉及AOP程式碼執行方式:

weaving - 將aspect程式碼插入到相應程式碼中的過程,一般是編譯完成或在執行時動態完成。取決於具體AOP產品,例如AspectJ是使用特殊編譯器在編譯完成weaving,而nanning、JBoss AOP是使用動態代理API,因此在執行時動態完成weaving的。
instrumentor - 用來實現weaving功能的工具。

參考資料: