SpringBoot基礎篇AOP之高階使用技能
更多相關內容,檢視: http://spring.hhui.top/
前面一篇博文 190301-SpringBoot基礎篇AOP之基本使用姿勢小結 介紹了aop的簡單使用方式,在文章最後,丟擲了幾個問題待解決,本篇博文則將針對前面的問題,看下更多關於AOP的使用說明
<!-- more -->
I. 高階技能
1. 註解攔截方式
前面一文,主要介紹的是根據正則表示式來攔截對應的方法,接下來演示下如何通過註解的方式來攔截目標方法,實現也比較簡單
首先建立註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnoDot {
}
接著在目標方法上添加註解,這裡藉助前面博文中工程進行說明,新建一個com.git.hui.boot.aop.demo2.AnoDemoBean
,注意這個包路徑,是不會被前文的AnoAspect
定義的Advice攔截的,這裡新建一個包路徑的目的就是為了儘可能的減少干擾項
@Component public class AnoDemoBean { @AnoDot public String genUUID(long time) { try { System.out.println("in genUUID before process!"); return UUID.randomUUID() + "|" + time; } finally { System.out.println("in genUUID finally!"); } } }
接下來定義對應的advice, 直接在前面的AnoAspect
中新增(不知道前文的也沒關係,下面貼出相關的程式碼類,前文的類容與本節內容無關)
@Aspect
@Component
public class AnoAspect {
@Before("@annotation(AnoDot)")
public void anoBefore() {
System.out.println("AnoAspect ");
}
}
測試程式碼
@SpringBootApplication public class Application { private AnoDemoBean anoDemoBean; public Application(AnoDemoBean anoDemoBean) { this.anoDemoBean = anoDemoBean; this.anoDemoBean(); } private void anoDemoBean() { System.out.println(">>>>>>>" + anoDemoBean.genUUID(System.currentTimeMillis())); } public static void main(String[] args) { SpringApplication.run(Application.class); } }
輸出結果如下,在執行目標方法之前,會先執行before advice中的邏輯
AnoAspect
in genUUID before process!
in genUUID finally!
>>>>>>>3a5d749d-d94c-4fc0-a7a3-12fd97f3e1fa|1551513443644
2. 多個advice攔截
一個方法執行時,如果有多個advice滿足攔截規則,是所有的都會觸發麼?通過前面一篇博文知道,不同型別的advice是都可以攔截的,如果出現多個相同型別的advice呢?
在前面一篇博文的基礎上進行操作,我們擴充套件下com.git.hui.boot.aop.demo.DemoBean
@Component
public class DemoBean {
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in genUUID finally!");
}
}
}
對應的測試切面內容如
@Aspect
@Component
public class AnoAspect {
@Before("execution(public * com.git.hui.boot.aop.demo.*.*(*))")
public void doBefore(JoinPoint joinPoint) {
System.out.println("do in Aspect before method called! args: " + JSON.toJSONString(joinPoint.getArgs()));
}
@Pointcut("execution(public * com.git.hui.boot.aop.demo.*.*(*))")
public void point() {
}
@After("point()")
public void doAfter(JoinPoint joinPoint) {
System.out.println("do in Aspect after method called! args: " + JSON.toJSONString(joinPoint.getArgs()));
}
/**
* 執行完畢之後,通過 args指定引數;通過 returning 指定返回的結果,要求返回值型別匹配
*
* @param time
* @param result
*/
@AfterReturning(value = "point() && args(time)", returning = "result")
public void doAfterReturning(long time, String result) {
System.out.println("do in Aspect after method return! args: " + time + " ans: " + result);
}
@Around("point()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("do in Aspect around ------ before");
Object ans = joinPoint.proceed();
System.out.println("do in Aspect around ------- over! ans: " + ans);
return ans;
}
@Before("point()")
public void sameBefore() {
System.out.println("SameAspect");
}
@Before("@annotation(AnoDot)")
public void anoBefore() {
System.out.println("AnoAspect");
}
}
測試程式碼如下
@SpringBootApplication
public class Application {
private DemoBean demoBean;
public Application(DemoBean demoBean) {
this.demoBean = demoBean;
this.demoBean();
}
private void demoBean() {
System.out.println(">>>>> " + demoBean.genUUID(System.currentTimeMillis()));
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
輸出結果如下,所有的切面都執行了,也就是說,只要滿足條件的advice,都會被攔截到
do in Aspect around ------ before
AnoAspect
do in Aspect before method called! args: [1551520547268]
SameAspect
in genUUID before process!
in genUUID finally!
do in Aspect around ------- over! ans: 5f6a5616-f558-4ac9-ba4b-b4360d7dc238|1551520547268
do in Aspect after method called! args: [1551520547268]
do in Aspect after method return! args: 1551520547268 ans: 5f6a5616-f558-4ac9-ba4b-b4360d7dc238|1551520547268
>>>>> 5f6a5616-f558-4ac9-ba4b-b4360d7dc238|1551520547268
3. 巢狀攔截
巢狀的方式有幾種case,先看第一種
a. 呼叫方法不滿足攔截規則,呼叫本類中其他滿足攔截條件的方法
這裡我們藉助第一節中的bean來繼續模擬, 在AnoDemoBean
類中,新增一個方法
@Component
public class AnoDemoBean {
public String randUUID(long time) {
try {
System.out.println("in randUUID start!");
return genUUID(time);
} finally {
System.out.println("in randUUID finally!");
}
}
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in genUUID finally!");
}
}
}
對應的切面為
@Aspect
@Component
public class NetAspect {
@Around("@annotation(AnoDot)")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("In NetAspect doAround before!");
Object ans = joinPoint.proceed();
System.out.println("In NetAspect doAround over! ans: " + ans);
return ans;
}
}
然後測試case需要改為直接呼叫 AnoDemoBean#randUUID
,需要看這個方法內部呼叫的genUUID
是否會被切面攔截住
@SpringBootApplication
public class Application {
private AnoDemoBean anoDemoBean;
public Application(AnoDemoBean anoDemoBean) {
this.anoDemoBean = anoDemoBean;
this.anoDemoBean();
}
private void anoDemoBean() {
System.out.println(">>>>>>>" + anoDemoBean.randUUID(System.currentTimeMillis()));
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
輸出結果如下,沒有切面的日誌,表明這種場景下,不會被攔截
in randUUID start!
in genUUID before process!
in genUUID finally!
in randUUID finally!
>>>>>>>0c6a5ccf-30c0-4ac0-97f2-3dc063580f3d|1551522176035
b. 呼叫方法不滿足攔截規則,呼叫其他類中滿足攔截條件的方法
依然使用前面的例子進行說明,不過是稍稍改一下AnoDemoBean
,呼叫第二節中的DemoBean的方法
DemoBean的程式碼如下
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in DemoBean genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in DemoBean genUUID finally!");
}
}
然後AnoDemoBean的程式碼如下
@Component
public class AnoDemoBean {
@Autowired
private DemoBean demoBean;
public String randUUID(long time) {
try {
System.out.println("in AnoDemoBean randUUID start!");
return genUUID(time) + "<<<>>>" + demoBean.genUUID(time);
} finally {
System.out.println("in AnoDemoBean randUUID finally!");
}
}
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in AnoDemoBean genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in AnoDemoBean genUUID finally!");
}
}
}
測試程式碼和前面完全一致,接下來看下輸出
in AnoDemoBean randUUID start!
in AnoDemoBean genUUID before process!
in AnoDemoBean genUUID finally!
### 上面三行為 anoDemoBean#randUUID方法呼叫 anoDemoBean#genUUID方法的輸出結果,可以看到沒有切面執行的日誌輸出
### 下面的為呼叫 demoBean#genUUID 方法,可以看到切面(NetAspect#doAround)執行的日誌
In NetAspect doAround before!
in DemoBean genUUID before process!
in DemoBean genUUID finally!
In NetAspect doAround over! ans: f35b8878-fbd0-4840-8fbe-5fef8eda5e31|1551522532092
### 最後是收尾
in AnoDemoBean randUUID finally!
>>>>>>>e516a35f-b85a-4cbd-aae0-fa97cdecab47|1551522532092<<<>>>f35b8878-fbd0-4840-8fbe-5fef8eda5e31|1551522532092
從上面的日誌分析中,可以明確看出對比,呼叫本類中,滿足被攔截的方法,也不會走切面邏輯;呼叫其他類中的滿足切面攔截的方法,會走切面邏輯
c. 呼叫方法滿足切面攔截條件,又呼叫其他滿足切面攔截條件的方法
這個和兩個case有點像,不同的是直接呼叫的方法也滿足被切面攔截的條件,我們主要關注點在於巢狀呼叫的方法,會不會進入切面邏輯,這裡需要修改的地方就很少了,直接把 AnoDemoBean#randUUID
方法上添加註解,然後執行即可
@Component
public class AnoDemoBean {
@Autowired
private DemoBean demoBean;
@AnoDot
public String randUUID(long time) {
try {
System.out.println("in AnoDemoBean randUUID start!");
return genUUID(time) + "<<<>>>" + demoBean.genUUID(time);
} finally {
System.out.println("in AnoDemoBean randUUID finally!");
}
}
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in AnoDemoBean genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in AnoDemoBean genUUID finally!");
}
}
}
輸出結果如下
## 最外層的切面攔截的是 AnoDemoBean#randUUID 方法的執行
In NetAspect doAround before!
in AnoDemoBean randUUID start!
in AnoDemoBean genUUID before process!
in AnoDemoBean genUUID finally!
### 從跟上面三行的輸出,可以知道內部呼叫的 AnoDemoBean#genUUID 即便滿足切面攔截規則,也不會再次走切面邏輯
### 下面4行,表明其他類的方法,如果滿足切面攔截規則,會進入到切面邏輯
In NetAspect doAround before!
in DemoBean genUUID before process!
in DemoBean genUUID finally!
In NetAspect doAround over! ans: d9df7388-2ef8-4b1a-acb5-6639c47f36ca|1551522969801
in AnoDemoBean randUUID finally!
In NetAspect doAround over! ans: cf350bc2-9a9a-4ef6-b496-c913d297c960|1551522969801<<<>>>d9df7388-2ef8-4b1a-acb5-6639c47f36ca|1551522969801
>>>>>>>cf350bc2-9a9a-4ef6-b496-c913d297c960|1551522969801<<<>>>d9df7388-2ef8-4b1a-acb5-6639c47f36ca|1551522969801
從輸出結果進行反推,一個結論是
- 執行的目標方法,如果呼叫了本類中一個滿足切面規則的方法A時,在執行方法A的過程中,不會觸發切面邏輯
- 執行的目標方法,如果呼叫其他類中一個滿足切面規則的方法B時,在執行方法B的過程中,將會觸發切面邏輯
4. AOP攔截方法作用域
前面測試的被攔截方法都是public,那麼是否表明只有public方法才能被攔截呢?
從第三節基本可以看出,private方法首先淘汰出列,為啥?因為private方法正常來講只能內部呼叫,而內部呼叫不會走切面邏輯;所以接下來需要關注的主要放在預設作用域和protected作用域
@Component
public class ScopeDemoBean {
@AnoDot
String defaultRandUUID(long time) {
try {
System.out.println(" in ScopeDemoBean defaultRandUUID before!");
return UUID.randomUUID() + " | default | " + time;
} finally {
System.out.println(" in ScopeDemoBean defaultRandUUID finally!");
}
}
@AnoDot
protected String protectedRandUUID(long time) {
try {
System.out.println(" in ScopeDemoBean protectedRandUUID before!");
return UUID.randomUUID() + " | protected | " + time;
} finally {
System.out.println(" in ScopeDemoBean protectedRandUUID finally!");
}
}
@AnoDot
private String privateRandUUID(long time) {
try {
System.out.println(" in ScopeDemoBean privateRandUUID before!");
return UUID.randomUUID() + " | private | " + time;
} finally {
System.out.println(" in ScopeDemoBean privateRandUUID finally!");
}
}
}
我們不直接使用這個類裡面的方法,藉助前面的 AnoDemoBean
, 下面給出了通過反射的方式來呼叫private方法的case
@Component
public class AnoDemoBean {
@Autowired
private ScopeDemoBean scopeDemoBean;
public void scopeUUID(long time) {
try {
System.out.println("-------- default --------");
String defaultAns = scopeDemoBean.defaultRandUUID(time);
System.out.println("-------- default: " + defaultAns + " --------\n");
System.out.println("-------- protected --------");
String protectedAns = scopeDemoBean.protectedRandUUID(time);
System.out.println("-------- protected: " + protectedAns + " --------\n");
System.out.println("-------- private --------");
Method method = ScopeDemoBean.class.getDeclaredMethod("privateRandUUID", long.class);
method.setAccessible(true);
String privateAns = (String) method.invoke(scopeDemoBean, time);
System.out.println("-------- private: " + privateAns + " --------\n");
} catch (Exception e) {
e.printStackTrace();
}
}
}
測試case
@SpringBootApplication
public class Application {
private AnoDemoBean anoDemoBean;
public Application(AnoDemoBean anoDemoBean) {
this.anoDemoBean = anoDemoBean;
this.anoDemoBean();
}
private void anoDemoBean() {
anoDemoBean.scopeUUID(System.currentTimeMillis());
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
輸出結果如下,從日誌列印來看,protected和default方法的切面都走到了
-------- default --------
In NetAspect doAround before!
in ScopeDemoBean defaultRandUUID before!
in ScopeDemoBean defaultRandUUID finally!
In NetAspect doAround over! ans: 2ad7e509-c62c-4f25-b68f-eb5e0b53196d | default | 1551524311537
-------- default: 2ad7e509-c62c-4f25-b68f-eb5e0b53196d | default | 1551524311537 --------
-------- protected --------
In NetAspect doAround before!
in ScopeDemoBean protectedRandUUID before!
in ScopeDemoBean protectedRandUUID finally!
In NetAspect doAround over! ans: 9eb339f8-9e71-4321-ab83-a8953d1b8ff8 | protected | 1551524311537
-------- protected: 9eb339f8-9e71-4321-ab83-a8953d1b8ff8 | protected | 1551524311537 --------
-------- private --------
in ScopeDemoBean privateRandUUID before!
in ScopeDemoBean privateRandUUID finally!
-------- private: 1826afac-6eca-4dc3-8edc-b4ca7146ce28 | private | 1551524311537 --------
5. 小結
本篇博文篇幅比較長,主要是測試程式碼比較佔用地方,因此有必要簡單的小結一下,做一個清晰的歸納,方便不想看細節,只想獲取最終結論的小夥伴
註解攔截方式:
- 首先宣告註解
- 在目標方法上添加註解
- 切面中,advice的內容形如
@Around("@annotation(AnoDot)")
多advice情況:
- 多個advice滿足攔截場景時,全部都會執行
巢狀場景
- 執行的目標方法,如果呼叫了本類中一個滿足切面規則的方法A時,在執行方法A的過程中,不會觸發切面邏輯
- 執行的目標方法,如果呼叫其他類中一個滿足切面規則的方法B時,在執行方法B的過程中,將會觸發切面邏輯
作用域
- public, protected, default 作用域的方法都可以被攔截
優先順序
這個內容因為特別多,所以有必要單獨拎出來,其主要的分類如下
- 同一aspect,不同advice的執行順序
- 不同aspect,advice的執行順序
- 同一aspect,相同advice的執行順序
II. 其他
0. 專案
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 專案: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/010-aop
1. 一灰灰Blog
- 一灰灰Blog個人部落格 https://blog.hhui.top
- 一灰灰Blog-Spring專題部落格 http://spring.hhui.top
一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 宣告
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
3. 掃描關注
一灰灰blog
知識星球
相關推薦
SpringBoot基礎篇AOP之高階使用技能
開發十年,就只剩下這套架構體系了! >>>
SpringBoot基礎篇AOP之基本使用姿勢小結
浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>
SpringBoot基礎篇Bean之基本定義與使用
我們知道在Spring中,有兩個非常有名的特性,依賴注入(DI)與切面(AOP),其中依賴注入其主要的作用,可以說就是維護Spring容器建立的Bean之間的依賴關係,簡單來說就是一個bean(假定名為A)持有另一個Bean(假定名為B)的引用作為成員變數b,
SpringBoot基礎篇Bean之自動載入
前面一篇介紹了Bean的常用姿勢,在一個專案中,可能不會出現什麼問題,可如果你提供了一個Jar包供第三方使用者使用,那麼你這個jar包中的Bean,能被第三方載入麼? 本篇博文將主要介紹AutoConfig相關的內容,即如果我想提供一個jar包供第三方在Spr
SpringBoot基礎篇Bean之條件注入@Condition使用姿勢
前面幾篇關於Bean的基礎博文中,主要集中在Bean的定義和使用,但實際的情況中有沒有一些場景是不載入我定義的bean,或者只有滿足某些前提條件的時候才載入我定義的Bean呢? 本篇博文將主要介紹bean的載入中,條件註解@Conditional的相關使用 I. @Con
SpringCloud基礎篇AOP之攔截優先順序詳解
開發十年,就只剩下這套架構體系了! >>>
SpringBoot基礎篇配置資訊之如何讀取配置資訊
SpringBoot極大的減少了配置,開一個新專案時,完全可以做到什麼配置都不加,就可以直接跑,簡單方便的同時,就帶來了一個問題 怎麼知道這些預設的配置是什麼? 如果要修改預設配置怎麼辦? 如何新增自定義的配置? 如何讀取這些配置? I. 配置資訊讀取 首
SpringBoot基礎篇配置資訊之多環境配置資訊
前面一篇主要介紹的是如何獲取配置資訊,接下來則是另外一個非常非常基礎和必要的知識點了,應用如何根據不同的環境來選擇對應的配置,即配置的多環境選擇問題 I. 多環境配置 配置區分環境,最直觀的如測試環境和生產環境的DB不同,測試環境的應用要求連線測試DB;生成
SpringBoot基礎篇之重名Bean的解決與多實例選擇
choose r.java console 自動 turn tin pri nested port 當通過接口的方式註入Bean時,如果有多個子類的bean存在時,具體哪個bean會被註入呢?系統中能否存在兩個重名的bean呢?如果可以,那麽怎麽選擇引入呢?如果不行的話又該
《Python基礎篇》之初識Python一
file dff lam lag port nag elong dir car %E4%BD%BF%E7%94%A8CHttpFile%E4%BB%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E6%AD%A3%E7%A1%AE%E7%9A%
[ 轉載 ] Java多線程系列--“基礎篇”04之 synchronized關鍵字
span 多線程 sync body 關鍵字 .com style 基礎 pos http://www.cnblogs.com/skywang12345/p/3479202.html[ 轉載 ] Java多線程系列--“基礎篇”04之 synchronized關鍵字
申論(基礎篇)之基礎常識復習建議
地形 pos 基礎 ima clas 技術 相關 歷史人物 復習 一、歷史常識復習建議(1~3題) 以歷史人物為中心學習相關的歷史事實、常識。 二、地理知識復習建議(1~3題) 1、自轉和公轉 2、中國三級階梯 3、
Java多線程系列---“基礎篇”04之 synchronized關鍵字
www. 時間 pub syn pre 現在 執行流程 什麽 你是 轉自:https://www.cnblogs.com/skywang12345/p/3479202.html (含部分修改) 概要 本章,會對synchronized關鍵字進行介紹。涉及到的內容包括:
Java多線程系列---“基礎篇”07之 線程休眠
技術分享 ring 說明 href super 等於 在線 for logs 轉自:http://www.cnblogs.com/skywang12345/p/3479256.html (含部分修改) 概要 本章,會對Thread中sleep()方法進行介紹。涉及到的內
Java多線程系列---“基礎篇”08之 join()
spa www gif 怎麽 ... try run stack 通過 轉自:http://www.cnblogs.com/skywang12345/p/3479275.html (含部分修改) 概要 本章,會對Thread中join()方法進行介紹。涉及到的內容包括:
JAVA常用集合框架用法詳解基礎篇一之Colletion介面
首先,在學習集合之前我們能夠使用的可以儲存多個元素的容器就是陣列。 下面舉幾個例子主要是引出集合類的: 1、8,4,5,6,7,55,7,8 像這樣的型別相同的可以使用陣列來儲存,本例可以用int[] arr來儲存。 2、”zhnagsan”,true,68 像這樣的可以使
JAVA常用集合框架用法詳解基礎篇三之Colletion子介面Set
這一篇我們來介紹Collection介面的另一個子介面,Set介面。Set是個介面,元素不可以重複,是無序的。Set介面中的方法和Collection的一致。 A、Set的子類: 1、HashSet:此類實現的Set介面,由雜湊表(實際上是一個HashMap)例項支援,它不保證Set的迭代順
JAVA常用集合框架用法詳解基礎篇二之Colletion子介面List
接著上一篇,接著講講集合的知識。上一篇講了Collection介面。它可以說是集合的祖先了,我們這一篇就說說它的子孫們。 一、Collection的子介面 List:有序(存入和取出的順序一致),元素都有索引(即角標),元素可以重複。 Set:元素不能重複,無序的。 首先講講L
Java多線程系列---“基礎篇”13之 樂觀鎖與悲觀鎖
而是 關系型 lock color 情況 發現 mis 再次 中一 轉自:http://www.cnblogs.com/zhengbin/p/5657435.html 樂觀鎖 樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認
Java多執行緒系列---“基礎篇”14之 wait,sleep,join,yield,park,unpark,notify等通訊機制對比
1. 執行緒讓步: yield() yield()的作用是讓步。它能讓當前執行緒由“執行狀態”進入到“就緒狀態”,從而讓其它具有相同優先順序的等待執行緒獲取執行權;但是,並不能保證在當前執行緒呼叫yield()之後,其它具有相同優先順序的執行緒就一定能獲得執行權;也有可能是當前執行緒又進入到“執行狀態”繼續