其實人最大悲哀莫過於知道自己想要什麼,卻不知道怎麼堅持!最近迷戀上了死侍 其實和我平時的狀態差不多,以一個混子的心態去做任何事情,往往成功的概率會更大!!!
一張圖片鎮樓!!!
上文說到了AspectJ的整合問題,如果沒有看過上一篇文章的小夥伴可以看看本系列的第一篇文章。
這篇文章充分的講解了關於AspectJ的整合問題,接下來我們講講怎麼更好的使用AspectJ來唯我所用。。。
1. 一些亂七八糟東西的解釋
其實我感覺這個東西說起來是最難的,因為要記住一大堆概念!其實記憶力是我的最煩的東西,但是我是一隻猿,一隻牛逼的猿!所以當背課文了...
先來看一段程式碼
@Aspect
public class TraceAspect {
private static final String TAG = "hjl";
@Before("execution(* android.app.Activity.on*(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodBefore: 切面的點執行了!" + key);
}
}
複製程式碼
先說一下這段程式碼實現了什麼?主要實現了在Activity中所有以on開頭的方法前面列印一段**Log.e(TAG, "onActivityMethodBefore: 切面的點執行了!" + key);**程式碼!接下來我們來逐一講解!
1.1 頂部@Aspect的含義
關於@Aspect這個註解,是下面所有內容的基礎,如果沒有這個註解AspectJ沒有相應的入口,就不會有相應的切面了!AspectJ會找到所有@Aspect註解,然後
1.2 萬用字元的概念(Pointcut語法)(難點)
首先說一下萬用字元的大體格式:(我也不知道理解的對不對,但是專案中使用的時候沒有發現什麼不對的地方)
@註解 | 訪問許可權 | 返回值型別 | 類名 | 函式名 | 引數 |
---|
大體上的萬用字元都是這個格式的,我們用上面的一個萬用字元去說明一下:
execution(* android.app.Activity.on*(..))
複製程式碼
execution 一般指定方法的執行,在往後因為沒有註解和訪問許可權的限制,所以這裡什麼也沒寫,返回值用*代替,說明可以用任何返回值,android.app.Activity.on*代表函式名稱的全路徑,後面的一個型號代表on後面可以接任何東西,後面的(..)代表其引數可以為任何值。
上面就是一段萬用字元的含義了!其實學習AspectJ的時候,我覺得最難懂的就是相應的操作符了,如果操作符弄明白了的話,真的就很簡單了!但是如果之前做過後臺的話,這個應該就很簡單了,就是Spring框架中的AOP是一樣的都是用的Pointcut語法。因為自己不是java後臺開發人員,所以解釋的可能不到位,你可以去找你們java後臺組的人去問問,學習一下!應該比我講的強很多,因為我真是第一次接觸這個東西!
因為平時沒接觸過,所以這裡就寫一些常用的吧!
分類
JPoint | 說明 | Pointcut語法說明 |
---|---|---|
method execution | 一般指定方法的執行 | execution(MethodSignnature) |
method call | 函式被呼叫 | call(MethodSignnature) |
constructor call | 建構函式被呼叫 | call(ConstructorSignature) |
constructor execution | 建構函式執行內部 | execution(ConstructorSignature) |
field get | 讀變數 | get(FieldSIgnature) |
field set | 寫變數 | set(FieldSIgnature) |
handler | 異常處理 | handler(TypeSignature) 注意:只能和@Before()配合使用,不支援@After、@Around等 |
advice execution | advice執行 | adciceexectuin() |
Signature參考
Sigbature | 語法(間隔一個空格) |
---|---|
MethodSignature | @註解 訪問許可權 返回值型別 類名.函式名(引數) |
ConstructorSignature | @註解 訪問許可權 類名.new(引數) |
FieldSignature | @註解 訪問許可權 變數型別 類名.類成員變數名 |
Signature語法明細
Sigbature語法明細 | 解釋 |
---|---|
@註解 | @完整類名,如果沒有則不寫 |
訪問許可權 | public/private/portect,以及static/final,如果沒有則不寫 注意:如果只寫public則匹配出來的是全部,如果寫public final則匹配的是所有public final開頭的內容 |
返回值型別 | 如果不限定型別,使用萬用字元*表示 |
類名.函式名 | 可以使用的萬用字元,包括*和..以及+號。其中*號用於陪陪除.號之外的任意字元,而..則表示任意字package,+號表示子類 注意:1.ConstructorSignature的函式名只能為new 2.(.函式名可以不寫),重用和註解一起使用 3.不能以..開頭 |
變數型別 | 成員變數型別,*代表任意型別 |
類名.成員變數名 | 類名可以使用萬用字元,與函式。函式名類似 |
Advice內容
Advice | 說明 |
---|---|
@Before(Pointcut) | 執行在jPoint之前 |
@After(Pointcut) | 執行在jPoint之後 |
@Around(Pointcut) | 替代原來的程式碼,如果要執行原來的程式碼,需要使用proceedingJoinPoint.proceed(); 注意:不可以和@After和@Before等一起使用 |
上面這寫表的你先簡單看一下,估計你一會還是會回來看的!!!
1.2.1 method->call的示例:
@Pointcut("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
public void callMethod() {
//為了演示call方法的使用
}
@Before("callMethod()")
public void beforeCallMethod(JoinPoint joinPoint) {
Log.e(TAG, "call方法的演示");
}
複製程式碼
說一下上面程式碼:@Pointcut是來註解方法的,call後面新增了一系列的萬用字元,簡單說就是一個方法的地址,*代表沒有返回值,@Before是說明在切片前執行!這裡千萬別把com.jinlong.aspectjdemo.MainActivity.callMethod這個地址寫錯了就行!
其實上面這段程式碼可以簡化為
@Before("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
public void beforeCallMethod(JoinPoint joinPoint){
Log.e(TAG, "call方法的演示");
}
複製程式碼
如果你把上面的@Before換為@After,那麼就會在方法之後列印!!!
再來看一段程式碼:
@Around("call(* com.jinlong.aspectjdemo.MainActivity.callMethod(..))")
public void aroundCallMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
joinPoint.proceed();
long endTime = System.currentTimeMillis();
Log.e(TAG, "方法執行的時間為" + (endTime - startTime));
}
複製程式碼
這段程式碼是統計方法執行時間的,這裡著重講兩個內容
- joinPoint.proceed();還原你原來的程式碼,如果沒有這句,你原來的程式碼就沒了!沒了!
- @Around 替代原來的程式碼,加上上面這句就可以還原了之前的程式碼了!
所以這裡就能計算出方法的執行時間了!!!也就是@Around的用法了!
1.2.2 method->execution 的示例:
之前的時候,我總覺得execution和call是一樣的,但是後來我知道了,他們最大的區別是這樣的!!!
比如你有一個方法:callMethod()對吧! 然後使用call是這個樣子滴~
call的相應方法();
callMethod();
複製程式碼
但是如果是execution的話就程式設計這個樣子滴了~
callMethod(){
execution的相應方法();
}
複製程式碼
其他的就沒有什麼區別了,也就不在這裡舉例說明了。
1.2.3 構造方法的操作
先說下這個東西是構造方法上用的,也就是說針對於相應的構造方法進行相應切面操作的!但是我一隻有一個疑問不明白,如果我按照上面方法的萬用字元進行操作的話,按照常理說應該也是能在相應切面進行操作的才對啊!編譯不報錯,但就是怎麼也列印不出來結果,還請明白的大神幫我解答一下!
按照上面的表格還有一種方案解決相應構造方法的問題
@Before("execution(com.jinlong.aspectjdemo.Person.new(..))")
public void beforeConstructorExecution(JoinPoint joinPoint) {
//這個是顯示Constructor的
Log.e(TAG, "before->" + joinPoint.getThis().toString() + "#" + joinPoint.getSignature().getName());
}
複製程式碼
這段程式碼我嘗試過了,可以列印出結果。也就是以**ConstructorSignature @註解 訪問許可權 類名.new(引數)**這種方式就可以列印出相應結果來!不過我勸你把那個@Before換成@After否則你列印出來的內容可能是一個空!
1.2.4 關於相應成員變數的問題
就是相當你可以修改類的成員變數,不管你怎麼設定最終返回的都是你設定的值!
看下面這段程式碼:
這裡是正常的一個類:
public class Person {
private String name;
private String age;
public Person() {
}
public Person(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
複製程式碼
這才是核心程式碼!
@Around("get(String com.jinlong.aspectjdemo.Person.age)")
public String aroundFieldGet(ProceedingJoinPoint joinPoint) throws Throwable {
// 執行原始碼
Object obj = joinPoint.proceed();
String age = obj.toString();
Log.e(TAG, "age: " + age);
return "100";
}
複製程式碼
這裡用到的的是上面FieldSignature @註解 訪問許可權 變數型別 類名.類成員變數名上面這段程式碼的含義是這樣滴,在每次用到age這個內容的時候,都會被修改,但是有一個問題,就是你不能重寫類的**toString()**方法,如果你重寫了這個方法的話,obj返回的就是一個空!我還真不知道為什麼,還請明白的告知一二!,這裡面get是一個關鍵字,就這麼理解吧!因為沒有設定訪問許可權和註解,所以這裡直接就省返回的變數型別(String型別)和類成員變數名的全路徑了!
在看下面這段程式碼:
@Around("set(String com.jinlong.aspectjdemo.Person.age)")
public void aroundFieleSet(ProceedingJoinPoint joinPoint) {
Log.e(TAG, "aroundFieleSet: " + joinPoint.getTarget().toString() +
joinPoint.getSignature().getName());
}
複製程式碼
這個可以對相應的age屬性進行設定的方法,也就是當發生賦值操作的時候都會被修改!這裡和大家說明一下,上面這段程式碼沒有**joinPoint.proceed();**程式碼,所以之前的程式碼中執行的內容就會失效了!也就是說被列印這段話替換了!其實上面這段程式碼你執行的時候你會發現一件事,Log被列印了兩次,為什麼呢?你想啊!this.age = age;在set方法中出現一次,而且還在構造方法中出現一次呢。仔細看看,所以這裡要排除構造方法總的那一次,怎麼處理呢?就要用到 withincode了!
1.2.5 withincode排除內容
怎麼理解這個東西呢?表示某個構造方法或函式中涉及到的JPoint。不理解吧!沒關係,看一段程式碼你就理解了!
@Around("set(String com.jinlong.aspectjdemo.Person.age)&&!withincode(com.jinlong.aspectjdemo.Person.new(..))")
public void aroundFieleSet(ProceedingJoinPoint joinPoint) throws Throwable {
//設定相應的成員變數
joinPoint.proceed();
}
複製程式碼
在1.2.4上面說到set會在兩個地方都有,但是其實我是不想要構造方法中的那個的,怎麼把他排除呢?那就是後面新增的這句程式碼**com.jinlong.aspectjdemo.Person.new(..))**就是把構造方法中的內容排除!其實很好理解,就是排除相應的構造方法,可以簡單理解withincode就是帶著某個內容,但是由於取反了,所以就是不帶著這個東西了!!!就醬紫了。。。
1.2.5 handler的異常的捕捉
這個相比較之下就簡單一點了,直接上程式碼:
這是在程式碼中的一個異常,很簡單的一個異常,如果這個方法走了相應的catch,那麼就能捕獲相應的異常了!
private void catchMethod() {
try {
int sum = 100 / 0;
} catch (Exception e) {
e.printStackTrace();
}
}
複製程式碼
關鍵程式碼來了。。。
@Before("handler(java.lang.Exception)")
public void handlerMethod() {
Log.e(TAG, "handlerMethod: 異常產生了");
}
複製程式碼
是不是很簡單,使用一個handler關鍵字,加上一個異常的全路徑,ok搞定,但是這裡一定要注意,前面的註解只能是@Before,切記!!!
1.2.6 關於註解的使用
有許多第三方你是不知道具體方法名稱的,但是你還想使用的話怎麼辦?那就是註解了,因為註解可以很好的解決這種需求。
再來看一段程式碼:
定義一段註解內容:
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DebugTrace {
}
複製程式碼
因為註解的這些內容不是本篇文章的重點,所以這裡不準備講解了。感興趣的你可以百度一下,這個註解主要是在編譯完成後也會起作用,並且是方法和成員變數都可以使用!
在加上下面這段程式碼就可以進行相應切面的操作了!
@Pointcut("execution(@com.jinlong.aspectjdemo.DebugTrace * *(..))")
public void DebugTraceMethod() {
}
@Before("DebugTraceMethod()")
public void beforeDebugTraceMethod(JoinPoint joinPoint) {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "註解這個方法執行了: ");
}
複製程式碼
看到在這段程式碼大家應該不怎麼陌生了,就是在方法內容新增相應的切面操作。最後在使用的方法的上面新增相應的註解就可以了!就這麼簡單!
@DebugTrace
private void mothod1() {
Log.e(TAG, "方法1執行了");
}
複製程式碼
上面基本上包含了我們APP使用中,能用到一些內容,如果有什麼講的不到位的地方還請指出。因為是第一次接觸這個東西,可能有些細節講解的不是很到位,還請諒解!!!
想看原始碼嗎?想看連結嗎?點這裡