AOP-Android-AspectJ使用

一. 介紹

以下介紹的其中部分內容是摘抄修改自網絡:

AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。

它並沒有幫助我們解決任何新的問題,它只是提供了一種更好的辦法,能夠用更少的工作量來解決現有的一些問題,並且使得系統更加健壯,可維護性更好。同時,它讓我們在進行系統架構和模塊設計的時候多了新的選擇和新的思路。

AOP通過對切面進行切入執行代碼,達到對原代碼無侵入,比如如下圖:有A->B,C->D流程,在不改變原流程代碼情況下,咱們可以橫向插入Aspect流程執行。

AOP

使用AOP,咱們這裏將講一個AOP庫Aspectj,咱們利用它來實現AOP。

  1. AspectJ是什麼

AspectJ是一個代碼生成工具(Code Generator)。
AspectJ語法就是用來定義代碼生成規則的語法。您如果使用過Java Compiler Compiler (JavaCC),您會發現,兩者的代碼生成規則的理念驚人相似。
AspectJ有自己的語法編譯工具,編譯的結果是Java Class文件,運行的時候,classpath需要包含AspectJ的一個jar文件(Runtime lib)。
AspectJ和xDoclet的比較。AspectJ和EJB Descriptor的比較。

  1. AspectJ能幹什麼?

AOP是Object Oriented Programming(OOP)的補充。
OOP能夠很好地解決對象的數據和封裝的縱向問題,卻不能很好的解決Aspect(“方面”)分離的橫向問題

  1. 使用價值

認證、事務、日誌等等

如:你已經寫好一個功能,有一天客戶提出一個需求,需要對調用這個服務的用戶進行權限認證,這時候通過AspectJ實現一個AOP是你的首選。

二. 使用

(一) 切入點Pointcuts

咱們要切入執行代碼,需要有一個切入點,可以作爲切入點的描述,Pointcuts,以下列舉幾個常用的Pointcuts:

常用Pointcuts 使用 說明
方法與構造方法(Methods and Constructors) Call 或者 execution 在切入點方法前後插入
字段(Fields) get 或 set 通過插入字段相應的get與set方法,來達到對字段值修改和返回的變相處理
異常(Exception Handlers) handler 通過handler方式,得到異常信息,做一些操作

(二) 切入點使用介紹

咱們主要切入的都是在方法上,構造方法也好普通方法也好,接下來看下如何使用進行切入代碼執行。

1. 配置Gradle依賴

咱們這裏以Android代碼爲例:

  1. Project的build.gradle中添加
buildscript {
    dependencies {
        classpath group: 'org.aspectj', name: 'aspectjtools', version: '1.9.2'
        // https://mvnrepository.com/artifact/org.aspectj/aspectjweaver
        classpath group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.2'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
  1. App工程的build.gradle中添加依賴
//aspectj庫
    implementation group: 'org.aspectj', name: 'aspectjrt', version: '1.9.2'
  1. App工程的build.gradle中添加配置
/*Aspectj配置*/
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

//log 打印工具和構建配置
final def log = project.logger
final def variants = project.android.applicationVariants

variants.all { variant ->
    // 通過debug判斷是否需要打入aspectj,比如咱們切入的是打印執行時間工具代碼,那如果release不需要打則return掉
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return;
    }

    //執行到這裏,使 aspectj 配置生效
    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)

        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler);
        //在編譯時打印信息如警告、error 等等
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break;
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break;
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
}
2. 切入執行使用方式
  1. 創建切入處理對象,使用@Aspect註解供庫識別,比如:
@Aspect
public class AspectJAfterBefore {
}
  1. 在第1步類中聲明切入點,使用@Pointcuts註解來標明匹配:
/**
     * 切入方法
     */
    @Pointcut("call(* shixin.aopdemo.aspect_afterbefore.AfterBeforeTestHelper.aspectjTest(..))")
    public void callWith() {
    }

說明:call代表方式,括號中的第一個 * 代表方法的返回值爲任意,後面則是切入方法的全路徑,方法括號(…),其中…代碼參數任意

注:當然這個不是必須,可以將Pointcut註解中的value直接放入之後的切入代碼,比 如@After註解中的value,不過使用@Pointcut來聲明一下會更方便

  1. 在第1步類中加切入代碼編寫

比如咱們這裏使用@After註解,該註解的方法會在切入點後執行:

/**
     * 使用After,那麼將在切入點方法執行之後執行該切入方法代碼
     *
     * @param jp 切入點信息參數(如果不需要相關信息,可以不填這個參數)
     */
    @After("callWith()")
    public void testCallAft(JoinPoint jp) {
        //打印返回值
        Log.i(TAG, "AspectJAfterBefore after,args:" + Arrays.toString(jp.getArgs()));
    }

說明:那麼testCallAft方法則會在切入點"callWith()"之後執行,這個callWith()即第2步的方法名稱,所以就是切入到第2步指定的方法代碼shixin.aopdemo.aspect_afterbefore.AfterBeforeTestHelper.aspectjTest()方法之後會執行testCallAft中的打印

(三) 方法切入-Advice:

對於方法切入方式有call和execution,咱們使用這兩種方式來對方法切入,然後切入代碼Advice,對應After、Before、Around等

call與execution的區別,execution是在方法內部插入,call是在方法外部插入,如下圖:

那麼咱們看看常用的幾種Advice:

1. After 與 Before:

就如字面意思,就是在插入點之前和之後執行的代碼。

  1. 測試對象類:testAfterBeforeOne()調用aspectjTest方法
public void testAfterBeforeOne() {
        Log.d(TAG, "testAfterBeforeOne");
        int i = aspectjTest();
        Log.i(TAG, "testAfterBeforeOne over");
    }

    /**
     * 被調用
     */
    private int aspectjTest() {
        Log.i(TAG, "exe aspectjTest");
        return 5;
    }
  1. 切入代碼:切入點爲aspectjTest
/**
 * afterreturn切入代碼
 */
@Aspect
public class AspectJAfterBefore {
    private static final String TAG = AspectJAfterBefore.class.getSimpleName();
    /**
     * 切入方法
     */
    @Pointcut("call(* shixin.aopdemo.aspect_afterbefore.AfterBeforeTestHelper.aspectjTest(..))")
    public void callWith() {
    }

    /**
     * 使用After
     *
     * @param jp 切入點信息參數(如果不需要相關信息,可以不填這個參數)
     */
    @After("callWith()")
    public void testCallAft(JoinPoint jp) {
        //打印返回值
        Log.i(TAG, "AspectJAfterBefore after,args:" + Arrays.toString(jp.getArgs()));
    }

    /**
     * 使用before
     */
    @Before("callWith()")
    public void testCallBef() {
        Log.d(TAG, "AspectJAfterBefore before");
    }
}
  1. 打印:
D/AfterBeforeTestHelper: testAfterBeforeOne
D/AspectJAfterBefore: AspectJAfterBefore before
I/AfterBeforeTestHelper: exe aspectjTest
I/AspectJAfterBefore: AspectJAfterBefore after,args:[]
I/AfterBeforeTestHelper: testAfterBeforeOne over

從打印即可看出,在咱們切入的aspectjTest方法前執行了切入了指定的before方法,之後切入了指定的after方法。

2. Around

Around相當於是After和Before的結合,是在切入方法的前後進行的。

  1. 測試對象類:
public void testAroundOne() {
        Log.d(TAG, "testAroundOne");
        aspectjTest();
        Log.i(TAG, "testAroundOne over");
    }
    /**
     * 被調用
     */
    private void aspectjTest() {
        Log.d(TAG, "aspectjTest");
    }
  1. 切入點類:
@Aspect
public class AspectJAround {
    private static final String TAG = AspectJAround.class.getSimpleName();
    /**
     * 切入方法
     */
    @Pointcut("call(* shixin.aopdemo.aspect_around.AroundTestHelper.aspectjTest(..))")
    public void callWith() {
    }
    @Around("callWith()")
    public void testCallAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Log.d(TAG, "testCallAround before");
        //採用proceed來調用方法的執行
        proceedingJoinPoint.proceed();

        //如果方法是有參數的話,可以採用調用參數方式來執行
//        Object[] args = proceedingJoinPoint.getArgs();
//        proceedingJoinPoint.proceed(args);
        Log.d(TAG, "testCallAround after");
    }
}
  1. 打印:
D/AroundTestHelper: testAroundOne
D/AspectJAround: testCallAround before
D/AroundTestHelper: aspectjTest
D/AspectJAround: testCallAround after
I/AroundTestHelper: testAroundOne over

如代碼一樣,around裏咱們可以通過ProceedingJoinPoint的proceed方法來讓方法執行,那麼切入點不調用這個方法,那方法不就不會執行了嗎?哈哈,利用這個可以做一些事,比如單點登陸的權限判斷。

注:Around,對於這種確定方法的切入點是不支持帶返回值的,只能返回類型爲void

3. AfterReturn

如果想在after之後,還想獲取到切入方法的返回值,那麼AftetReturn就可以上了,看看用法

  1. 測試對象類:這裏咱們從testAfterReturnOne進入,去調用aspectjTest方法,傳入兩個參數並得到返回值
/**
 * AfterReturn測試
 */
public class AfterReturnTestHelper {

    private static final String TAG = AfterReturnTestHelper.class.getSimpleName();

    public void testAfterReturnOne() {
        Log.d(TAG, "testAfterReturnOne");
        //調用帶返回值方法,傳入1,3參數
        int i = aspectjTest(1, 3);
        Log.i(TAG, "testAfterReturnOne over");
    }

    /**
     * 被調用
     */
    private int aspectjTest(int a, int b) {
        return 5;
    }
}
  1. 那麼看看切入核心方法:切入點爲第一步的aspectjTest方法
/**
 * afterreturn
 */
@Aspect
public class AspectJAftwerReturn {
    private static final String TAG = AspectJAftwerReturn.class.getSimpleName();

    /**
     * 切入方法
     */
    @Pointcut("call(* shixin.aopdemo.aspect_afterreturn.AfterReturnTestHelper.aspectjTest(..))")
    public void callWith() {
    }

    /**
     * 使用AfterReturning
     * @param jp JoinPoint,可獲取方法、類、參數等信息(這個參數不是AfterReturning特有,不用關心)
     * @param ret 返回值 AfterReturning特有,注意:註解中的參數名稱與方法中的名稱要一樣,比如這裏的ret
     */
    @AfterReturning(returning = "ret",  pointcut = "callWith()")
    public void testCallAft(JoinPoint jp, Object ret) {
        //打印返回值
        Log.d(TAG, "AspectJAftwerReturn after,return:" + ret);
        Log.i(TAG, "AspectJAftwerReturn after,args:" + Arrays.toString(jp.getArgs()));
    }

    @Before("callWith()")
    public void testCallBef() {
        Log.d(TAG, "AspectJAftwerReturn before");
    }
}
  1. 打印:
D/AfterReturnTestHelper: testAfterReturnOne
D/AspectJAftwerReturn: AspectJAftwerReturn before
D/AspectJAftwerReturn: AspectJAftwerReturn after,return:5
I/AspectJAftwerReturn: AspectJAftwerReturn after,args:[1, 3]
I/AfterReturnTestHelper: testAfterReturnOne over

可以看到,通過AfterReturning中的’returning’則可以獲取到方法的返回值了。

(四) 字段切入-Field

咱們看看字段切入,字段切入其主要是針對字段的get和set方法做切入處理

  1. 測試代碼:對age做賦值操作,並打印。對name做賦值,並打印
private static final String TAG = .class.getSimpleName();
    /**
     * 姓名,String類型
     */
    private String name;
    /**
     * 年齡 int
     */
    private int age;

    public void testStart() {
        Log.d(TAG, "testStart");
        //給age賦值,那麼代表了做了set
        this.age = 5;
        Log.i(TAG, "get age value:" + age);

        this.name = "小寶貝";
        //打印name,等於獲取name值,代表了get
        Log.i(TAG, "get name value:" + name);
        Log.i(TAG, "testStart over");
    }
  1. 切入代碼: 對age的賦值set做切入,切入代碼將賦值減2。對name的獲取get做切入,添加後綴"老大"後返回。
/**
 * Field切入
 */
@Aspect
public class AspectJFiled {
    private static final String TAG = AspectJFiled.class.getSimpleName();

    /**
     * 切入方法
     */
    @Pointcut("get(String shixin.aopdemo.aspect_filed.FiledHelper.name)")
    public void getMethod() {
    }

    @Pointcut("set(int shixin.aopdemo.aspect_filed.FiledHelper.age)")
    public void setMethod() {
    }

    @Around("getMethod()")
    public String testGetAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Log.d(TAG, "testGetAround before");
        //採用proceed來調用方法的執行,得到返回String的字段
        String proceedStr = (String) proceedingJoinPoint.proceed();
        //修改返回字段
        String returnStr = proceedStr + "老大";
        Log.d(TAG, "testGetAround after");
        return returnStr;
    }

    @Around("setMethod()")
    public void testSetAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Log.d(TAG, "testSetAround before");
        //採用getArgs方法獲取設置的參數String的字段
        Integer argsAge = (Integer) proceedingJoinPoint.getArgs()[0];
        //修改設置字段,將年齡改小兩歲
        int modifyAge = argsAge - 2;
        proceedingJoinPoint.proceed(new Integer[]{modifyAge});
        Log.d(TAG, "testSetAround after");
    }
}
  1. 調用testStart()後的打印:
D/FiledHelper: testStart
D/AspectJFiled: testSetAround before
D/AspectJFiled: testSetAround after
I/FiledHelper: get age value:3
D/AspectJFiled: testGetAround before
D/AspectJFiled: testGetAround after
I/FiledHelper: get name value:小寶貝老大
I/FiledHelper: testStart over

由此可看出,雖然我們沒有明寫get和set方法,但是由於切入field的原因,在賦值和獲取值的地方會自動切入我們設置定代碼。

注:使用Filed切入的時候需要考慮好,避免因爲使用不當造成的一些奇怪現象哦

(五) 異常捕獲切入-Exception

咱們說說對指定異常進行代碼切入,比如來看看捕獲應用中的IOException,並做一些操作

  1. 測試代碼:
public void testStart() {
        Log.d(TAG, "testStart");
        try {
            testException();
        } catch (IOException e) {
            e.printStackTrace();
        }
        Log.i(TAG, "testStart over");
    }

    private void testException() throws IOException {
        //拋出異常
        throw new IOException("testStart");
    }
  1. 切入代碼:
@Aspect
public class AspectJException {
    private static final String TAG = AspectJException.class.getSimpleName();

    /**
     * 異常產生之後插入
     * handler方式,指定IO異常,並獲取參數ex
     */
    @Before(value = "handler(java.io.IOException+) && args(ex)")
    public void testHandleBefore(IOException ex) throws Throwable {
        Log.d(TAG, "testHandleBefore before");
        Log.d(TAG, "出現io異常:" + ex);
        //打印棧信息
        ex.printStackTrace();
        //還可以統一寫入文件等
    }
}
  1. 打印:
D/ExceptionHelper: testStart
D/AspectJException: testHandleBefore before
D/AspectJException: 出現io異常:java.io.IOException: testStart
W/System.err: java.io.IOException: testStart
W/System.err:     at shixin.aopdemo.aspect_handle_exception.ExceptionHelper.testStart(ExceptionHelper.java:17)
W/System.err:     at shixin.aopdemo.AspectHelper.test(AspectHelper.java:27)
W/System.err:     at shixin.aopdemo.activity.MainActivity.testAspect(MainActivity.java:36)
W/System.err:     at shixin.aopdemo.activity.MainActivity.onCreate(MainActivity.java:32)
W/System.err:     at android.app.Activity.performCreate(Activity.java:6975)
W/System.err:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213)
W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770)
I/ExceptionHelper: testStart over

由示例代碼和打印可看出,我們只是指定了IO異常,就將IO異常相關給收集了,那麼如果handler括號裏的是*,那就將所有的都給收集了,那麼通過切入對某種異常捕獲,可以將應用中對應的異常代碼都進行切入,做日誌記錄啊等等都可以了。

注意: 這裏切入代碼沒有用@Pointcuts註解,因爲需要用到args(e)來獲取註解參數信息,所以需要直接放到@Before等的value中

(六) 組合-Combine

前面將到了切入點的匹配,除了單一的匹配,其還支持 !、||、&& 這樣的組合匹配。在講這個之前先提一下:within與withincode

說明
within 匹配定義類型在指定位置內,傳入指定類型爲TypePattern,即類
withincode 匹配定義類型在指定位置內,傳入類型爲Signature,即方法

好了,那麼我們結合withincode來講一個帶邏輯與的操作:

  1. 測試類: 咱們要切入的方法爲aspectJ,它被test1和test2調用
/**
 * Combine測試
 */
public class CombineHelper {

    private static final String TAG = CombineHelper.class.getSimpleName();

    public void testStart() {
        Log.d(TAG, "testStart");
        test1();
        test2();
        Log.i(TAG, "testStart over");
    }

    /**
     * 測試方法1,調用aspectJ()
     */
    private void test1() {
        Log.d(TAG, "test1");
        aspectJ();
    }

    /**
     * 測試方法2,調用aspectJ()
     */
    private void test2() {
        Log.d(TAG, "test2");
        aspectJ();
    }

    /**
     * 被切入的方法
     */
    private void aspectJ() {
        Log.d(TAG, "conbineNotPickedOut2");
    }
}

如上代碼,那麼正常情況下,咱們執行testStart()的時候,aspectJ()會被調用兩次就會被插入執行兩次,但是我們只想在test1調用aspectJ的時候插入,test2調用aspectJ方法的時候不做插入,那就需要&&組合上場了,不多話,上代碼:

  1. 切入代碼:切入aspectJ() && 非test2()方法內調用
/**
 * 邏輯切入
 */
@Aspect
public class AspectJCombine {
    private static final String TAG = AspectJCombine.class.getSimpleName();

    /**
     * 切入方法aspectJ
     */
    @Pointcut("call(void shixin.aopdemo.aspect_combine.CombineHelper.aspectJ())")
    public void exeMethod() {
    }

    /**
     * !withincode  代表不是指定方法內
     */
    @Pointcut("!withincode(void shixin.aopdemo.aspect_combine.CombineHelper.test2())")
    public void notPickedOut1(){
    }

    /**
     * before 其中帶 && 也就是說兩個都滿足才進行插入
     */
    @Before("exeMethod()&&notPickedOut1()")
    public void testExeAround() throws Throwable {
        Log.d(TAG, "testCombination before");
    }
}

就這樣就能達到在test2方法內調用aspectJ的時候不切入代碼了,對於其他邏輯 || 等等,都差不多,根據實際場景實際使用即可。

注:在這裏切入方法需要用call,不用execution,否則不起效果哦!至於爲什麼,想想call與execution的區別就知道了。

(七) Inter-Type

Aspectj可以聲明類實現一個接口或繼承一個類,其也是通過插入代理的方式來執行的,這個也或許用的不是那麼多,因爲真的要實現和繼承往往我們會自行實現,也簡單看一看:

  1. 創建JavaBean:Baby類
/**
 * javabean:baby
 */
public class Baby {
    /**
     * 姓名
     */
    private String name;

    public Baby(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. 創建準備聲明實現的接口:People
/**
 * 人的接口
 */
public interface People {
    /**
     * 提供年齡獲取
     * @return
     */
    int getAge();
}
  1. 切入代碼:利用@DeclareParents註解
/**
 * intertype切入
 */
@Aspect
public class AspectJInterType {
    private static final String TAG = AspectJInterType.class.getSimpleName();

    /**
     * 對人這個接口:
     * 1. 指定其切入點爲Baby類
     * 2. 指定真正的實現類教師類
     */
    @DeclareParents(value = "shixin.aopdemo.aspect_intertype.Baby", defaultImpl = Teacher.class)
    private People people;

    /**
     * 提供教師類,實現人的接口
     */
    public static class Teacher implements People {

        /**
         * 提供年齡獲取
         *
         * @return
         */
        @Override
        public int getAge() {
            return 18;
        }
    }
}
  1. 測試代碼:
Log.d(TAG, "testStart");
        Baby baby = new Baby("小寶貝");
        Log.i(TAG, "name:" + baby.getName());
        if (baby instanceof People) {
            Log.i(TAG, "baby instance of People true,baby age:" + ((People) baby).getAge());
        }
        Log.i(TAG, "testStart over");
  1. 輸出:
D/InterTypeHelper: testStart
I/InterTypeHelper: name:小寶貝
I/InterTypeHelper: baby instance of People true,baby age:18
I/InterTypeHelper: testStart over

通過在切入代碼裏面指定defaultImpl的class爲Teacher,使得Baby的getAge行爲由Teacher來承擔,看看編譯後的Baby就清楚了:

//1. 編譯後使得Baby實現了People接口
public class Baby implements People {
    private String name;

    public Baby(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        //2. 採用創建了Teacher來進行返回,即使用Teacher來作爲getAge的承擔者
        if (this.ajc$instance$shixin_aopdemo_aspect_intertype_AspectJInterType$shixin_aopdemo_aspect_intertype_People == null) {
            this.ajc$instance$shixin_aopdemo_aspect_intertype_AspectJInterType$shixin_aopdemo_aspect_intertype_People = new Teacher();
        }

        return this.ajc$instance$shixin_aopdemo_aspect_intertype_AspectJInterType$shixin_aopdemo_aspect_intertype_People.getAge();
    }
}

可以看到是採用代理方式,最後getAge的承擔者爲Teacher。

(八) 自定義處理-Annotation

前面講的切入方法都是固定的方法,但是實際使用中,我們一般切入的方法都是不固定的,在需要的時候才切入,那麼就採用註解來做切入,咱們來做一個切入輸出方法執行時間的工具

  1. 定義註解:因爲aspectj也是編譯時註解,所以類型爲CLASS即可,目標爲方法
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface AnnoExeTimeLogAspect {
}
  1. 切入方法:
@Aspect
public class LogAspect {
    /*下面是使用註解自定義方式*/
    @Pointcut("execution(@shixin.aopdemo.logh.AnnoExeTimeLogAspect * *(..))")
    public void testExeMethodTime() {
    }

    @Around("testExeMethodTime()")
    public void timeEva(ProceedingJoinPoint joinPoint) throws Throwable {
        long timeBefore = SystemClock.currentThreadTimeMillis();
        Object[] args = joinPoint.getArgs();
        /*分帶參數執行和無參數執行*/
        if (args != null && args.length > 0) {
            Log.d("timeEva", "have args:" +  Arrays.toString(args));
            joinPoint.proceed(args);
        } else {
            Log.d("timeEva", "have no args");
            joinPoint.proceed();
        }
        long timeAfter = SystemClock.currentThreadTimeMillis();
        long time = timeAfter - timeBefore;
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalStateException("LoginFilter 註解只能用於方法上");
        }
        MethodSignature methodSignature = (MethodSignature) signature;
        Log.i(methodSignature.getDeclaringType().getSimpleName(), joinPoint.getSignature().getName() + ",花費時間:" + time);
    }
}
  1. 被調用的方法:包含咱們要切入的註解,被註解的方法即會被切入
//包含咱們的註解
@AnnoExeTimeLogAspect()
    private void testLog(int value) {
        Log.d("MainActivity", "日誌:" + value);
    }
  1. 打印:
D/timeEva: have args:[7]
D/MainActivity: 日誌:7
I/MainActivity: testLog,花費時間:0

可以看到,咱們將註解放到方法上,方法被調用後,就會執行咱們切入的方法,先打印參數,再proceed執行方法,再打印話費時間。就這樣一個簡單打印方法執行時間的註解處理就OK了。

最後,項目可參考: https://github.com/nxSin/AOP-Android-Demo

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