写点什么

你真的确定 Spring AOP 的执行顺序吗,爆赞

用户头像
极客good
关注
发布于: 刚刚
  • 代码验证

  • 结论


问题描述


========================================================================


公司新项目需要搭建一个新的前后分离 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();


// 初始化 traceId


initTraceId(request);


// 打印请求相关参数


LOGGER.info("========================================== Start ==========================================");


// 打印请求 url


LOGGER.info("URL : {}", request.getRequestURL().toString());


// 打印 Http method


LOGGER.info("HTTP Method : {}", request.getMethod());


// 打印调用 controller 的全路径以及执行方法


LOGGER.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());


// 打印请求的 IP


LOGGER.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


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


.org/aspectj/doc/next/progguide/semantics-advice.html



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

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
你真的确定Spring AOP的执行顺序吗,爆赞