1. 程式人生 > >初窺AspectJ

初窺AspectJ

odi start 方式 不同的 alt 解決 奇怪 str maven插件

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

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

AOP是什麽

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

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

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運行時的依賴:

<!--aspectj runtime classes -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version>
</dependency>

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

<!--to introduce aspect to java class in load time-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

一個簡單的類

先寫一個簡單的類:

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:

<plugins>
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.7</version>
        <configuration>
            <complianceLevel>1.8</complianceLevel>
            <source>1.8</source>
            <target>1.8</target>
            <showWeaveInfo>true</showWeaveInfo>
            <verbose>true</verbose>
            <encoding>utf-8</encoding>
        </configuration>
        <executions>
            <execution>
                <goals>
                    <!--use this goal to weave all your main classes-->
                    <goal>compile</goal>
                    <!--use this goal to weave all your test classes-->
                    <goal>test-compile</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

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。

文章同步發布在我的個人博客https://jianyuan.me上,歡迎拍磚。
傳送門: 初窺AspectJ

初窺AspectJ