文章目录

  • 前言
  • 一、AOP是什么?
  • 二、注解介绍
  • 三、使用步骤
  • 1.导入jar
  • 2.数据库表
  • 3.实体类
  • 4.核心注解类
  • 5.工具类
  • 6.Service类
  • 7.Controller类
  • 总结



前言

本文章主要是SpringBoot使用@Aspect进行日志管理
@Log实现日志切入


一、AOP是什么?

AOP(Aspect Oriented Programming,⾯向切⾯编程)是通过预编译⽅式和运⾏期动态代理实现程序功能的统⼀维护的⼀种技术。AOP是OOP的延续,是软件开发中的⼀个热点,也是Spring框架中的⼀个重要内容,是函数式编程的⼀种衍⽣范型。利⽤AOP可以对业务逻辑的各个部分进⾏隔离,从⽽使得业务逻辑各部分之间的耦合度降低,提⾼程序的可重⽤性,同时提⾼了开发的效率。
在Spring AOP中业务仅仅关注自身的逻辑,将日志,性能统计,安全控制,事物处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到飞指导业务逻辑的方法中去,进而改变这些行为的时候不会影响业务逻辑。因此@Aspect注解应运而生。

二、注解介绍

@Aspect:作用是把当前类标识为一个切面供容器读取。
@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around:环绕增强,相当于MethodInterceptor。
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行。
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有。
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice。
@After: final增强,不管是抛出异或者正常退出都会执行。

三、使用步骤

1.导入jar

简单的引包,需要什么请自行网上找哈,网上都有。

/*springboot*/
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.1.4.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter', version: '2.1.4.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-jdbc', version: '2.1.4.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-jetty', version: '2.1.4.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '2.1.4.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '2.1.4.RELEASE'
    /*mybatis*/
    compile group: 'org.mybatis', name: 'mybatis', version: '3.5.2'
    compile group: 'org.mybatis', name: 'mybatis-spring', version: '2.0.2'

    /*mysql*/
    compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'

    /*commons工具类*/
    compile group: 'commons-codec', name: 'commons-codec', version: '1.6'
    compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1'
    compile group: 'org.apache.commons', name: 'commons-text', version: '1.6'
    
    /*Apache commons-io*/
    compile group: 'commons-io', name: 'commons-io', version: '2.5'

2.数据库表

就是一个普通的日志表,可以自行修改。

DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log`  (
  `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志di',
  `log_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作菜单名字',
  `log_description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
  `log_params` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT 'params',
  `log_addr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ip地址',
  `log_browser` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '浏览器',
  `log_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '日志类型',
  `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `user_phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '电话',
  `execute_time` int(20) NULL DEFAULT NULL COMMENT '请求耗时(毫秒)',
  `exception_detail` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '异常详细',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '日志记录时间创建时间',
  PRIMARY KEY (`log_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 978944553169981443 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
)

Springboot AOP实现日志 JoinPoint 请求方法 springboot aop日志管理_ci

3.实体类

get、set自行引哈,不贴了,太长了,上下翻闹心。

import java.util.Date;

/**
 * 日志
 */
public class H5LogEntity {

   	private Long logId;//日志id
    private String logMethod;//操作菜单名字
    private String logDescription;//描述
    private String logParams;//params
    private String logAddr;//ip地址
    private String logBrowser;//浏览器
    private String logType;//日志类型
    private String userName;//用户名
    private String userPhone;//用户名
    private Long executeTime;//请求耗时(毫秒)
    private byte[] exceptionDetail;//异常详细
    private Date createTime;//日志记录时间创建时间

    public LogEntity(String logType, Long executeTime) {
        this.logType = logType;
        this.executeTime = executeTime;
    }
    public LogEntity(){}
}

4.核心注解类

package com.xxxx.business.sys.annotation;

import java.lang.annotation.*;

/**
 * 日志注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    String value() default "";
}
package com.xxxx.business.sys.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * 日志切面处理类
 */
@Component//实现bean的注入
@Aspect
public class LogAspect {

    @Autowired
    LogService logService;

    ThreadLocal<Long> currentTime = new ThreadLocal<>();

    /**
     * 日志配置切入点
     */
    @Pointcut("@annotation(com.xxxx.business.sys.annotation.Log)")
    public void logPointcut() {
        System.out.println("--AOP日志切入--");
        // 该方法无方法体,主要为了让同类中其他方法使用此切入点
    }

    /**
     * 配置环绕通知,使用在方法logPointcut()上注册的切入点
     *
     * @param joinPoint JoinPoint对象封装了SpringAop中切面方法的信息
     */
    @Around("logPointcut()")// 此注解为:调用前和后都执行,如果只调用前或后,用@Before或@After
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result;
        currentTime.set(System.currentTimeMillis());//时间戳
        result = joinPoint.proceed();
        LogEntity log = new LogEntity("INFO", System.currentTimeMillis() - currentTime.get());
        currentTime.remove();//移除时间戳
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        logService.addLog(request, joinPoint, log);
        return result;
    }
    
    /**
     * 配置异常通知
     *
     * @param joinPoint JoinPoint对象封装了SpringAop中切面方法的信息
     * @param e         exception
     */
    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        LogEntity log = new LogEntity("ERROR", System.currentTimeMillis() - currentTime.get());
        currentTime.remove();//移除时间戳
        log.setExceptionDetail(ThrowableUtil.getStackTrace(e).getBytes());
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        logService.addLog(request, (ProceedingJoinPoint) joinPoint, log);
    }
}

5.工具类

没什特殊的,配合核心注解类使用。

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

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

/**
 * 获取 HttpServletRequest
 */
public class RequestHolder {

    public static HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
    }
}
import java.io.PrintWriter;
import java.io.StringWriter;

/**
 * 异常工具
 */
public class ThrowableUtil {

    /**
     * 获取堆栈信息
     */
    public static String getStackTrace(Throwable throwable) {
        StringWriter sw = new StringWriter();
        try (PrintWriter pw = new PrintWriter(sw)) {
            throwable.printStackTrace(pw);
            return sw.toString();
        }
    }
}

6.Service类

就是一个日志Service类,一看就懂。
Mapper类就不整了,自行写sql就行,我用的是mybatis。

package com.xxxx.business.sys.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.useragent.Browser;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.javassist.*;
import org.apache.ibatis.javassist.bytecode.CodeAttribute;
import org.apache.ibatis.javassist.bytecode.LocalVariableAttribute;
import org.apache.ibatis.javassist.bytecode.MethodInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * 日志
 */
@Service
public class LogService {

    private static final Logger logger = LoggerFactory.getLogger(LogService.class);

    @Autowired
    SnowflakeIdWorker idWorker;//生成主键的类
    @Autowired
    LogMapper logMapper;

    /**
     * 添加日志
     *
     * @param request
     * @param joinPoint
     * @param logEntity 日志实体类
     */
    public void addLog(HttpServletRequest request, ProceedingJoinPoint joinPoint, LogEntity logEntity) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取注解中的参数值
            Method method = signature.getMethod();
            //获取注解Log
            com.northinfo.business.sys.annotation.Log aopLog = method.getAnnotation(com.northinfo.business.sys.annotation.Log.class);

            //方法路径
            String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName();

            assert logEntity != null;//断言

            logEntity.setLogId(idWorker.nextId());//日志id
            logEntity.setLogMethod(methodName);//操作菜单名字
            logEntity.setLogDescription(aopLog.value());//描述
            logEntity.setLogParams(getParameter(joinPoint, signature.getName(), request));//params
            logEntity.setLogAddr(this.getIp2(request));//ip地址
            logEntity.setLogBrowser(this.getBrowser(request));//浏览器
            UserEntity user = null;//自行获取当前登录用户
            if (user != null) {
                logEntity.setUserName(user.getUserName());//用户名
                logEntity.setUserPhone(user.getUserPhone());//电话
            }
            logEntity.setCreateTime(new Date());//日志记录时间创建时间
            logMapper.insert(logEntity);
        } catch (Exception e) {
            logger.error("addLogError:{}", e);
        }
    }

    /**
     * 获取参数
     *
     * @param joinPoint
     * @param methodName 方法名
     * @param request
     * @return
     * @throws ClassNotFoundException
     * @throws NotFoundException
     */
    private String getParameter(JoinPoint joinPoint, String methodName, HttpServletRequest request) throws ClassNotFoundException, NotFoundException {
        Object[] params = joinPoint.getArgs();//参数值
        String classType = joinPoint.getTarget().getClass().getName();//获取操作菜单名字
        Class<?> clazz = Class.forName(classType);//获取操作菜单名字class
        String clazzName = clazz.getName();获取操作菜单名字
        Map<String, Object> nameAndArgs = getFieldsName(this.getClass(), clazzName, methodName, params);
        StringBuffer bf = new StringBuffer();//结果集
        if (!CollUtil.isEmpty(nameAndArgs)) {
            Iterator it = nameAndArgs.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry entry = (Map.Entry) it.next();
                String key = (String) entry.getKey();
                String value = "";
                if (entry.getValue() != null) {
                    value = JsonUtils.toJson(entry.getValue().toString());
                }
                bf.append(key).append("=");
                bf.append(value).append("&");
            }
        }
        if (StringUtils.isEmpty(bf.toString())) {
            bf.append(request.getQueryString());
        }
        bf.deleteCharAt(bf.length() - 1);//去掉最后一个“&”
        return String.format(bf.toString());
    }

    /**
     * 获取路径名称
     */
    private Map<String, Object> getFieldsName(Class cls, String clazzName, String methodName, Object[] args) throws NotFoundException {
        Map<String, Object> map = new HashMap<String, Object>();

        ClassPool pool = ClassPool.getDefault();//默认的类搜索路径
        ClassClassPath classPath = new ClassClassPath(cls);//获取一个ctClass对象
        pool.insertClassPath(classPath);

        CtClass cc = pool.get(clazzName);//class文件的抽象表示
        CtMethod cm = cc.getDeclaredMethod(methodName);//获取名称
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        //处理参数
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            // exception
            return map;
        }
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < cm.getParameterTypes().length; i++) {
            map.put(attr.variableName(i + pos), args[i]);//paramNames即参数名
        }
        return map;
    }

    /**
     * java获取客户端ip地址
     *
     * @param request
     * @return
     */
    private static String getIp2(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (com.northinfo.utils.StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if (com.northinfo.utils.StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        return request.getRemoteAddr();
    }

    /**
     * 获取浏览器
     *
     * @param request
     * @return
     */
    private static String getBrowser(HttpServletRequest request) {
        UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
        Browser browser = userAgent.getBrowser();
        return browser.getName();
    }
}

7.Controller类

想记录哪个,就是方法上@Log(“xxxxx”)就行。

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 登陆
 */
@RestController
@RequestMapping("/sys")
public class LoginController {

    /**
     * 发送验证码
     *
     * @param phoneNumber 接收验证码的手机号
     * @return
     */
    @Log("发送验证码")
    @RequestMapping("/sendVerfiyCode")
    public StringVo sendVerfiyCode(String phoneNumber) {
    	//自行写成功内容
        return new StringVo("发送成功");
    }

    /**
     * 通过短信验证码登录
     *
     * @param phoneNumber 接收验证码的手机号
     * @param code        短信验证码
     * @param request
     * @param response
     */
    @Log("登陆")
    @RequestMapping("/login")
    public StringVo loginBySmsVerfiyCode(String phoneNumber, String code, HttpServletRequest request, HttpServletResponse response) {
    	//自行写登录内容
        return new StringVo("登录成功");
    }
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家的支持。
此文章也借鉴了其他老师的代码,请多多包涵。