摘要:通知和切点共同定义了关于切面的全部内容,它是什么时候,在何时和何处完成功能引入允许我们向现有的类添加新的方法或者属性组装方面来创建一个被通知对象。这可以在编译时完成例如使用编译器,也可以在运行时完成。和其他纯框架一样,在运行时完成织入。
原文:190301-SpringBoot基础篇AOP之基本使用姿势小结
一般来讲,谈到Spring的特性,绕不过去的就是DI(依赖注入)和AOP(切面),在将bean的系列中,说了DI的多种使用姿势;接下来看一下AOP的玩法
I. 背景知识在实际使用之前有必要了解一下什么是AOP,以及AOP的几个基本概念
1. advicebefore: 在方法执行之前被调用
after: 在方法执行之后调用
after returning: 方法执行成功之后
after throwing: 方法抛出异常之后
around: 环绕,自己在内部决定方法的执行时机,因此可以在之前之后做一些业务逻辑
2. join point连接点,比如方法调用,方法执行,字段设置/获取、异常处理执行、类初始化、甚至是 for 循环中的某个点
但 Spring AOP 目前仅支持方法执行 (method execution)
简单来说,Spring AOP中,PointCut就是那个被拦截的方法
3. pointcut切点,用来描述满足什么规则的方法会被拦截
正则表达式 : @Before("execution(public * com.git.hui.demo.base.bean.*.*(..))")
注解拦截方式 :@Around("@annotation(parameterCheck)")
4. aspect切面是切点和通知的结合。通知和切点共同定义了关于切面的全部内容,它是什么时候,在何时和何处完成功能
5. introduction引入允许我们向现有的类添加新的方法或者属性
6. weaving组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
简单来讲就是生成一个代理类,在调用被拦截的方法时,实际上执行的是代理类,这个代理类内部执行切面逻辑
II. 使用说明 1. 基本配置首先是基本环境的搭建, 先贴上必要的xml配置, 使用aop需要引入包: spring-boot-starter-aop
2. 代码准备org.springframework.boot spring-boot-starter-parent 2.0.4.RELEASE UTF-8 UTF-8 Finchley.RELEASE 1.8 org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-maven-plugin spring-milestones Spring Milestones https://repo.spring.io/milestone false
首先创建一个被拦截的bean: com.git.hui.boot.aop.demo.DemoBean,如下
@Component public class DemoBean { /** * 返回随机的字符串 * * @param time * @return */ public String randUUID(long time) { try { System.out.println("in randUUID before process!"); return UUID.randomUUID() + "|" + time; } finally { System.out.println("in randUUID finally!"); } } }
接着在启动类中,执行
@SpringBootApplication public class Application { public Application(DemoBean demoBean) { String ans = demoBean.randUUID(System.currentTimeMillis()); System.out.println("----- ans: " + ans + "---------"); } public static void main(String[] args) { SpringApplication.run(Application.class); } }3. AOP使用
在实际使用之前,需要创建一个切面,用@Aspect声明,其次切面也需要作为bean托付给Spring容器管理
@Aspect @Component public class AnoAspcet { }a. before
在方法调用之前,需要执行一些操作,这个时候可以使用 @Before 注解来声明before advice
一种可使用姿势如下,我们的切点直接在注解中进行定义,使用正则表达式的方式
@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())); }b. after
在方法调用完毕之后,再执行一些操作,这个时候after就可以派上用场,为了考虑切点的通用性,我们可以考虑声明一个切点,使用@Pointcut注解
@Pointcut("execution(public * com.git.hui.boot.aop.demo.*.*(*))") public void point() { }
使用pointcut的方式也比较简单,如下
@After("point()") public void doAfter(JoinPoint joinPoint) { System.out.println("do in Aspect after method called! args: " + JSON.toJSONString(joinPoint.getArgs())); }c. after returning
在正常返回结果之后,再次执行,这个也挺有意思的,通常使用这个advice时,一般希望获取返回结果,那么应该怎么处理呢?
org.aspectj.lang.annotation.AfterReturning#returning 指定返回结果对应参数name
返回结果作为参数传入,要求类型一致,否则不生效
/** * 执行完毕之后,通过 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); }d. around
这个也比较常见,在方法执行前后干一些事情,比如常见的耗时统计,日志打印,安全控制等,很多都是基于around advice实现的
使用这个advice需要注意的是传入参数类型为 ProceedingJoinPoint,需要在方法内部显示执行org.aspectj.lang.ProceedingJoinPoint#proceed()来表示调用方法
@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; }e. 输出
执行之后输出如下
do in Aspect around ------ before do in Aspect before method called! args: [1551433188205] in randUUID before process! in randUUID finally! do in Aspect around ------- over! ans: 6849544b-160e-464c-80bd-641f2651c6c1|1551433188205 do in Aspect after method called! args: [1551433188205] do in Aspect after method return! args: 1551433188205 ans: 6849544b-160e-464c-80bd-641f2651c6c1|1551433188205 ----- ans: 6849544b-160e-464c-80bd-641f2651c6c1|1551433188205---------
从输出结果上,可以看到每个advice的使用范围,当然也带来了一些疑问
可以存在多个同类型的advice,拦截同一个目标吗?(如两个around都拦截methodA方法,那么methodA方法被调用时,两个around advice是否都会执行)
多个advice之间的优先级怎么定义?
aop拦截的目标方法有没有限制(对非public的方法可以拦截么?)
被拦截的方法中存在相互调用的时候,会怎样?(如methodA,methodB都可以被拦截,且methodA中调用了methodB,那么在执行methodA时,methodB的各种advice是否会被触发?)
基于注解的aop方式可以怎样用
以上这些问题留在下一篇进行介绍
III. 其他 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
知识星球
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/19468.html
摘要:时间年月日星期日说明本文部分内容均来自慕课网。慕课网教学示例源码个人学习源码第一章课程介绍课程介绍本课程紧接着小时学会课程,请先看入门课。异常返回通知在连接点抛出异常后执行。 时间:2017年3月19日星期日说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学示例源码:https://github.com/zccodere/s...个人学习源码:htt...
摘要:原文高级篇之修改基本使用姿势本篇依然是中的一篇,主要介绍的更新,主要内容如下常见类型成员的修改数组类型成员的增删改类型成员的增删改基本使用首先是准备好基本环境,可以参考博文高级篇之基本环境搭建与使用高级篇之查询基本使用姿势在开 原文: 190218-SpringBoot高级篇MongoDB之修改基本使用姿势 本篇依然是MongoDB curd中的一篇,主要介绍document的更新,...
摘要:总结动态代理的相关原理已经讲解完毕,接下来让我们回答以下几个思考题。 【干货点】 此处是【好好面试】系列文的第12篇文章。文章目标主要是通过原理剖析的方式解答Aop动态代理的面试热点问题,通过一步步提出问题和了解原理的方式,我们可以记得更深更牢,进而解决被面试官卡住喉咙的情况。问题如下 SpringBoot默认代理类型是什么 为什么不用静态代理 JDK动态代理原理 CGLIB动态代理...
阅读 2096·2021-11-16 11:45
阅读 1131·2021-10-22 09:53
阅读 3955·2021-09-07 10:26
阅读 1181·2021-09-06 15:00
阅读 2054·2019-08-28 18:09
阅读 2770·2019-08-26 14:06
阅读 3892·2019-08-26 13:48
阅读 1280·2019-08-26 12:11