自定义注解+Aop+springboot应用于记录操作日志

在我们做的系统中,有时需要记录操作日志,方便找到某个操作是谁进行的,这个可以用spring的aop来实现,本篇博客记录用自定义注解+aop应用于springboot项目中实现操作日志的记录

1、aop相关术语介绍

  • 连接点(Joinpoint): 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点;简单来说,就是在哪加入你的逻辑增强,连接点表示具体要拦截的方法,切点是定义一个范围,而连接点是具体到某个方法
  • 切点(PointCut):每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。比如,连接点是数据库的记录,切点是查询条件;切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围
  • 增强(Advice):增强是织入到目标类连接点上的一段程序代码;在Spring中,像BeforeAdvice等还带有方位信息;通知是直译过来的结果,我个人感觉叫做“业务增强”更合适,对照代码就是拦截器定义的相关方法,通知分为如下几种:

       前置通知(before):在执行业务代码前做些操作,比如获取连接对象
       后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象
       异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务
       返回通知(afterReturning):在执行业务代码后无异常,会执行的操作
       环绕通知(around):环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行

  • 目标对象(Target):需要被加强的业务对象
  • 织入(Weaving):织入就是将增强添加到对目标类具体连接点上的过程;织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
  • 代理类(Proxy):一个类被AOP织入增强后,就产生了一个代理类
  • 切面(Aspect):切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。SpringAOP将切面定义的内容织入到我们的代码中,从而实现前后的控制逻辑。 比如我们常写的拦截器Interceptor,就是一个切面类

2、核心代码

Log.java(自定义注解):

package com.zking.component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解
 * @Author LiJun
 * @Date 2020/02/10
 * @Time 20:35
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
   //用于获取方法描述
   String value() default "";
}

WebLogAspect.java(用于打印输出日志的切面):

package com.zking.component;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * 用于打印输出日志的切面
 * @Author LiJun
 * @Date 2020/02/10
 * @Time 20:35
 */
@Component
@Aspect
@Slf4j
public class WebLogAspect {

    @Pointcut("execution( * com.zking..controller.*.*(..))")//两个..代表所有子目录,最后括号里的两个..代表所有参数
    public void logPointCut() {
    }

    @Before("logPointCut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        //接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        //记录下请求内容
        log.info("请求地址 : " + request.getRequestURL().toString());
        log.info("HTTP METHOD : " + request.getMethod());
        log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        log.info("参数 : " + Arrays.toString(joinPoint.getArgs()));
    }

    @AfterReturning(returning = "ret", pointcut = "logPointCut()")//returning的值和doAfterReturning的参数名一致
    public void doAfterReturning(Object ret) throws Throwable {
        //处理完请求,返回内容(返回值太复杂时,打印的是物理存储空间的地址)
        log.debug("返回值 : " + ret);
    }

    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object ob = pjp.proceed();//ob为方法的返回值
        log.info("耗时 : " + (System.currentTimeMillis() - startTime));
        return ob;
    }
}

LogAspect.java(用于记录操作日志到数据库的切面):

package com.zking.component;

import com.zking.entity.LogDO;
import com.zking.entity.User;
import com.zking.service.LogService;
import com.zking.utils.HttpContextUtils;
import com.zking.utils.IPUtils;
import com.zking.utils.JSONUtils;
import com.zking.utils.ShiroUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

/**
 * 用于记录日志的切面
 * @Author LiJun
 * @Date 2020/02/13
 */
@Aspect
@Component
@Slf4j
public class LogAspect {
    @Autowired
    LogService logService;

    /**
     * 注解Pointcut切入点
     * 定义出一个或一组方法,当执行这些方法时可产生通知
     * 指向你的切面类方法
     * 由于这里使用了自定义注解所以指向你的自定义注解
     */
    @Pointcut("@annotation(com.zking.component.Log)")
    public void logPointCut() {
    }

    /**
     * 使用环绕通知
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //异步保存日志
        saveLog(point, time);
        return result;
    }

    void saveLog(ProceedingJoinPoint joinPoint, long time) throws InterruptedException {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogDO sysLog = new LogDO();
        //获取方法上的自定义注解
        Log syslog = method.getAnnotation(Log.class);
        if (syslog != null) {
            // 注解上的描述
            sysLog.setOperation(syslog.value());
        }
        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");

        //获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        //请求的参数
        sysLog.setParams(JSONUtils.beanToJson(request.getParameterMap()));

        //设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));

        //用户名
        User currUser = ShiroUtils.getUser();
        if (null == currUser) {
            sysLog.setUserId(-1);
            sysLog.setUserName("获取用户信息为空");
        }
        else {
            sysLog.setUserId(ShiroUtils.getUserId());
            sysLog.setUserName(ShiroUtils.getUser().getUserName());
        }
        //用时
        sysLog.setTime((int)time);
        //系统当前时间
        Date date = new Date();
        sysLog.setGmtCreate(date);
        //保存系统日志
        logService.save(sysLog);
    }
}

使用如下:

自定义注解+Aop+springboot应用于记录操作日志_第1张图片

日志展示界面如下:

自定义注解+Aop+springboot应用于记录操作日志_第2张图片

3、相关表设计

保存操作日志所用表设计如下:

自定义注解+Aop+springboot应用于记录操作日志_第3张图片

参考:http://javaxl.com/blog/articles/236

你可能感兴趣的:(总结,springboot,java,自定义注解,记录操作日志)