一文讀懂AspectJ

AspectJ可以說是Java中當之無愧的黑魔法。說它是黑魔法,一方面是因爲它很強大,能夠解決一些傳統編程方法論解決不了的問題,而另一方面,它也相當的晦澀,有着比較陡峭的學習曲線。

本文將帶大家探索下AspectJ是什麼,能做什麼,以及如何來做,希望通過本文能夠讓大家初窺AspectJ之門道

AOP是什麼
相信很多人第一次聽說AOP是在學習spring的時候,筆者也是。這個概念其實困擾了我很久,到底是AOP?AOP是Aspect Oriented Programming的縮寫,和OOP(Object Oriented Programming)一樣,都代表一種編程思想。不同的是,OOP是對世界萬物的抽象,而AOP做的則是對業務處理過程的抽象,一定程度上說,AOP是OOP思想的一種延續,對程序進行了進一步的封裝。

那麼AOP到底能夠解決什麼問題呢?以在現有系統上增加一個安全策略爲例,我們需要在各個模塊的代碼中不同地方添加代碼,進行安全策略的檢查。這種方式實現起來很複雜,容易出錯,而且沒法複用。這裏描述的安全問題就是一個橫切關注點問題,開發者需要找到所有需要關注的代碼斷,在現有代碼中間插入新的業務代碼(就好像對現有代碼做了切分)。類似這裏安全策略的問題還有很多,比如tracing等。
歡迎夾裙 四九··九七···五四六··一四 交流,備註CS

AspectJ基本概念
AspectJ是AOP的Java實現版本,定義了AOP的語法,可以說是對Java的一個擴展。相對於Java,AspectJ引入了join point(連接點)的概念,同時引入三個新的結構,pointcut(切點), advice(通知),inter-type declaration(跨類型聲明)以及aspect。其中pointcut和advice是AspectJ中動態額部分,用來指定在什麼條件下切斷執行,以及採取什麼動作來實現切面操作。顧名思義,這裏的pointcut就是用來定義什麼情況下進行橫切,而advice則是指橫切情況下我們需要做什麼操作,所以說pointcut和advice會動態的影響程序的運行流程。從某種角度上說,pointcut(切點)和我們平時用IDE調試程序時打的斷點很類似,當程序執行到我們打的斷點的地方的時候(運行到滿足我們定義的pointcut的語句的時候,也就是join point連接點),我們可以執行一段腳本(執行advice中定義的行爲)。

而AspectJ中的inter-type declaration(跨類型聲明)則是AspectJ中靜態的部分,它影響的是程序的靜態結構,比如成員變量,類之間的關係等。Aspect則是對前三個結構的封裝,類似於java中的類。

第一個AspectJ程序
這裏我們先不去具體探討AspectJ的語法問題,而重點關注如何用AspectJ寫一個簡單的Demo。這裏我用的開發環境是IntelliJ,且項目使用maven來構建。

maven依賴
要運行AspectJ程序,首先要引入AspectJ運行時的依賴:



org.aspectj
aspectjrt
1.8.9

除了運行時依賴,還需要aspectjweaver.jar:



org.aspectj
aspectjweaver
1.8.9

一個簡單的類
先寫一個簡單的類:

package cc.databus.aspect;

public class Account {
double balance = 200;

public boolean withdraw(int amount) {
    if (balance < amount) {
        return false;
    }
    balance = balance - amount;
    return true;
}

@Override
public String toString() {
    return "Account{" +
            "balance=" + balance +
            '}';
}

}
該類定義了一個Account類,並提供了withdraw(取款)的方法。

aspect定義
創建一個AccountAspect.aj文件來記錄取款前後的信息:

// aspect
package cc.databus.aspect;

public aspect AccountAspect {

// define a pointcut to pick up invoking Accont.withdraw 
pointcut callWithDraw(int amount, Account account):
        call(boolean Account.withdraw(int)) 
        && args(amount) 
        && target(account);

// advice definition executing before enterring method body
before(int amount, Account acc): callWithDraw(amount, acc) {
    System.out.println("Start withdraw " + amount + " from " + acc);
}


after(int amount, Account acc) returning (Object ret): callWithDraw(amount, acc) {
    System.out.print("Finish withdraw, return " 
    + ret +", account after withdraw is: " +  acc);
}

}
通過IntelliJ可以很方便的創建aspect文件,在包上面右鍵->New->Aspect:

正如你所見,上面的AccountAspect.aj定義了AspectJ的pointcut,advice以及aspect。

aspect織入(weaving)
Weaving….很奇怪的詞。。。這裏指的是將aspect中定義的advice植入到運行時的過程。這裏我們使用一個maven插件來講aspect織如,這個插件是Mojo AspectJ Maven Plugin:



org.codehaus.mojo
aspectj-maven-plugin
1.7

1.8
1.8
1.8
true
true
utf-8





compile

test-compile





OK,下面我們寫一個UT來測試下aspect是否生效了:

import cc.databus.aspect.Account;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class TestAccount {

private Account account;

@Before
public void before() {
    account = new Account();
}

@Test
public void given20AndMin10_whenWithdraw5_thenSuccess() {
    assertTrue(account.withdraw(5));
}

@Test
public void given20AndMin10_whenWithdraw100_thenFail() {
    assertFalse(account.withdraw(100));
}

}
運行測試,如果命令行有如下的輸出,則表示aspect成功織入我們的代碼,並且pointcut成功切入了Account.withdraw的調用:

Start withdraw 5 from Account{balance=200.0}
Finish withdraw, return true, account after withdraw is: Account{balance=195.0}
Process finished with exit code 0
總結
總的來說,AspectJ是一個相當晦澀難懂的技術,但是不得不承認它很強大。本文在從理論出發,先介紹AOP以及AspectJ的基本概念,然後以一個簡單的Demo程序介紹瞭如何在項目中使用AspectJ。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章