更多相關內容,查看: http://spring.hhui.top/
前面一篇博文 190301-SpringBoot基礎篇AOP之基本使用姿式小結 介紹了aop的簡單使用方式,在文章最後,拋出了幾個問題待解決,本篇博文則將針對前面的問題,看下更多關於AOP的使用說明java
<!-- more -->git
前面一文,主要介紹的是根據正則表達式來攔截對應的方法,接下來演示下如何經過註解的方式來攔截目標方法,實現也比較簡單github
首先建立註解正則表達式
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AnoDot { }
接着在目標方法上添加註解,這裏藉助前面博文中工程進行說明,新建一個com.git.hui.boot.aop.demo2.AnoDemoBean
,注意這個包路徑,是不會被前文的AnoAspect
定義的Advice攔截的,這裏新建一個包路徑的目的就是爲了儘量的減小干擾項spring
@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
中添加(不知道前文的也不要緊,下面貼出相關的代碼類,前文的類容與本節內容無關)dom
@Aspect @Component public class AnoAspect { @Before("@annotation(AnoDot)") public void anoBefore() { System.out.println("AnoAspect "); } }
測試代碼spring-boot
@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
一個方法執行時,若是有多個advice知足攔截規則,是全部的都會觸發麼?經過前面一篇博文知道,不一樣類型的advice是均可以攔截的,若是出現多個相同類型的advice呢?測試
在前面一篇博文的基礎上進行操做,咱們擴展下com.git.hui.boot.aop.demo.DemoBean
ui
@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
嵌套的方式有幾種case,先看第一種
這裏咱們藉助第一節中的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
依然使用前面的例子進行說明,不過是稍稍改一下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
從上面的日誌分析中,能夠明確看出對比,調用本類中,知足被攔截的方法,也不會走切面邏輯;調用其餘類中的知足切面攔截的方法,會走切面邏輯
這個和兩個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
從輸出結果進行反推,一個結論是
前面測試的被攔截方法都是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 --------
本篇博文篇幅比較長,主要是測試代碼比較佔用地方,所以有必要簡單的小結一下,作一個清晰的概括,方便不想看細節,只想獲取最終結論的小夥伴
註解攔截方式:
@Around("@annotation(AnoDot)")
多advice狀況:
嵌套場景
做用域
優先級
這個內容由於特別多,因此有必要單獨拎出來,其主要的分類以下
一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
一灰灰blog
知識星球