AOP之AspectJ
前言:這幾天一直在學(xué)習(xí)aop切面編程,以前一直也有聽過aop但是實(shí)際用的還是比較少,不是很清楚,這周版本剛上線,有點(diǎn)空閑時(shí)間,所以就把之前想學(xué)習(xí)的aop技術(shù)重點(diǎn)花時(shí)間學(xué)習(xí)了下,所以才有這篇博客總結(jié)下學(xué)習(xí)的情況,其實(shí)現(xiàn)在的博客大都是這個(gè)抄寫那個(gè),那個(gè)抄寫這個(gè)的,其實(shí)我認(rèn)為不管怎么抄寫,只要自己掌握了,就沒關(guān)系。
AOP技術(shù)有很多,目前比較出名流行的技術(shù)框架大概是這幾類:1.APT 2.ASM 3.AspectJ,經(jīng)過這幾天的學(xué)習(xí),我還是認(rèn)為AspectJ是最簡單最快的實(shí)現(xiàn)方式,所以接下來,我會對AspectJ重點(diǎn)總結(jié)下這幾天的學(xué)習(xí)。
開始接入AspectJ步驟:
1.首先,需要在項(xiàng)目根目錄的build.gradle中增加依賴:
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'
2.然后再主項(xiàng)目或者庫的build.gradle中增加AspectJ的依賴和插件依賴:
apply plugin: 'android-aspectjx'
compile 'org.aspectj:aspectjrt:1.8.10'
3.excludeJarFilter解釋:用該屬性,可以過濾減少AspectJ掃描庫文件,用法:
aspectjx {
//includes the libs that you want to weave
includeJarFilter 'universal-image-loader', 'AspectJX-Demo/library'
//excludes the libs that you don't want to weave
excludeJarFilter 'universal-image-loader'
}
AspectJ語法
Join Point
Join Point是AspectJ前言核心的地方,它就像一把刀,把程序的整個(gè)執(zhí)行過程切成了一段段不同的部分。例如,構(gòu)造方法調(diào)用、調(diào)用方法、方法執(zhí)行、異常等等,這些都是Join Points,實(shí)際上,也就是你想把新的代碼插在程序的哪個(gè)地方,是插在構(gòu)造方法中,還是插在某個(gè)方法調(diào)用前,或者是插在某個(gè)方法中,這個(gè)地方就是Join Points,當(dāng)然,不是所有地方都能給你插的,只有能插的地方,才叫Join Points。插入點(diǎn)的列舉:
JoinPoint | 說明 |
---|---|
Method call | 方法被調(diào)用 |
Content Cell | Content Cell |
Method execution | 方法執(zhí)行 |
Constructor call | 構(gòu)造函數(shù)被調(diào)用 |
Constructor execution | 構(gòu)造函數(shù)執(zhí)行 |
Field get | 讀取屬性 |
Field set | 寫入屬性 |
Pre-initialization | 與構(gòu)造函數(shù)有關(guān),很少用到 |
Initialization | 與構(gòu)造函數(shù)有關(guān),很少用到 |
Static initialization | static 塊初始化 |
Handler | 異常處理 |
Advice execution | 所有 Advice 執(zhí)行 |
以上都是AspectJ插入代碼的點(diǎn)。
Pointcuts
Pointcuts 是具體的切入點(diǎn),可以確定具體織入代碼的地方,基本的 Pointcuts 是和 Join Point 相對應(yīng)的,在我理解,實(shí)際上就是在Join Points中通過一定條件選擇出我們所需要的Join Points,所以說,Pointcuts,也就是帶條件的Join Points,作為我們需要的代碼切入點(diǎn),兩者相對應(yīng)的關(guān)系點(diǎn):
Pointcuts | 說明 |
---|---|
Join Point | Pointcuts syntax |
Method call | call(MethodPattern) |
Method execution | execution(MethodPattern) |
Constructor call | call(ConstructorPattern) |
Constructor execution | execution(ConstructorPattern) |
Field get | get(FieldPattern) |
Field set | set(FieldPattern) |
Pre-initialization | initialization(ConstructorPattern) |
Initialization | preinitialization(ConstructorPattern) |
Static initialization | staticinitialization(TypePattern) |
Handler | handler(TypePattern) |
Advice execution | advice excution() |
Advice
Advice是在插入點(diǎn)的地方植入代碼,在 AspectJ 中有五種類型:Before、After、AfterReturning、AfterThrowing、Around。
Advice | 說明 |
---|---|
@Before | 在執(zhí)行 Join Point 之前 |
@After | 在執(zhí)行 Join Point 之后,包括正常的 return 和 throw 異常 |
@AfterReturning | Join Point 為方法調(diào)用且正常 return 時(shí),不指定返回類型時(shí)匹配所有類型 |
@AfterThrowing | Join Point 為方法調(diào)用且拋出異常時(shí),不指定異常類型時(shí)匹配所有類型 |
@Around | 替代 Join Point 的代碼,如果要執(zhí)行原來代碼的話,要使用ProceedingJoinPoint.proceed() |
注意: After 和 Before 沒有返回值,但是 Around 的目標(biāo)是替代原 Join Point 的,所以它一般會有返回值,而且返回值的類型需要匹配被選中的 Join Point 的代碼。而且不能和其他 Advice 一起使用,如果在對一個(gè) Pointcut 聲明 Around 之后還聲明 Before 或者 After 則會失效。
Advice 注解修改的方法必須為 public,Before、After、AfterReturning、AfterThrowing 四種類型修飾的方法返回值也必須為 void,Advice 需要使用 JoinPoint、JoinPointStaticPart、JoinPoint.EnclosingStaticPart 時(shí),要在方法中聲明為額外的參數(shù),@Around 方法可以使用 ProceedingJoinPoint,用以調(diào)用 proceed() 方法.
注意(Advice 不能使用 After 和 Around)
例子用法說明
/**
* Created by wuminjian on 17/11/2.
*/
@Aspect
public class FragmentAspect {
private static final String TAG = "FragmentAspect";
@Before("execution(* com.test.aspectj.MainActivity.on**(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.d(TAG, "onActivityMethodBefore: " + key);
}
}
- @Aspect:這個(gè)注解的意思是一個(gè)AspectJ文件,編譯器在編譯的時(shí)候,就會自動去解析,然后將代碼注入到相應(yīng)的JPonit中。
- @Before: 是Advice,也就是具體的插入點(diǎn),是指調(diào)用這個(gè)某個(gè)Jpoint之前插入代碼。
- execution: 處理JPoint的類型,例如call,execution
- com.test.aspectj.MainActivity.on*(..)):這個(gè)是重要的表達(dá)式,第一個(gè)[ * ]表示返回值, 表示返回任意類型,后面的com.test.aspectj.MainActivity.on **表示典型的包名路徑,其中可用 * 來進(jìn)行通配.
- onActivityMethodBefore:是調(diào)用MainActivity.on**方法之前插入的實(shí)際代碼。
通過這種方式編譯后,我們來看下生成的代碼是怎樣的。AspectJ的原理實(shí)際上是在編譯的時(shí)候,根據(jù)一定的規(guī)則解析,然后插入一些代碼,通過aspectjx生成的代碼,會在Build目錄:
通過JD-GUI查看jar文件如下:
我們可以發(fā)現(xiàn),在onCreate的最前面,插入了一行AspectJ的代碼。這個(gè)就是AspectJ的主要功能。
自定義Pointcuts
自定義Pointcuts可以讓我們更加精確的切入一個(gè)或多個(gè)指定的切入點(diǎn).
- 首先,我們需要自定義一個(gè)注解類,例如——WmjLog
/**
* Created by wuminjian on 17/11/3.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface WmjLog {
}
- 接著,創(chuàng)建一個(gè)切入文件,內(nèi)部通過一個(gè)Pointcut來指定在帶有我們上面自定義注解類WmjLog注解的所有方法上進(jìn)行攔截。
/**
* Created by wuminjian on 17/11/3.
*/
@Aspect
public class WmjAppLogAspect {
//在帶有AopLog注解的方法進(jìn)行切入(注:此處的 * *前面都要有一個(gè)空格)
@Pointcut("execution(@com.test.aspectj.model.WmjLog * *(..))")
public void wmjLogPointcut() {
}
//注意,這個(gè)函數(shù)必須要有實(shí)現(xiàn),否則Java編譯器會報(bào)錯(cuò)
@After("wmjLogPointcut()")
public void onLogPointcutAfter(JoinPoint joinPoint) throws Throwable {
Log.i("WmjAOP", "onLogPointcutAfter:" + joinPoint.getSignature());
}
}
- 最后,在app項(xiàng)目中寫一個(gè)類來測試,代碼如下:
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import com.test.aspectj.model.WmjLog;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@WmjLog
public void WmjTestAop(){
Log.i("AOP","in wmjTestAop");
}
}
通過這種方式,我們可以非常方便的監(jiān)控指定的Pointcut,從而增加監(jiān)控的粒度,其實(shí)AspectJ很簡單關(guān)鍵就是Points的匹配寫法。
call和execution
在AspectJ的切入點(diǎn)表達(dá)式中,我們前面都是使用的execution,實(shí)際上,還有一種類型——call,execution是在被切入的方法中,call是在調(diào)用被切入的方法前或者后。具體事咧就不說了,大家自己去多試下就知道了。
@Before、@After、@ Around
Before、After
這兩個(gè)Advice應(yīng)該是使用的最多的,所以,我們先來看下這兩個(gè)Advice的實(shí)例,首先看下Before和After:
@Before("execution(* com.test.aspectj.MainActivity.on*(android.os.Bundle))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.d(TAG, "onActivityMethodBefore: " + key);
}
@After("execution(* com.test.aspectj.MainActivity.on*(android.os.Bundle))")
public void onActivityMethodAfter(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.d(TAG, "onActivityMethodAfter: " + key);
}
經(jīng)過上面的語法解釋,現(xiàn)在看這個(gè)應(yīng)該很好理解了,我們來看下編譯后的類:
其實(shí)就是在方法的前后插入代碼。
Around
Before和After其實(shí)還是很好理解的,也就是在Pointcuts之前和之后,插入代碼,那么Around呢,從字面含義上來講,也就是在方法前后各插入代碼,是的,他包含了Before和After的全部功能,代碼如下:
@Around("execution(* com.test.aspectj.MainActivity.testAOP())")
public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String key = proceedingJoinPoint.getSignature().toString();
Log.d(TAG, "onActivityMethodAroundFirst: " + key);
proceedingJoinPoint.proceed();
Log.d(TAG, "onActivityMethodAroundSecond: " + key);
}
其中,proceedingJoinPoint.proceed()代表執(zhí)行原始的方法,在這之前、之后,都可以進(jìn)行各種邏輯處理。
在主工程測試代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testAOP();
}
public void testAOP() {
Log.d("wmj", "testAOP");
}
}
編譯之后查看class文件如下:
我們可以發(fā)現(xiàn),Around確實(shí)實(shí)現(xiàn)了Before和After的功能,但是要注意的是,Around和After是不能同時(shí)作用在同一個(gè)方法上的,會產(chǎn)生重復(fù)切入的問題。