写了个牛逼的日志切面,甩锅更方便了
最近项目进入联调阶段 , 服务层的接口需要和协议层进行交互 , 协议层需要将入参 [json 字符串] 组装成服务层所需的 json 字符串 , 组装的过程中很容易出错 。
入参出错导致接口调试失败问题在联调中出现很多次 , 因此就想写一个请求日志切面把入参信息打印一下 , 同时协议层调用服务层接口名称对不上也出现了几次 , 通过请求日志切面就可以知道上层是否有没有发起调用 , 方便前后端甩锅还能拿出证据 。
写在前面
本篇文章是实战性的 , 对于切面的原理不会讲解 , 只会简单介绍一下切面的知识点
切面介绍
面向切面编程是一种编程范式 , 它作为 OOP 面向对象编程的一种补充 , 用于处理系统中分布于各个模块的横切关注点 , 比如事务管理、权限控制、缓存控制、日志打印等等 。
AOP 把软件的功能模块分为两个部分:核心关注点和横切关注点 。 业务处理的主要功能为核心关注点 , 而非核心、需要拓展的功能为横切关注点 。 AOP 的作用在于分离系统中的各种关注点 , 将核心关注点和横切关注点进行分离 , 使用切面有以下好处:
- 集中处理某一关注点 / 横切逻辑
- 可以很方便的添加 / 删除关注点
- 侵入性少 , 增强代码可读性及可维护性 因此当想打印请求日志时很容易想到切面 , 对控制层代码 0 侵入
- @Aspect => 声明该类为一个注解类
- @Pointcut => 定义一个切点 , 可以简化代码
- @Before => 在切点之前执行代码
- @After => 在切点之后执行代码
- @AfterReturning => 切点返回内容后执行代码 , 可以对切点的返回值进行封装
- @AfterThrowing => 切点抛出异常后执行
- @Around => 环绕 , 在切点前后执行代码
- 使用 @Pointcut 定义切点
@Pointcut("execution(* your_package.controller..*(..))")public void requestServer() {}
@Pointcut 定义了一个切点 , 因为是请求日志切边 , 因此切点定义的是 Controller 包下的所有类下的方法 。 定义切点以后在通知注解中直接使用 requestServer 方法名就可以了- 使用 @Before 再切点前执行
@Before("requestServer()")public void doBefore(JoinPoint joinPoint) {ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();LOGGER.info("===============================Start========================");LOGGER.info("IP: {}", request.getRemoteAddr());LOGGER.info("URL: {}", request.getRequestURL().toString());LOGGER.info("HTTP Method: {}", request.getMethod());LOGGER.info("Class Method: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());}
在进入 Controller 方法前 , 打印出调用方 IP、请求 URL、HTTP 请求类型、调用的方法名- 使用 @Around 打印进入控制层的入参
@Around("requestServer()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long start = System.currentTimeMillis();Object result = proceedingJoinPoint.proceed();LOGGER.info("Request Params: {}", getRequestParams(proceedingJoinPoint));LOGGER.info("Result: {}", result);LOGGER.info("Time Cost: {} ms", System.currentTimeMillis() - start);return result;}
打印了入参、结果以及耗时- getRquestParams 方法
private Map getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {Map requestParams = new HashMap<>();//参数名String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();//参数值Object[] paramValues = proceedingJoinPoint.getArgs();for (int i = 0; i < paramNames.length; i++) {Object value = http://kandian.youth.cn/index/paramValues[i];//如果是文件对象if (value instanceof MultipartFile) {MultipartFile file = (MultipartFile) value;value = file.getOriginalFilename();//获取文件名}requestParams.put(paramNames[i], value);}return requestParams;}
通过 @PathVariable 以及 @RequestParam 注解传递的参数无法打印出参数名 , 因此需要手动拼接一下参数名 , 同时对文件对象进行了特殊处理 , 只需获取文件名即可- @After 方法调用后执行
@After("requestServer()")public void doAfter(JoinPoint joinPoint) {LOGGER.info("===============================End========================");}
没有业务逻辑只是打印了 End- 完整切面代码
@Component@Aspectpublic class RequestLogAspect {private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);@Pointcut("execution(* your_package.controller..*(..))")public void requestServer() {}@Before("requestServer()")public void doBefore(JoinPoint joinPoint) {ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();LOGGER.info("===============================Start========================");LOGGER.info("IP: {}", request.getRemoteAddr());LOGGER.info("URL: {}", request.getRequestURL().toString());LOGGER.info("HTTP Method: {}", request.getMethod());LOGGER.info("Class Method: {}.{}", joinPoint.getSignature().getDeclaringTypeName(),joinPoint.getSignature().getName());}@Around("requestServer()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long start = System.currentTimeMillis();Object result = proceedingJoinPoint.proceed();LOGGER.info("Request Params: {}", getRequestParams(proceedingJoinPoint));LOGGER.info("Result: {}", result);LOGGER.info("Time Cost: {} ms", System.currentTimeMillis() - start);return result;}@After("requestServer()")public void doAfter(JoinPoint joinPoint) {LOGGER.info("===============================End========================");}/*** 获取入参* @param proceedingJoinPoint** @return* */private Map getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {Map requestParams = new HashMap
- 震惊!京东T4大佬面试整整三个月,才写了两份java面试笔记
- 你只写了两行代码,为什么要花两天时间?
- 韩国人:逼迫中国自研芯片不妥当,光刻机离不开中国技术
- 荷兰职员:为何逼迫中国研发芯片?不知道光刻机少不了中国技术?
- 大屏逼死小屏!荣耀发布三个月中端机,不温不火的原因
- “女性机器人”为何备受喜爱?买家:触感好、逼真,还有3大功能
- 识别|这个人工智能工具可以生成逼真的人像来骗过面部识别
- 微信增加两项收费!发红包也要交钱,网友:逼着我用支付宝?
- 法国人:美国逗笑我了!光刻机离不开中国技术还逼他们自研芯片
- iPhone12蓝色真机曝光,果粉措手不及:逼着大家换华为?