AOP实现日志入参出参打印


描述

减少代码中接口的入参出参打印,减少工作量,保持打印风格一致性。

效果展示

[2020-07-23 14:50:09 INFO  http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - top.gmaya.gmayaserviceadminimpl.system.controller.UserController.add()【新增登录信息】:===================
[2020-07-23 14:50:09 INFO  http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - top.gmaya.gmayaserviceadminimpl.system.controller.UserController.add()【方法请求参数为】:{"data":{"id":1,"name":"1212"},"token":"11111111"}
[2020-07-23 14:50:09 INFO  http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - top.gmaya.gmayaserviceadminimpl.system.controller.UserController.add()【方法返回结果为】:{"msg":"success","code":0,"data":1}
[2020-07-23 14:50:09 INFO  http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - top.gmaya.gmayaserviceadminimpl.system.controller.UserController.add()【方法执行时长为】:208 ms
[2020-07-23 14:50:09 INFO  http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - {"method":"POST","createTime":1595487009526,"ip":"192.168.21.1","methodName":"add","className":"top.gmaya.gmayaserviceadminimpl.system.controller.UserController","createUser":"GMaya","time":208,"operation":"新增登录信息","url":"http://localhost:9001/user/add"}
[2020-07-23 14:50:09 INFO  http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - top.gmaya.gmayaserviceadminimpl.system.controller.UserController.add()【保存数据库成功!】:===================

实现

思路:
一个注解类, 一个切面类。使用环绕通知,将入参出参打印出来,可以根据实际情况,有些接口只需要打印即可,有些需要打印并保存到数据库。

注解类GmLog

package top.gmaya.gmayaserviceadminimpl.system.annotation;

import java.lang.annotation.*;

/**
 * 自定义日志注解
 * @author GMaya
 * @dateTime 2020/7/23 10:29
 * 1.运行时 使用使用注解
 * 2.注解作用于方法上
 * 3.注解是否将包含在 JavaDoc 中
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@Documented
public @interface GmLog {

    // 打印日志描述信息
    String value() default "";
    // TODO 是否保存到数据库
    boolean isSave() default false;
}

切面类LogAspect
里面有两种方式,一个是拦截添加注解的方法(适合新建项目,写接口的时候加上注解),一个是指定的包名下面所有的接口(适合现有项目,不必要改变其余代码)


package top.gmaya.gmayaserviceadminimpl.system.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.gmaya.gmayaserviceadminimpl.system.annotation.GmLog;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.Date;

/**
 * 用于记录注解上接口的入参出参,统一规范。
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    /**
     * 方法一: 不需要自定义注解, 直接拦截所有controller的请求。全部打印
     * 定义切入点表达式
     * 第一个*号:表示返回类型, *号表示所有的类型。
     * 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包.
     * 第二个*号:表示类名,*号表示所有的类。
     * *(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
     */
    @Pointcut("execution(public * top.gmaya.gmayaserviceadminimpl.system.controller..*.*(..))")
    public void privilege() {
    }

    /**
     * 方法二:拦截该注解标识的方法
     */
    @Pointcut("@annotation(top.gmaya.gmayaserviceadminimpl.system.annotation.GmLog)")
    public void logPointCut() {
    }

    /**
     * 环绕通知
     * @param pjd
     * @return
     * @throws Throwable
     */
    //    @Around("privilege()") // 第一种方式
    @Around("logPointCut()") // 第二种方式
    public Object arount(ProceedingJoinPoint pjd) throws Throwable {
        long startTime = System.currentTimeMillis();

        // 类名
        String className = pjd.getTarget().getClass().getName();
        // 获取执行的方法名称
        String methodName = pjd.getSignature().getName();

        // 1. 如果是使用的第二种方式,则判断该方法是否使用了改注解
        // 2. 如果是使用的第一种方式,直接注释即可。
        GmLog gmLog = this.getAnnotationLog(pjd);
        if (gmLog != null) {
            String value = gmLog.value();
            log.info("{}.{}()【{}】:===================", className, methodName, value);
        }

        Object[] args = pjd.getArgs();
        try {
            String params = JSON.toJSONString(args[0]);
            //打印请求参数参数
            log.info("{}.{}()【方法请求参数为】:{}", className, methodName, params);
        } catch (Exception e) {
            log.info("{}.{}()【方法请求参数打印失败】:{}", className, methodName, e);
        }
        // 执行目标方法
        Object result = pjd.proceed();
        // 打印返回结果
        try {
            String s = JSON.toJSONString(result);
            log.info("{}.{}()【方法返回结果为】:{}", className, methodName, s);
        } catch (Exception e) {
            log.info("{}.{}()【方法返回结果打印失败】:{}", className, methodName, e);
        }
        // 获取执行完的时间
        long time = System.currentTimeMillis() - startTime;
        log.info("{}.{}()【方法执行时长为】:{}{}", className, methodName, time, " ms");
        // 如果使用第一种方式,把这里注释掉
        // TODO 这里可以考虑新加一个异步方法,保存信息到数据库,入参,出参,请求人,请求时间,ip信息等,如果有异常,还有异常信息。
        if (gmLog != null) {
            boolean save = gmLog.isSave();
            if (save) {
                String val = gmLog.value();
                // 调用异步保存数据库方法
                int i = this.saveLog(pjd, time, val);
                if (i > 0) {
                    // 判断插入条数,大于0,保存成功。
                    log.info("{}.{}()【{}】:===================", className, methodName, "保存数据库成功!");
                }
            }
        }
        return result;
    }

    /**
     * 是否存在注解,如果存在就获取
     * @param joinPoint
     * @return
     */
    private GmLog getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(GmLog.class);
        }
        return null;
    }

    /**
     * 保存到数据库
     * @param joinPoint
     * @param time 方法执行时间 单位ms
     * @param val 方法请求描述
     * @return
     */
    private int saveLog(JoinPoint joinPoint, long time, String val) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
            .getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        JSONObject jsonObject = new JSONObject();
        // ip地址
        String hostAddress = "";
        try {
            hostAddress = InetAddress.getLocalHost().getHostAddress();
        } catch (Exception e) {
            log.error("获取ip失败");
        }
        // redis.getUserId(); 结合实际情况 获取当前登录人信息
        // 类名
        String className = joinPoint.getTarget().getClass().getName();
        // 获取执行的方法名称
        String methodName = joinPoint.getSignature().getName();
        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        jsonObject.put("ip", hostAddress);
        jsonObject.put("className", className);
        jsonObject.put("methodName", methodName);
        jsonObject.put("url", url);
        // 执行时间
        jsonObject.put("time", time);
        jsonObject.put("createTime", new Date());
        jsonObject.put("createUser", "GMaya");
        // 操作描述
        jsonObject.put("operation", val);
        jsonObject.put("method", method);
        String s = jsonObject.toJSONString();
        // 调用日志service的add方法即可!
        log.info(s);
        return 1;
    }

}

controller层接口展示:

    @RequestMapping("add")
    @GmLog(value = "新增登录信息" , isSave = true)
    public R add(@RequestBody F<UserEntity> f) {
        // 登录用户信息
        UserEntity user = this.getUser(f.getToken());
        return R.data(userService.add(f,user));
    }

只需要添加注解即可,以及是否保存到数据库,默认不保存。

@GmLog(value = "新增登录信息" , isSave = true)

写完,测试一下,完美!


文章作者: GMaya
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 GMaya !
评论
 本篇
AOP实现日志入参出参打印 AOP实现日志入参出参打印
描述减少代码中接口的入参出参打印,减少工作量,保持打印风格一致性。 效果展示[2020-07-23 14:50:09 INFO http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.
2020-07-23
下一篇 
Feign注解的简单使用 Feign注解的简单使用
调用微服务如果在同一个注册中心上的微服务。name:就是在注册中心的名称 @FeignClient(name = "gmaya-service-admin") public interface UserFacade { @Requ
2020-06-28
  目录