前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot AOP 自定义注解异步监听方式实现日志记录(附源码)

SpringBoot AOP 自定义注解异步监听方式实现日志记录(附源码)

作者头像
小东啊
发布2019-06-26 15:14:05
2.8K0
发布2019-06-26 15:14:05
举报
文章被收录于专栏:李浩东的博客李浩东的博客

一. 功能简介

本文主要记录如何使用 注解+aop切面+异步监听 的方式来实现日志记录功能。

主要记录的信息有: 操作人,操作IP,方法名,参数,消耗时间,日志类型,操作类型(操作日志和异常日志)以及增删改查记录,操作时间等。

主要流程:

AOP切面得到请求数据 -> 发布监听事件 -> 异步监听日志入库

二. 项目结构

三. 项目实战

1.引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-aop</artifactId></dependency>
2.自定义注解

主要标注日志的具体用处也就是具体操作

package com.xd.pre.log;
import java.lang.annotation.*;
//元注解,定义注解被保留策略,一般有三种策略//1、RetentionPolicy.SOURCE 注解只保留在源文件中,在编译成class文件的时候被遗弃//2、RetentionPolicy.CLASS 注解被保留在class中,但是在jvm加载的时候北欧抛弃,这个是默认的声明周期//3、RetentionPolicy.RUNTIME 注解在jvm加载的时候仍被保留@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})  //定义了注解声明在哪些元素之前@Documentedpublic @interface SysLog {    //定义成员    String descrption() default "" ;//描述}
3.AOP切面类

AOP切面类是最主要的,可以使用自定义注解或针对包名实现AOP增强。

1)这里实现了对自定义注解的切点,对使用了自定义注解的方法进行AOP切面处理;

2)对方法运行时间进行监控;

3)对方法名,参数名,参数值,对日志描述以及异常信息的优化处理;

4)发布监听事件,日志异步入库

在方法上增加@Aspect 注解声明切面,使用@Pointcut 注解定义切点,标记方法。

package com.xd.pre.log;
import cn.hutool.core.convert.Convert;import cn.hutool.core.util.URLUtil;import cn.hutool.extra.servlet.ServletUtil;import com.xd.pre.security.PreUser;import com.xd.pre.security.util.SecurityUtil;import com.xd.pre.utils.LogUtil;import com.xd.pre.utils.R;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;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.time.Instant;import java.time.LocalDateTime;import java.util.Arrays;import java.util.Objects;
/** * @Classname SysLogAspect * @Description 系统日志切面 * @Author 李号东 lihaodongmail@163.comgit reset --merge * @Date 2019-04-22 23:52 * @Version 1.0 * ①切面注解得到请求数据 -> ②发布监听事件 -> ③异步监听日志入库 */@Slf4j@Aspect@Componentpublic class SysLogAspect {

    private com.xd.pre.domain.SysLog sysLog = new com.xd.pre.domain.SysLog();

    /**     * 事件发布是由ApplicationContext对象管控的,我们发布事件前需要注入ApplicationContext对象调用publishEvent方法完成事件发布     **/    @Autowired    private ApplicationContext applicationContext;
    /***     * 定义controller切入点拦截规则,拦截SysLog注解的方法     */    @Pointcut("@annotation(com.xd.pre.log.SysLog)")    public void sysLogAspect() {
    }
    /***     * 拦截控制层的操作日志     * @param joinPoint     * @return     * @throws Throwable     */    @Before(value = "sysLogAspect()")    public void recordLog(JoinPoint joinPoint) throws Throwable {
        // 开始时间        long beginTime = Instant.now().toEpochMilli();        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();        PreUser securityUser = SecurityUtil.getUser();        sysLog.setUserName(securityUser.getUsername());        sysLog.setActionUrl(URLUtil.getPath(request.getRequestURI()));        sysLog.setStartTime(LocalDateTime.now());        sysLog.setRequestIp(ServletUtil.getClientIP(request));        sysLog.setRequestMethod(request.getMethod());        sysLog.setUa(request.getHeader("user-agent"));        //访问目标方法的参数 可动态改变参数值        Object[] args = joinPoint.getArgs();        //获取执行的方法名        sysLog.setActionMethod(joinPoint.getSignature().getName());        // 类名        sysLog.setClassPath(joinPoint.getTarget().getClass().getName());        sysLog.setActionMethod(joinPoint.getSignature().getName());        sysLog.setFinishTime(LocalDateTime.now());        // 参数        sysLog.setParams(Arrays.toString(args));        sysLog.setDescription(LogUtil.getControllerMethodDescription(joinPoint));        long endTime = Instant.now().toEpochMilli();        sysLog.setConsumingTime(endTime - beginTime);    }
    /**     * 返回通知     * @param ret     * @throws Throwable     */    @AfterReturning(returning = "ret", pointcut = "sysLogAspect()")    public void doAfterReturning(Object ret) {        // 处理完请求,返回内容        R r = Convert.convert(R.class, ret);        if (r.getCode() == 200){            // 正常返回            sysLog.setType(1);        } else {            sysLog.setType(2);            sysLog.setExDetail(r.getMsg());        }        // 发布事件        applicationContext.publishEvent(new SysLogEvent(sysLog));    }
    /**     * 异常通知     * @param e     */    @AfterThrowing(pointcut = "sysLogAspect()",throwing = "e")    public void doAfterThrowable(Throwable e){        // 异常        sysLog.setType(2);        // 异常对象        sysLog.setExDetail(LogUtil.getStackTrace(e));        // 异常信息        sysLog.setExDesc(e.getMessage());        // 发布事件        applicationContext.publishEvent(new SysLogEvent(sysLog));    }
}

目前使用: 前置通知,后置通知,异常通知

事件发布是由 ApplicationContext对象管控的,我们发布事件前需要注入 ApplicationContext对象调用publishEvent方法完成事件发布

4.日志实体类

主要记录日志的详细信息

package com.xd.pre.domain;
import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableId;import java.time.LocalDateTime;import java.io.Serializable;import lombok.Data;import lombok.EqualsAndHashCode;import lombok.experimental.Accessors;
/** * <p> * 系统日志 * </p> * * @author lihaodong * @since 2019-04-27 */@Data@EqualsAndHashCode(callSuper = false)@Accessors(chain = true)public class SysLog implements Serializable {
    private static final long serialVersionUID = 1L;
    /**     * 主键     */    @TableId(value = "id", type = IdType.AUTO)    private Integer id;
    /**     * 操作IP     */    private String requestIp;
    /**     * 操作类型 1 操作记录 2异常记录     */    private Integer type;
    /**     * 操作人ID     */    private String userName;
    /**     * 操作描述     */    private String description;
    /**     * 请求方法     */    private String actionMethod;
    /**     * 请求url     */    private String actionUrl;
    /**     * 请求参数     */    private String params;
    /**     * 浏览器     */    private String ua;
    /**     * 类路径     */    private String classPath;
    /**     * 请求方法     */    private String requestMethod;
    /**     * 开始时间     */    private LocalDateTime startTime;
    /**     * 完成时间     */    private LocalDateTime finishTime;
    /**     * 消耗时间     */    private Long consumingTime;
    /**     * 异常详情信息 堆栈信息     */    private String exDetail;
    /**     * 异常描述 e.getMessage     */    private String exDesc;
}
5.ApplicationEvent&Listener完成业务解耦

ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。事件发布者并不需要考虑谁去监听,监听具体的实现内容是什么,发布者的工作只是为了发布事件而已。

其实生活中有很多这样的例子: 我们在平时常见的比赛中,裁判员或者主持人给我们开始的信号,也就是给我们发布了一个开始的事件,而参赛的双方人员都在监听着这个事件,一旦事件发布后双方人员就开始拼命努力赢得比赛。而裁判或者主持人并不关心你比赛的过程,只是给你发布事件你执行就可以了

系统日志事件

package com.xd.pre.log;
import com.xd.pre.domain.SysLog;import org.springframework.context.ApplicationEvent;
/** * @Classname SysLogEvent * @Description 系统日志事件 * @Author 李号东 lihaodongmail@163.com * @Date 2019-04-28 11:34 * @Version 1.0 */public class SysLogEvent extends ApplicationEvent {
    public SysLogEvent(SysLog source) {        super(source);    }}

我们自定义事件SysLogEvent继承了ApplicationEvent,继承后必须重载构造函数,构造函数的参数可以任意指定,其中source参数指的是发生事件的对象,一般我们在发布事件时使用的是this关键字代替本类对象,而sysLog参数是我们自定义的注册用户对象,该对象可以在监听内被获取。

异步监听日志事件

package com.xd.pre.log;
import com.xd.pre.domain.SysLog;import com.xd.pre.service.ISysLogService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.event.EventListener;import org.springframework.core.annotation.Order;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;
/** * @Classname SysLogListener * @Description 注解形式的监听 异步监听日志事件 * @Author 李号东 lihaodongmail@163.com * @Date 2019-04-28 11:34 * @Version 1.0 */@Slf4j@Componentpublic class SysLogListener {
    @Autowired    private ISysLogService sysLogService;
    @Async    @Order    @EventListener(SysLogEvent.class)    public void saveSysLog(SysLogEvent event) {        SysLog sysLog = (SysLog) event.getSource();        // 保存日志        sysLogService.save(sysLog);    }}

我们只需要让我们的监听类被Spring所管理即可,在我们用户注册监听实现方法上添加@EventListener注解,该注解会根据方法内配置的事件完成监听。

6.具体用法

只需要在你控制层要记录日志的方法上加上@SysLog注解即可

/** * 获取用户列表集合 * * @param page * @param userDTO * @return */@SysLog(descrption = "查询用户集合")@GetMapping@PreAuthorize("hasAuthority('sys:user:view')")public R getList(Page page, UserDTO userDTO) {    return R.ok(userService.getUsersWithRolePage(page, userDTO));}

源码地址

码云: https://gitee.com/li_haodong/pre

GitHub: https://github.com/LiHaodong888/pre

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 李浩东的博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 功能简介
  • 二. 项目结构
  • 三. 项目实战
    • 1.引入依赖
      • 2.自定义注解
        • 3.AOP切面类
          • 4.日志实体类
            • 5.ApplicationEvent&Listener完成业务解耦
              • 6.具体用法
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档