本文内容重点:

  • 问题描述

  • Spring AOP执行顺序

  • 探究顺序错误的真相

  • 代码验证

  • 结论

 

问题描述

公司新项目需要搭建一个新的前后分离HTTP服务,我选择了目前比较熟悉的SpringBoot Web来快速搭建一个可用的系统。

鲁迅说过,不要随便升级已经稳定使用的版本。我偏不信这个邪,仗着自己用了这么久Spring,怎么能不冲呢。不说了,直接引入了最新的SprinBoot 2.3.4.RELEASE版本,开始给项目搭架子。

起初,大多数的组件引入都一切顺利,本以为就要大功告成了,没想到在搭建日志切面时栽了跟头。

作为一个接口服务,为了方便查询接口调用情况和定位问题,一般都会将请求日志打印出来,而Spring的AOP作为切面支持,完美的切合了日志记录的需求。

之前的项目中,运行正确的切面日志记录效果如下图:

可以看到图内的一次方法调用,会输出请求url,出入参,以及请求IP等等,之前为了好看,还加入了分割线。

我把这个实现类放入新项目中,执行出来却是这样的:

我揉了揉眼睛,仔细看了看复制过来的老代码,精简版如下:

/*** 在切点之前织入* @param joinPoint* @throws Throwable*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {// 开始打印请求日志ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 初始化traceIdinitTraceId(request);// 打印请求相关参数LOGGER.info("========================================== Start ==========================================");// 打印请求 urlLOGGER.info("URL            : {}", request.getRequestURL().toString());// 打印 Http methodLOGGER.info("HTTP Method    : {}", request.getMethod());// 打印调用 controller 的全路径以及执行方法LOGGER.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());// 打印请求的 IPLOGGER.info("IP             : {}", IPAddressUtil.getIpAdrress(request));// 打印请求入参LOGGER.info("Request Args   : {}", joinPoint.getArgs());
}/*** 在切点之后织入* @throws Throwable*/
@After("webLog()")
public void doAfter() throws Throwable {LOGGER.info("=========================================== End ===========================================");
}/*** 环绕* @param proceedingJoinPoint* @return* @throws Throwable*/
@Around("webLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object result = proceedingJoinPoint.proceed();// 打印出参LOGGER.info("Response Args  : {}", result);// 执行耗时LOGGER.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);return result;
}

代码感觉完全没有问题,难道新版本的SpringBoot出Bug了。

显然,成熟的框架不会在这种大方向上犯错误,那会不会是新版本的SpringBoot把@After和@Around的顺序反过来了?

其实事情也没有那么简单。

 

Spring AOP执行顺序

我们先来回顾下Spring AOP执行顺序。

我们在网上查找关于SpringAop执行顺序的的资料,大多数时候,你会查到如下的答案:

正常情况

异常情况

多个切面的情况

所以@Around理应在@After之前,但是在SprinBoot 2.3.4.RELEASE版本中,@Around切切实实执行在了@After之后。

当我尝试切换回2.2.5.RELEASE版本后,执行顺序又回到了@Around-->@After

 

探究顺序错误的真相

既然知道了是SpringBoot版本升级导致的问题(或者说顺序变化),那么就要来看看究竟是哪个库对AOP执行的顺序进行了变动,毕竟,SpringBoot只是“形”,真正的内核在Spring。

我们打开pom.xml文件,使用插件查看spring-aop的版本,发现SpringBoot 2.3.4.RELEASE 版本使用的AOP是spring-aop-5.2.9.RELEASE。

而2.2.5.RELEASE对应的是spring-aop-5.2.4.RELEASE

于是我去官网搜索文档,不得不说Spring由于过于庞大,官网的文档已经到了冗杂的地步,不过最终还是找到了:

https://docs.spring.io/spring-framework/docs/5.2.9.RELEASE/spring-framework-reference/core.html#aop-ataspectj-advice-ordering

As of Spring Framework 5.2.7, advice methods defined in the same @Aspect class that need to run at the same join point are assigned precedence based on their advice type in the following order, from highest to lowest precedence: @Around, @Before, @After, @AfterReturning, @AfterThrowing.

我粗浅的翻译一下重点:

从Spring5.2.7开始,在相同@Aspect类中,通知方法将根据其类型按照从高到低的优先级进行执行:@Around,@Before ,@After,@AfterReturning,@AfterThrowing。

这样看其实对比不明显,我们再回到老版本,也就是2.2.5.RELEASE对应的spring-aop-5.2.4.RELEASE,当时的文档是这么写的:

What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first "on the way in" (so, given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so, given two pieces of after advice, the one with the highest precedence will run second).

简单翻译:在相同@Aspect类中Spring AOP遵循与AspectJ相同的优先级规则来确定advice执行的顺序。

再挖深一点,那么AspectJ的优先级规则是什么样的?

我找了AspectJ的文档:

https://www.eclipse.org/aspectj/doc/next/progguide/semantics-advice.html

At a particular join point, advice is ordered by precedence.

A piece of around advice controls whether advice of lower precedence will run by calling proceed. The call to proceed will run the advice with next precedence, or the computation under the join point if there is no further advice.

A piece of before advice can prevent advice of lower precedence from running by throwing an exception. If it returns normally, however, then the advice of the next precedence, or the computation under the join pint if there is no further advice, will run.

Running after returning advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation returned normally, the body of the advice will run.

Running after throwing advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation threw an exception of an appropriate type, the body of the advice will run.

Running after advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then the body of the advice will run.

大伙又要说了,哎呀太长不看!简短地说,Aspectj的规则就是上面我们能够在网上查阅到的顺序图展示的那样,依旧是老的顺序。

 

代码验证

我把业务逻辑从代码中删除,只验证下这几个advice的执行顺序:

/*** 日志切面*/
@Aspect
@Component
public class WebLogAspect {private final static Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);/** 以 controller 包下定义的所有请求为切入点 */@Pointcut("execution(public * com.xx.xxx.xxx.controller..*.*(..))")public void webLog() {}/*** 在切点之前织入* @param joinPoint* @throws Throwable*/@Before("webLog()")public void doBefore(JoinPoint joinPoint) throws Throwable {LOGGER.info("-------------doBefore-------------");}@AfterReturning("webLog()")public void afterReturning() {LOGGER.info("-------------afterReturning-------------");}@AfterThrowing("webLog()")public void afterThrowing() {LOGGER.info("-------------afterThrowing-------------");}/*** 在切点之后织入* @throws Throwable*/@After("webLog()")public void doAfter() throws Throwable {LOGGER.info("-------------doAfter-------------");}/*** 环绕* @param proceedingJoinPoint* @return* @throws Throwable*/@Around("webLog()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long startTime = System.currentTimeMillis();LOGGER.info("-------------doAround before proceed-------------");Object result = proceedingJoinPoint.proceed();LOGGER.info("-------------doAround after proceed-------------");return result;}

我们将版本改为2.2.5.RELEASE,结果如图:

我们将版本改为2.3.4.RELEASE,结果如图:

结论

经过上面的资料文档查阅,我能给出的结论是

从Spring5.2.7开始,Spring AOP不再严格按照AspectJ定义的规则来执行advice,而是根据其类型按照从高到低的优先级进行执行:@Around,@Before ,@After,@AfterReturning,@AfterThrowing。

这次的研究思考十分仓促,如果结论有误请大家踊跃指正,也欢迎大家自己尝试,毕竟口说无凭,实验是检验真理的唯一标准!

参考

https://www.cnblogs.com/dennyLee2025/p/13724981.html

https://segmentfault.com/a/1190000011283029

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

你真的确定Spring AOP的执行顺序吗相关推荐

  1. 顺序执行_执行流程 | 你真的了解Spring AOP的执行顺序吗?

    Hi! 我是小小,我们又见面了,今天的主要内容是,你真的了解Spring AOP的执行顺序吗?跟随着我的脚步,一块丈量世界,了解世界,重新认识,重新了解Spring AOP的执行顺序. 聊一聊毕业四个 ...

  2. gtw-050090|执行拦截器时发生异常_执行流程 | 你真的了解Spring AOP的执行顺序吗?...

    Hi! 我是小小,我们又见面了,今天的主要内容是,你真的了解Spring AOP的执行顺序吗?跟随着我的脚步,一块丈量世界,了解世界,重新认识,重新了解Spring AOP的执行顺序. 聊一聊毕业四个 ...

  3. 执行流程 | 你真的了解Spring AOP的执行顺序吗?

    Hi! 我是小小,我们又见面了,今天的主要内容是,你真的了解Spring AOP的执行顺序吗?跟随着我的脚步,一块丈量世界,了解世界,重新认识,重新了解Spring AOP的执行顺序. 聊一聊毕业四个 ...

  4. spring aop不执行_使用Spring AOP重试方法执行

    spring aop不执行 我的一位博客关注者发送了一封电子邮件,要求我显示" Spring AOP的RealWorld用法"示例. 他提到,在大多数示例中,都演示了Spring ...

  5. 【线上排查实战】AOP切面执行顺序你真的了解吗

    前言 忙,是我这个月的主旋律,也是我频繁鸽文章的接口----蛮三刀把刀 公司这两个月启动了全新的项目,项目排期满满当当,不过该学习还是要学习.这不,给公司搭项目的时候,踩到了一个Spring AOP的 ...

  6. Spring学习总结(16)——Spring AOP实现执行数据库操作前根据业务来动态切换数据源

    深刻讨论为什么要读写分离? 为了服务器承载更多的用户?提升了网站的响应速度?分摊数据库服务器的压力?就是为了双机热备又不想浪费备份服务器?上面这些回答,我认为都不是错误的,但也都不是完全正确的.「读写 ...

  7. 灵魂画手图解Spring AOP实现原理!

    本篇旨在让读者对Spring AOP实现原理有一个宏观上的认识,因此会丢失一些细节,具体实现参考:老实人Spring源码目录 阅读本篇文章前,希望读者对Spring Ioc以及Spring AOP的使 ...

  8. Spring Aop 常见注解和执行顺序

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:juejin.cn/post/7062506923194581029 Spring 一开始最强大的就是 IOC / AOP 两 ...

  9. 多层AOP 解决AOP执行顺序

    2019独角兽企业重金招聘Python工程师标准>>> 业务概要: 1.表单验证. 2.短信验证码验证,如果是认证用户需要校验接收验证码的手机号和认证的手机号是否一致. 这种通用代码 ...

最新文章

  1. The Innovation | 中科院青促会主办精品英文期刊视频简介,定位IF 20+国际顶刊
  2. R语言percent函数用百分比表示数值实战
  3. linux系统未来或应用广泛
  4. 原理剖析-Netty之服务端启动工作原理分析(上)
  5. mysql 碎片率_mysql数据碎片太多怎么办?
  6. Prism安装、MVVM基础概念及一个简单的样例
  7. javascript - dom
  8. axios在派遣方法时候的异步
  9. Java与Web前端发展前景及薪资对比
  10. Oracle listener lsnrctl
  11. ADO.NET 基础知识
  12. 修改android设备型号
  13. 嵌入式思维 、不深不浅的理解
  14. 转载:技术大停滞——范式春梦中的地球工业文明:前言
  15. bcoma 应用程序发生错误_打开网页老是出现《应用程序错误》是怎么回事?
  16. 嵌入地图跳转高德地图_各种地图跳转导航
  17. 哔哩哔哩第三方神器软件,早该用上了
  18. 情人节 礼物TOP10
  19. 数据链路层 使用广播(一对多)信道 (图文详解)
  20. 我的EeePC 700,你快回来啊

热门文章

  1. windowblinds 6_过年回家选车很重要!锐骐6强势对比纳瓦拉
  2. wpf绑定treeview 带查找_如何查找,修复和避免C#.NET中内存泄漏的8个最佳实践
  3. html jade文件,Jade模板
  4. 简述运行PHP文件的步骤,简述PHP运行机制
  5. python可变对象与不可变对象_python 可变对象与不可变对象
  6. python 线性回归 统计检验 p值_PAST:最简便易用的统计学分析软件教程(一)软件基本信息介绍...
  7. KVM 创建虚拟机时,--os-variant参数这样填写
  8. IPS与IDS部署场景(直路部署,单臂部署,旁路部署,阻断)
  9. Error: Could not find or load main class org.elasticsearch.tools.JavaVersionChecker
  10. File “/usr/bin/yum“, line 30 及 File “/usr/libexec/urlgrabber-ext-down“, line 28