Android AOP三劍客之AspectJ
前言
本章節目的不是詳細的介紹AspectJ的細節,而是最近專案用到了AspectJ,通過一個簡單例子來看下定義切片以及使用切片的流程是怎樣的。
AspectJ
-
AspectJ 是使用最為廣泛的 AOP 實現方案,適用於 Java 平臺,官網地址: ofollow,noindex">http://www.eclipse.org/aspectj/ 。AspectJ 是在靜態織入程式碼,即在編譯期注入程式碼的。
-
AspectJ 提供了一套全新的語法實現,完全相容 Java(跟 Java 之間的區別,只是多了一些關鍵詞而已)。同時,還提供了純 Java 語言的實現,通過註解的方式,完成程式碼編織的功能。因此我們在使用 AspectJ 的時候有以下兩種方式:
-
使用 AspectJ 的語言進行開發
-
通過 AspectJ 提供的註解在 Java 語言上開發
-
-
因為最終的目的其實都是需要在位元組碼檔案中織入我們自己定義的切面程式碼,不管使用哪種方式接入 AspectJ,都需要使用 AspectJ 提供的程式碼編譯工具 ajc 進行編譯。
-
在 Android+Studio/">Android Studio 上一般使用註解的方式使用 AspectJ,因為 Android Studio 沒有 AspectJ 外掛,無法識別 AspectJ 的語法(不過在 Intellij IDEA 收費版上可以使用 AspectJ 外掛),所以後面的語法說明和示例都是以註解的實現方式。
常用術語
在瞭解AspectJ的具體使用之前,先了解一下其中的一些基本的術語概念,這有利於我們掌握AspectJ的使用以及AOP的程式設計思想。
JoinPoints
JoinPoints(連線點),程式中可能作為程式碼注入目標的特定的點。在AspectJ中可以作為JoinPoints的地方包括:

PointCuts
PointCuts(切入點),其實就是程式碼注入的位置。與前面的JoinPoints不同的地方在於,其實PointCuts是有條件限定的JoinPoints。比如說,在一個Java原始檔中,會有很多的JoinPoints,但是我們只希望對其中帶有@debug註解的地方才注入程式碼。所以,PointCuts是通過語法標準給JoinPoints添加了篩選條件限定。
Advice
Advice(通知),其實就是注入到class檔案中的程式碼片。典型的 Advice 型別有 before、after 和 around,分別表示在目標方法執行之前、執行後和完全替代目標方法執行的程式碼。
Aspect
Aspect(切面),Pointcut 和 Advice 的組合看做切面。
Weaving
注入程式碼(advices)到目標位置(joint points)的過程
接下來通過專案看一下實踐過程

傳送門: android-aop-samples
在annotation裡定義註解
@Retention(RetentionPolicy.CLASS) @Target(ElementType.METHOD) public @interface CheckLogin { }
在android studio的android工程中使用AspectJ的時候,我們需要在專案的build.gradle的檔案中新增一些配置:
dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' ... }
在新建module裡定義AspectjPlugin,也可以直接寫到gradle裡面,固定寫法沒啥說的
public class AspectjPlugin implements Plugin<Project> { void apply(Project project) { project.dependencies { compile 'org.aspectj:aspectjrt:1.8.9' } final def log = project.logger log.error "========================"; log.error "Aspectj切片開始編織Class!"; log.error "========================"; project.android.applicationVariants.all { variant -> def 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); 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; } } } } } }
在app的build.gradle裡面
import com.app.plugin.AspectjPlugin apply plugin: AspectjPlugin
定義切片
@Aspect public class CheckLoginAspect { @Pointcut("execution(@com.app.annotation.aspect.CheckLogin * *(..))")//方法切入點 public void methodAnnotated() { } @Around("methodAnnotated()")//在連線點進行方法替換 public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable { if (!SharedPreferenceUtil.isLogin()) { Snackbar.make(AopApplication.getAppContext().getCurActivity().getWindow().getDecorView(), "請先登入!", Snackbar.LENGTH_LONG) .setAction("登入", new View.OnClickListener() { @Override public void onClick(View view) { SharedPreferenceUtil.setLogin(AopApplication.getAppContext(), true); Toast.makeText(AopApplication.getAppContext(), "登入成功", Toast.LENGTH_SHORT).show(); } }).show(); return; } joinPoint.proceed();//執行原方法 } }
在MainActivity裡面使用註解@CheckLogin,看下build/intermediates/classes編譯出來的class裡面的插入程式碼
@CheckLogin public void doMarkDown() { JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, this, this);Object[] arrayOfObject = new Object[2];arrayOfObject[0] = this;arrayOfObject[1] = localJoinPoint;CheckLoginAspect.aspectOf().aroundJoinPoint(new MainActivity.AjcClosure1(arrayOfObject).linkClosureAndJoinPoint(69648)); } static final void doMarkDown_aroundBody0(MainActivity ajc$this, JoinPoint paramJoinPoint) { Toast.makeText(AopApplication.getAppContext(), , 1).show(); }
使用總結
1.定義註解
2.新增入口plugin或者直接寫在gradle裡
3.定義切片,設定@Pointcut使用execution來設定方法的切入點為com.app.annotation.aspect包下的CheckLogin
4.編寫切片處理邏輯在Advice裡,Advice就是我們插入的程式碼可以以何種方式插入,有Before 還有 After、Around
5.在專案裡使用切片,達到在指定位置插入程式碼的目的,可以在具體專案裡面同一種場景使用該註解達到處理切面的問題,大大減少了程式碼的書寫,更加是AOP的具體體現,對OOP的一種彌補
最後
本章節只是介紹了少部分的AspectJ的使用,還是那句老話,AspectJ本身並沒有技術難點,難的是怎麼設計出好用的切面,無論是log還是監控日誌都可以使用該方式進行嘗試.