前面一篇博文 190301-SpringBoot基础篇AOP之基本使用姿势小结 介绍了aop的简单使用方式,在文章最后,抛出了几个问题待解决,本篇博文则将针对前面的问题,看下更多关于AOP的使用说明
I. 高级技能 1. 注解拦截方式 前面一文,主要介绍的是根据正则表达式来拦截对应的方法,接下来演示下如何通过注解的方式来拦截目标方法,实现也比较简单
首先创建注解
1 2 3 4 @Target (ElementType.METHOD)@Retention (RetentionPolicy.RUNTIME)public @interface AnoDot {}
接着在目标方法上添加注解,这里借助前面博文中工程进行说明,新建一个com.git.hui.boot.aop.demo2.AnoDemoBean
,注意这个包路径,是不会被前文的AnoAspect
定义的Advice拦截的,这里新建一个包路径的目的就是为了尽可能的减少干扰项
1 2 3 4 5 6 7 8 9 10 11 12 @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
中添加(不知道前文的也没关系,下面贴出相关的代码类,前文的类容与本节内容无关)
1 2 3 4 5 6 7 8 @Aspect @Component public class AnoAspect { @Before ("@annotation(AnoDot)" ) public void anoBefore () { System.out.println("AnoAspect " ); } }
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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中的逻辑
1 2 3 4 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
1 2 3 4 5 6 7 8 9 10 11 12 @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!" ); } } }
对应的测试切面内容如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @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())); } @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" ); } }
测试代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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,都会被拦截到
1 2 3 4 5 6 7 8 9 10 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
类中,新增一个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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!" ); } } }
对应的切面为
1 2 3 4 5 6 7 8 9 10 11 12 @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
是否会被切面拦截住
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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 ) ; } }
输出结果如下,没有切面的日志,表明这种场景下,不会被拦截
1 2 3 4 5 in randUUID start! in genUUID before process! in genUUID finally! in randUUID finally! >>>>>>>0c6a5ccf-30c0-4ac0-97f2-3dc063580f3d|1551522176035
b. 调用方法不满足拦截规则,调用其他类中满足拦截条件的方法 依然使用前面的例子进行说明,不过是稍稍改一下AnoDemoBean
,调用第二节中的DemoBean的方法
DemoBean的代码如下
1 2 3 4 5 6 7 8 9 @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的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @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!" ); } } }
测试代码和前面完全一致,接下来看下输出
1 2 3 4 5 6 7 8 9 10 11 12 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
方法上添加注解,然后执行即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @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!" ); } } }
输出结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ## 最外层的切面拦截的是 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作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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方法的切面都走到了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 -------- 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情况:
嵌套场景
执行的目标方法,如果调用了本类中一个满足切面规则的方法A时,在执行方法A的过程中,不会触发切面逻辑
执行的目标方法,如果调用其他类中一个满足切面规则的方法B时,在执行方法B的过程中,将会触发切面逻辑
作用域
public, protected, default 作用域的方法都可以被拦截
优先级
这个内容因为特别多,所以有必要单独拎出来,其主要的分类如下
同一aspect,不同advice的执行顺序
不同aspect,advice的执行顺序
同一aspect,相同advice的执行顺序
II. 其他 0. 项目
1. 一灰灰Blog
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
2. 声明 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
3. 扫描关注 一灰灰blog
QrCode
知识星球
goals
Be the first person to leave a comment!