一大早, 小王就急匆匆的跑过来找我, 说: 周哥, 那个记录日志的功能我想请教一下.
因为公司某个项目要跟别的平台做对接, 我们这边需要给他们提供一套接口. 昨天, 我就将记录接口日志的工作安排给了小王.
下面是我跟小王的主要对话.
我: 说说怎么了?
小王: 我将记录接口日志的功能放到了每个 controller 中, 现在感觉有点繁琐, 我这样做是不是不太合适?
我: 为什么要去每个接口里记录日志?
小王: 最开始我是用的拦截器, 但是这样一个请求就记录了两条记录.
我: 为什么是两条?
小王: 在 preHandle 中记录一条请求数据, 在 postHandle 中记录一条响应数据.
我:... 你不是说你会 Aop 吗?
小王: Aop 也是一样, 在前置通知记录一条请求数据, 后置通知记录一条响应数据.
小王: 这个数据和以前记录操作日志的不太一样, 以前只需要在前置通知记录一条操作日志就可以了, 但是现在有响应, 所以只能在 controller 中记录日志了.
我: 那你知不知道有个环绕通知? 你说一下 Aop 就几种通知类型.
小王: 总共有五种, 分别是:
前置通知: 在我们执行目标方法之前运行 (@Before)
后置通知: 在我们目标方法运行结束之后, 不管有没有异常 (@After)
返回通知: 在我们的目标方法正常返回值后运行 (@AfterReturning)
异常通知: 在我们的目标方法出现异常后运行 (@AfterThrowing)
环绕通知: 目标方法的调用由环绕通知决定, 即你可以决定是否调用目标方法, joinPoint.procced() 就是执行目标方法的代码 . 环绕通知可以控制返回对象 (@Around)
接下来, 我们一起来演示一下如何使用环绕通知来解决小王的问题.
第一步: 提供接口用来接收参数和响应接口
- @RestController
- public class TestController {
- @GetMapping("/getName")
- public String getName(HttpServletRequest request) throw Exception {
- String result = "Java 旅途";
- String age = request.getParameter("age");
- if("18".equals(age)){
- result = "无法识别";
- }
- return result;
- }
- }
第二步: 定义切点
execution() 是比较常用的定义切点的表达式, execution() 语法如下:
execution(修饰符 返回值 包. 类. 方法名 (参数) throws 异常)
其中:
修饰符和 throws 异常可以省略不写
根据这些解释, 我们可以将第一步中的接口用 execution() 表达式来描述一下:
execution(String binzh.website.controller.TestController.GetName(HttpServletRequest))
*: 匹配所有项
..: 匹配任意个方法参数
.. 出现在类名中时, 后面必须跟 *, 表示包, 子孙包下的所有类;
现在我们优化一下上面的表达式, 定义切面为 controller 包及 controller 下面所有包的所有方法
execution(* binzh.website.controller..*.*(..))
第三步: 环绕通知记录日志
- @Around("execution(* binzh.website.controller..*.*(..))")
- public Object around(ProceedingJoinPoint joinPoint) {
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
- HttpServletRequest request = attributes.getRequest();
- String age = request.getParameter("age");
- Object proceed = "";
- try {
- proceed = joinPoint.proceed();
- } catch (Throwable e) {
- e.printStackTrace();
- }
- System.out.println("age==="+age);
- System.out.println("proceed ===="+proceed);
- return proceed;
- }
运行结果如下:
age===19
proceed ====Java 旅途
我们之所以可以用环绕通知来处理小王的问题. 其中一个重要的原因就是, 我们提供的所有接口都是经过统一加密的, 最后请求的参数都是一个固定的名字. 还需要注意的一点就是, 环绕通知的返回值类型必须大于等于方法的返回值, 即: 加入你方法返回 String 类型, 环绕通知不能写成 void 类型.
小王看到这里后, 恍然大悟, 准备赶紧回去试一下. 我急忙拉住他.
我: 如果接口出现异常了怎么办?
小王: 那我在异常通知里处理就可以了.
我: 你再想一下?
小王: 好像不行, 异常通知里获取不到请求参数.
我: 在环绕通知中捕获处理可以吗?
这时候, 看见小王眼睛发光, 惊讶的说了一句: 环绕通知太牛批了, 竟然可以完成前置通知, 后置通知和异常通知的工作!
这篇文章戏有点多, 别见怪. 实战是提升技术最有效的途径!
来源: https://www.cnblogs.com/zhixie/p/13431251.html