写了个牛逼的日志切面,甩锅更方便了

最近项目进入联调阶段 , 服务层的接口需要和协议层进行交互 , 协议层需要将入参 [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