前言
忙,是我这个月的主旋律,也是我频繁鸽文章的接口————蛮三刀把刀
公司这两个月启动了全新的我的项目,我的项目排期满满当当,不过该学习还是要学习。这不,给公司搭我的项目的时候,踩到了一个 Spring AOP 的坑。
本文内容重点:
- 问题形容
- Spring AOP 执行程序
- 探索程序谬误的假相
- 代码验证
- 论断
本文浏览大略须要:3 分钟
码字不易,求个关注,欢送关注我的集体原创公众号:后端技术漫谈(二维码见文章底部)
问题形容
公司新我的项目须要搭建一个新的前后拆散 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…
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/aspec…
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 的执行程序:
package com.bj58.xfbusiness.cloudstore.system.aop;
import com.bj58.xfbusiness.cloudstore.utils.IPAddressUtil;
import com.bj58.xfbusiness.cloudstore.utils.TraceIdUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* 日志切面
*/
@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;
}
咱们将版本改为 <version>2.2.5.RELEASE</version>,后果如图:
咱们将版本改为 <version>2.3.4.RELEASE</version>,后果如图:
论断
通过下面的材料文档查阅,我能给出的论断是:
从 Spring5.2.7 开始,Spring AOP 不再严格依照 AspectJ 定义的规定来执行 advice,而是依据其类型依照从高到低的优先级进行执行:@Around,@Before,@After,@AfterReturning,@AfterThrowing。
这次的钻研思考非常仓促,如果论断有误请大家踊跃斧正,也欢送大家本人尝试,毕竟口说无凭,实验室测验真谛的唯一标准!
参考
https://www.cnblogs.com/denny…
https://segmentfault.com/a/11…
关注我
我是一名后端开发工程师。次要关注后端开发,数据安全,边缘计算等方向,欢送交换。
各大平台都能够找到我
- 微信公众号:后端技术漫谈
- Github:@qqxx6661
- CSDN:@蛮三刀把刀
- 知乎:@后端技术漫谈
- 掘金:@蛮三刀把刀
- 腾讯云 + 社区:@后端技术漫谈
原创文章次要内容
- 后端开发实战
- Java 面试常识
- 设计模式 / 数据结构 / 算法题解
- 读书笔记 / 逸闻趣事 / 程序人生
集体公众号:后端技术漫谈
如果文章对你有帮忙,无妨点赞,珍藏起来~