AspectJ AOP介紹


idea下aspectj程序運行示例

有些同學可能想自己編寫aspect程序進行測試練習,博主在這簡單介紹運行環境的搭建,首先博主使用的idea的IDE,因此只對idea進行介紹。首先通過maven倉庫下載工具包aspectjtools-1.8.9.jar,該工具包包含ajc核心編譯器,然后打開idea檢查是否已安裝aspectJ的插件:

配置項目使用ajc編譯器(替換javac)如下圖:

如果使用maven開發(否則在libs目錄自行引入jar)則在pom文件中添加aspectJ的核心依賴包,包含了AspectJ運行時的核心庫文件:

<dependency>
    <groupId>aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>

新建文件處創建aspectJ文件,然后就可以像運行java文件一樣,操作aspect文件了。

這里先進行一個簡單案例的演示。編寫一個HelloWord的類,然后利用AspectJ技術切入該類的執行過程。

/**
 * Created by zejian on 2017/2/15. */
public class HelloWord {

    public void sayHello(){
        System.out.println("hello world !");
    }
    public static void main(String args[]){
        HelloWord helloWord =new HelloWord();
        helloWord.sayHello();
    }
} 

編寫AspectJ類,注意關鍵字為aspect(MyAspectJDemo.aj,其中aj為AspectJ的后綴),含義與class相同,即定義一個AspectJ的類

/**
 * Created by zejian on 2017/2/15.
 * 切面類
 */
public aspect MyAspectJDemo {
    /**
     * 定義切點,日志記錄切點
     */
    pointcut recordLog():call(* HelloWord.sayHello(..));

    /**
     * 定義切點,權限驗證(實際開發中日志和權限一般會放在不同的切面中,這里僅為方便演示)
     */
    pointcut authCheck():call(* HelloWord.sayHello(..));

    /**
     * 定義前置通知!
     */
    before():authCheck(){
        System.out.println("sayHello方法執行前驗證權限");
    }

    /**
     * 定義后置通知
     */
    after():recordLog(){
        System.out.println("sayHello方法執行后記錄日志");
    }
} 

ok~,運行helloworld的main函數:

對於結果不必太驚訝,完全是意料之中。我們發現,明明只運行了main函數,卻在sayHello函數運行前后分別進行了權限驗證和日志記錄,事實上這就是AspectJ的功勞了。

AOP中抽象概念(切入點(pointcut)、通知(advice)、切面(aspect)、織入(weaving))解釋

對aspectJ有了感性的認識后,再來聊聊aspectJ到底是什么?AspectJ是一個java實現的AOP框架,它能夠對java代碼進行AOP編譯(一般在編譯期進行),讓java代碼具有AspectJ的AOP功能(當然需要特殊的編譯器),可以這樣說AspectJ是目前實現AOP框架中最成熟,功能最豐富的語言,更幸運的是,AspectJ與java程序完全兼容,幾乎是無縫關聯,因此對於有java編程基礎的工程師,上手和使用都非常容易。

在案例中,我們使用aspect關鍵字定義了一個類,這個類就是一個切面,它可以是單獨的日志切面(功能),也可以是權限切面或者其他,在切面內部使用了pointcut定義了兩個切點,一個用於權限驗證,一個用於日志記錄,而所謂的切點就是那些需要應用切面的方法,如需要在sayHello方法執行前后進行權限驗證和日志記錄,那么就需要捕捉該方法,而pointcut就是定義這些需要捕捉的方法(常常是不止一個方法的),這些方法也稱為目標方法,最后還定義了兩個通知,通知就是那些需要在目標方法前后執行的函數,如before()即前置通知在目標方法之前執行,即在sayHello()方法執行前進行權限驗證,另一個是after()即后置通知,在sayHello()之后執行,如進行日志記錄。到這里也就可以確定,切面就是切點和通知的組合體,組成一個單獨的結構供后續使用,下圖協助理解。

這里簡單說明一下切點的定義語法:關鍵字為pointcut,定義切點,后面跟着函數名稱,最后編寫匹配表達式,此時函數一般使用call()或者execution()進行匹配,這里我們統一使用call()

pointcut 函數名 : 匹配表達式

案例:recordLog()是函數名稱,自定義的,* 表示任意返回值,接着就是需要攔截的目標函數,sayHello(..)的..,表示任意參數類型。這里理解即可,后面Spring AOP會有關於切點表達式的分析,整行代碼的意思是使用pointcut定義一個名為recordLog的切點函數,其需要攔截的(切入)的目標方法是HelloWord類下的sayHello方法,參數不限。

pointcut recordLog():call(* HelloWord.sayHello(..)); 

關於定義通知的語法:首先通知有5種類型分別如下:

  • before 目標方法執行前執行,前置通知
  • after 目標方法執行后執行,后置通知
  • after returning 目標方法返回時執行 ,后置返回通知
  • after throwing 目標方法拋出異常時執行 異常通知
  • around 在目標函數執行中執行,可控制目標函數是否執行,環繞通知

語法:

[返回值類型] 通知函數名稱(參數) [returning/throwing 表達式]:連接點函數(切點函數){ 
函數體 
}

案例如下,其中要注意around通知即環繞通知,可以通過proceed()方法控制目標函數是否執行。

/**
  * 定義前置通知
  *
  * before(參數):連接點函數{
  *     函數體
  * }
  */
 before():authCheck(){
     System.out.println("sayHello方法執行前驗證權限");
 }

 /**
  * 定義后置通知
  * after(參數):連接點函數{
  *     函數體
  * }
  */
 after():recordLog(){
     System.out.println("sayHello方法執行后記錄日志");
 }


 /**
  * 定義后置通知帶返回值
  * after(參數)returning(返回值類型):連接點函數{
  *     函數體
  * }
  */
 after()returning(int x): get(){
     System.out.println("返回值為:"+x);
 }

 /**
  * 異常通知
  * after(參數) throwing(返回值類型):連接點函數{
  *     函數體
  * }
  */
 after() throwing(Exception e):sayHello2(){
     System.out.println("拋出異常:"+e.toString());
 }



 /**
  * 環繞通知 可通過proceed()控制目標函數是否執行
  * Object around(參數):連接點函數{
  *     函數體
  *     Object result=proceed();//執行目標函數
  *     return result;
  * }
  */
 Object around():aroundAdvice(){
     System.out.println("sayAround 執行前執行");
     Object result=proceed();//執行目標函數
     System.out.println("sayAround 執行后執行");
     return result;
 } 

切入點(pointcut)和通知(advice)的概念已比較清晰,而切面則是定義切入點和通知的組合如上述使用aspect關鍵字定義的MyAspectJDemo,把切面應用到目標函數的過程稱為織入(weaving)。在前面定義的HelloWord類中除了sayHello函數外,還有main函數,以后可能還會定義其他函數,而這些函數都可以稱為目標函數,也就是說這些函數執行前后也都可以切入通知的代碼,這些目標函數統稱為連接點,切入點(pointcut)的定義正是從這些連接點中過濾出來的,下圖協助理解。

AspectJ的織入方式及其原理概要

經過前面的簡單介紹,我們已初步掌握了AspectJ的一些語法和概念,但這樣仍然是不夠的,我們仍需要了解AspectJ應用到java代碼的過程(這個過程稱為織入),對於織入這個概念,可以簡單理解為aspect(切面)應用到目標函數(類)的過程。對於這個過程,一般分為動態織入和靜態織入,動態織入的方式是在運行時動態將要增強的代碼織入到目標類中,這樣往往是通過動態代理技術完成的,如Java JDK的動態代理(Proxy,底層通過反射實現)或者CGLIB的動態代理(底層通過繼承實現),Spring AOP采用的就是基於運行時增強的代理技術,這里主要重點分析一下靜態織入,ApectJ采用的就是靜態織入的方式。ApectJ主要采用的是編譯期織入,在這個期間使用AspectJ的acj編譯器(類似javac)把aspect類編譯成class字節碼后,在java目標類編譯時織入,即先編譯aspect類再編譯目標類。

關於ajc編譯器,是一種能夠識別aspect語法的編譯器,它是采用java語言編寫的,由於javac並不能識別aspect語法,便有了ajc編譯器,注意ajc編譯器也可編譯java文件。為了更直觀了解aspect的織入方式,我們打開前面案例中已編譯完成的HelloWord.class文件,反編譯后的java代碼如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.zejian.demo;

import com.zejian.demo.MyAspectJDemo;
//編譯后織入aspect類的HelloWord字節碼反編譯類
public class HelloWord {
    public HelloWord() {
    }

    public void sayHello() {
        System.out.println("hello world !");
    }

    public static void main(String[] args) {
        HelloWord helloWord = new HelloWord();
        HelloWord var10000 = helloWord;

   try {
        //MyAspectJDemo 切面類的前置通知織入
        MyAspectJDemo.aspectOf().ajc$before$com_zejian_demo_MyAspectJDemo$1$22c5541();
        //目標類函數的調用
           var10000.sayHello();
        } catch (Throwable var3) {
        MyAspectJDemo.aspectOf().ajc$after$com_zejian_demo_MyAspectJDemo$2$4d789574();
            throw var3;
        }

        //MyAspectJDemo 切面類的后置通知織入 
        MyAspectJDemo.aspectOf().ajc$after$com_zejian_demo_MyAspectJDemo$2$4d789574();
    }
} 

顯然AspectJ的織入原理已很明朗了,當然除了編譯期織入,還存在鏈接期(編譯后)織入,即將aspect類和java目標類同時編譯成字節碼文件后,再進行織入處理,這種方式比較有助於已編譯好的第三方jar和Class文件進行織入操作,由於這不是本篇的重點,暫且不過多分析。

參考資料

http://blog.csdn.net/javazejian/article/details/56267036#神一樣的aspectj-aop的領跑者


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM