一、AspectJ
Aspectj是一個AOP架構,也是通過對位元組碼操作,來實作AOP的,但是與ASM需要開發者直接編寫操作位元組碼的代碼之外,使用Aspectj不需要開發者直接編寫操作位元組碼代碼, 而是隻需要按照規範編寫我們需要處理的Java代碼即可;由Aspectj幫助我們将編寫的Java代碼的位元組碼織入到.class位元組碼檔案中實作位元組碼插樁操作;
二、AspectJ中的基本概念
1.切面 Aspect
我們在學習AOP的時候,知道AOP實際就是針對的是業務邏輯中的某個處理步驟,比如日志處理,登入判斷,權限擷取,資料埋點,異常處理,這些很多業務邏輯中都需要的,可以從業務邏輯中單獨抽出來的,這個在需要業務邏輯處都需要,可以從業務邏輯抽離出來的部分,我們稱之為一個切面。
我們到時候需要定義一個切面類,使用@Aspect注解,表示是切面處理類;
@Aspect
public class TagAspect {
....
}
2.連接配接點 JoinPoint
就是我們切面可以插入的地方,也就是我們位元組碼可以織入的地方,比如方法的調用處,方法的執行處,都是連接配接點,也即我們可以織入位元組碼的地方;
JoinPoint | 說明 |
---|---|
method call | 方法調用處 |
method execution | 方法執行處 |
constructor call | 構造方法調用處 |
constructor execution | 構造方法執行處 |
field get | field get |
field set | 設定成員處 |
static initialization | static塊初始化 |
handler | 異常處理處 |
這些都是JointPoint
3.切點 PointCut
切點(PointCut)很簡單,我們上面說了連接配接點,切點就是具體是哪一個連接配接點,比具體如是在哪一個或哪一種方法中進行代碼的織入;
那如何篩選出具體是哪一個連接配接點,我們在切面類中定義方法,使用@Pointcut注解,表示定義的是切點,注解值是一個表達式: 連接配接點類型(簽名),表示具體是哪一個或者哪一類連接配接點;
比如連接配接點是一個方法執行處,表達式規範如下:
execution表示的是方法的執行類連接配接點;括号裡面就是方法的簽名;
@注解 :表示的是方法使用的注解類型(要寫全類名:@com.example.aspectjmodule.Tag)
?表示可選的,不是必須要寫的
<修飾符模式>: 就是方法的修飾符 public,private,protected等,也不是必填的
*表示任意類型
<傳回類型模式> :方法傳回值類型,void,int,等 , *表示任意類型 必填
方法名: 表示方法名字
test 就是表示方法名字是testDev所有方法
com.example…* 表示是com.example包下的任意方法
*表示任意方法
等等寫法
<參數模式> :方法的形參類型
(int) 一個int類型形參
(int,int) 兩個int類型形參
(. .) 任意個數,任意類型形參
@Aspect
public class TagAspect {
@Pointcut("execution(@execution(@com.example.aspectjmodule.Tag * *(..)))")
public void tagPointCut(){
}
}
JoinPoint | 說明 | PointCut |
---|---|---|
method call | 方法調用處 | call(MethodSignature) |
method execution | 方法執行處 | execution(MethodSignature) |
constructor call | 構造方法調用處 | call(ConstructorSignature) |
constructor execution | 構造方法執行處 | execution(ConstructorSignature) |
field get | field get | get(FieldSignature) |
field set | 設定成員處 | set(FieldSignature) |
static initialization | static塊初始化 | staticinitialization(TypeSignature) |
handler | 異常處理處 | handler(TypeSignature) |
三、使用Aspectj實作位元組碼插樁
1.Aspectj的內建步驟
(1)在Android項目下建立一個Module (Android Library), 一般會将Aspectj相關的代碼放到一個單獨的Module;是以先建立一個Module,名稱為AspectjModule
(2) 在Aspectj Module的build.gradle下的buildsc添加Aspectj插件的依賴
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.9.7'
}
}
注意這個buildscript塊必須要放到build.gradle的plugins{}塊的上面,
否則會報如下錯誤:
all buildscript {} blocks must appear before any plugins {} blocks in the script
(3)在Module的build.gradle的dependencies下添加Aaspectj的依賴
dependencies {
api 'org.aspectj:aspectjrt:1.9.7'
}
(4)在Aspectj Module的build.gradle下添加如下建構腳本
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android.libraryVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)
]
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler)
def log = project.logger
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:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
(5) 将Aspectj Module 添加為app 主Module的的依賴Moduleapp Module的
app 的build.gradle中dependencies添加如下
dependencies {
implementation project(path: ':AspectjModule')
}
(6)在app主Module中也要添加Aspectj的插件依賴,以及上面的建構腳本,但是app主Module的建構腳本稍有不同:
app主module 也即Application Module
用的是project.android.applicationVariants.all
AspectjModule也即Android Libdary Module使用的是android.libraryVariants.all
app主Module建構腳本如下
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
project.android.applicationVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)
]
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler)
def log = project.logger
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:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
這裡要注意的是,不僅僅是app主Module,其他隻要要使用Aspectj的Module,也是要添加Aspectj的插件依賴,以及建構腳本的;
上面就是內建Aspectj的步驟
2.用Aspectj實作位元組碼插樁
內建了Aspectj之後,下面我們來使用Aspectj實作一個功能;
比如我們希望在某些方法開始執行,和結束執行添加列印日志;
Aspectj的相關代碼,我們就在AspectjModule中去編寫;
(1)首先我們定義一個注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tag {
String value();
}
(2)定義一個Aspectj處理類,Aspectj相關處理代碼會在這個類中
@Aspect
public class TagAspect {
}
Aspectj處理類要是用@Aspect注解
(3)定義PointCut(切點)
定義切點:
定義一個方法,名字任意,使用@PointCut注解,@PointCut注解值是篩選具體的連接配接點的表達式
@Pointcut("execution(@com.example.aspectjmodule.Tag * *(..))")
public void tagPointCut() {
}
這個切點的定義表示是所有使用了com.example.aspectjmodule.Tag這個注解的方法
(4)定義代碼織入的地方,并編寫織入代碼
也是要定義一個方法,方法名任意,然後我們這裡給方法使用了@Around注解
注解的值就是切點的方法名加形參 tagPointCut()
@Before 前置通知 切點方法執行之前通知
@After 後置通知 切點方法執行之後通知
@Around 環繞通知 可以實作前置後置通知
@AfterReturning 傳回通知 在切點方法傳回之後執行
@AfterThrowing 異常通知 切點抛出異常時執行
@Around("tagPointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
//拿到方法的簽名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//類名
String className = methodSignature.getDeclaringType().getSimpleName();
//方法名
String methodName = methodSignature.getName();
Tag tagAnnotation = methodSignature.getMethod().getAnnotation(Tag.class);
String value = tagAnnotation.value();
Log.e(value, "begin execute"); //方法執行前添加列印代碼
Object proceed = null;
try {
proceed = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.e(value, "end execute"); //方法執行後添加列印代碼
return proceed;
}
我們在方法執行的前後都添加了列印日志
(5)我們需要添加列印的方法使用Tag注解
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
}
@Tag("TAG")
public void test() {
Log.e(TAG, "test: ");
}
}
然後運作程式,運作程式之後,logcat中日志
begin execute
test:
end execute
在test方法執行前後成功添加了列印日志
以上就是使用AspectJ實作位元組碼插樁的基本流程;
注意:我們在使用Java編寫的Android項目中可以這樣使用AspectJ,但是如果是Kotlin編寫的Android項目,這樣使用AspectJ是無效的,但是我們使用gradle_plugin_android_aspectjx這個第三方庫,一個基于AspectJ并在此基礎上擴充出來可應用于Android開發平台的AOP架構,可作用于java源碼,class檔案及jar包,同時支援kotlin的應用;
歡迎掃碼訂閱公衆号,公衆号主要分享原創或轉載移動開發技術文章,和廣大移動開發者一起學習成長!