使用Aop的方式实现自动日志记录的方式详细介绍

 更新时间:2022年04月21日 12:00:21   作者:蠢蠢欲动的猫  
这篇文章主要介绍了使用Aop的方式实现自动日志记录,通过监听器去监听,当访问到具体的类方法,通过aop切面去获取访问的方法,然后将日志记录下来,就这种方式给大家介绍的非常详细,需要的朋友可以参考下

34、使用Aop的方式实现自动日志记录

自动日志记录的实现的两种方式:

①通过监听器去监听,当访问到具体的类方法,通过aop切面去获取访问的方法,然后将日志记录下来
②通过拦截器,编写一个类去继承HandlerInterceptorAdapter,重写preHandle,postHandle,然后在里面进行日志记录,编写的类加到spring容器里

采用第一种方式:

1、第一步、定义一个注解:

Annotation 注解的作用:

@interface 表示这是一个注解类, 不是interface,是注解类 定义注解用的,是jdk1.5之后加入的,java没有给它新的关键字,所以就用@interface 这么个东西表示了

@Inherited //这个Annotation 可以被继承

@Documented //这个Annotation可以被写入javadoc

@Target:注解的作用目标

@Target(ElementType.TYPE) //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包

@Retention(RetentionPolicy.RUNTIME) //可以用来修饰注解,是注解的注解,称为元注解。

public enum RetentionPolicy {   
    SOURCE, // 编译器处理完Annotation后不存储在class中   
    CLASS, // 编译器把Annotation存储在class中,这是默认值   
    RUNTIME // 编译器把Annotation存储在class中,可以由虚拟机读取,反射需要   
} 

创建一个注解:

default 0 相当于set和get方法,添加一个默认值

/**
 * 系统日志注解
 * 
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoLog {

	/**
	 * 日志内容
	 * 
	 * @return
	 */
	String value() default "";
	 * 日志类型
	 * @return 1:登录日志;2:操作日志;3:访问日志;4:异常日志;5:定时任务;
	int logType() default CommonConstant.LOG_TYPE_2;
	
	 * 操作日志类型
	 * @return (1查询,2添加,3修改,4删除)
	int operateType() default 0;
}

CommonConstant 相关的配置

public interface CommonConstant {

	/**
	 * 正常状态
	 */
	public static final Integer STATUS_NORMAL = 0;
	 * 禁用状态
	public static final Integer STATUS_DISABLE = -1;
	 * 删除标志
	public static final Integer DEL_FLAG_DELETED = 1;
	 * 未删除
	public static final Integer DEL_FLAG_UNDELETED = 0;
	 * 系统日志类型: 登录
	public static final int LOG_TYPE_1 = 1;
	
	 * 系统日志类型: 操作
	public static final int LOG_TYPE_2 = 2;
    /**
     * 系统日志类型: 访问
     */
	public static final int LOG_TYPE_3 = 3;
     * 系统日志类型: 异常
    public static final int LOG_TYPE_4 = 4;
     * 系统日志类型: 定时任务
    public static final int LOG_TYPE_5 = 5;
     * 系统日志类型: 用户管理
    public static final int LOG_TYPE_6 = 6;
     * 系统登陆日志:正常账户密码登录
    public static final int OPERATE_TYPE_LT1_1 = 1;
     * 系统登陆日志:二维码登陆
    public static final int OPERATE_TYPE_LT1_2 = 2;
     * 系统登陆日志:单点登陆
    public static final int OPERATE_TYPE_LT1_3 = 3;
     * 系统登陆日志:登出
    public static final int OPERATE_TYPE_LT1_4 = 4;
     * 系统登陆日志:模拟登陆
    public static final int OPERATE_TYPE_LT1_5 = 5;
	 * 操作日志类型: 查询
	public static final int OPERATE_TYPE_LT2_1 = 1;
	 * 操作日志类型: 添加
	public static final int OPERATE_TYPE_LT2_2 = 2;
	 * 操作日志类型: 更新
	public static final int OPERATE_TYPE_LT2_3 = 3;
	 * 操作日志类型: 删除
	public static final int OPERATE_TYPE_LT2_4 = 4;
	 * 操作日志类型: 导入
	public static final int OPERATE_TYPE_LT2_5 = 5;
	 * 操作日志类型: 导出
	public static final int OPERATE_TYPE_LT2_6 = 6;
     * 访问日志类型: 进入
    public static final int OPERATE_TYPE_LT3_1 = 1;
     * 异常日志类型: 普通操作即代码错误
    public static final int OPERATE_TYPE_LT4_1 = 1;
     * 异常日志类型: 非法操作即越权操作
    public static final int OPERATE_TYPE_LT4_2 = 2;
	public static final String CLIENT_TYPE_PC="0";
	public static final String CLIENT_TYPE_MOBILE="1";
	/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
    public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
    /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
    public static final Integer SC_OK_200 = 200;
    
    /**访问权限认证未通过 510*/
    public static final Integer SC_JEECG_NO_AUTHZ=510;
    /** 登录用户Shiro权限缓存KEY前缀 */
    public static String PREFIX_USER_SHIRO_CACHE  = "shiro:cache:org.jeecg.modules.shiro.authc.ShiroRealm.authorizationCache:";
    /** 登录用户Token令牌缓存KEY前缀 */
    public static final String PREFIX_USER_TOKEN  = "prefix_user_token_";
    /** Token缓存时间:3600秒即一小时 */
    public static final int  TOKEN_EXPIRE_TIME  = 3600;
     *  0:一级菜单
    public static final Integer MENU_TYPE_0  = 0;
   /**
    *  1:子菜单 
    */
    public static final Integer MENU_TYPE_1  = 1;
     *  2:按钮权限
    public static final Integer MENU_TYPE_2  = 2;
    /**通告对象类型(USER:指定用户,ALL:全体用户)*/
    public static final String MSG_TYPE_UESR  = "USER";
    public static final String MSG_TYPE_ALL  = "ALL";
    /**发布状态(0未发布,1已发布,2已撤销)*/
    public static final String NO_SEND  = "0";
    public static final String HAS_SEND  = "1";
    public static final String HAS_CANCLE  = "2";
    /**阅读状态(0未读,1已读)*/
    public static final String HAS_READ_FLAG  = "1";
    public static final String NO_READ_FLAG  = "0";
    /**优先级(L低,M中,H高)*/
    public static final String PRIORITY_L  = "L";
    public static final String PRIORITY_M  = "M";
    public static final String PRIORITY_H  = "H";
     * 短信模板方式  0 .登录模板、1.注册模板、2.忘记密码模板
    public static final String SMS_TPL_TYPE_0  = "0";
    public static final String SMS_TPL_TYPE_1  = "1";
    public static final String SMS_TPL_TYPE_2  = "2";
     * 状态(0无效1有效)
    public static final String STATUS_0 = "0";
    public static final String STATUS_1 = "1";
     * 同步工作流引擎1同步0不同步
    public static final String ACT_SYNC_0 = "0";
    public static final String ACT_SYNC_1 = "1";
     * 消息类型1:通知公告2:系统消息
    public static final String MSG_CATEGORY_1 = "1";
    public static final String MSG_CATEGORY_2 = "2";
     * 是否配置菜单的数据权限 1是0否
    public static final Integer RULE_FLAG_0 = 0;
    public static final Integer RULE_FLAG_1 = 1;
     * 用户状态 0冻结 1正常 2待定
    public static final Integer USER_FREEZE = 0;
    public static final Integer USER_NORMAL = 1;
     * 用户删除标志位 0未删 1已删
    public static final Integer USER_DELETE_NO=0;
    public static final Integer USER_DELETE_YES=1;
    /**字典翻译文本后缀*/
    public static final String DICT_TEXT_SUFFIX = "_dictText";
    public static final String ITEM_DISPLAY = "_display";
     * 表单设计器主表类型
    public static final Integer DESIGN_FORM_TYPE_MAIN = 1;
     * 表单设计器子表表类型
    public static final Integer DESIGN_FORM_TYPE_SUB = 2;
     * 表单设计器URL授权通过
    public static final Integer DESIGN_FORM_URL_STATUS_PASSED = 1;
     * 表单设计器URL授权未通过
    public static final Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2;
     * 表单设计器新增 Flag
    public static final String DESIGN_FORM_URL_TYPE_ADD = "add";
     * 表单设计器修改 Flag
    public static final String DESIGN_FORM_URL_TYPE_EDIT = "edit";
     * 表单设计器详情 Flag
    public static final String DESIGN_FORM_URL_TYPE_DETAIL = "detail";
     * 表单设计器复用数据 Flag
    public static final String DESIGN_FORM_URL_TYPE_REUSE = "reuse";
     * 表单设计器编辑 Flag (已弃用)
    public static final String DESIGN_FORM_URL_TYPE_VIEW = "view";
     * online参数值设置(是:Y, 否:N)
    public static final String ONLINE_PARAM_VAL_IS_TURE = "Y";
    public static final String ONLINE_PARAM_VAL_IS_FALSE = "N";
     * 文件上传类型(本地:local,Minio:minio,阿里云:alioss)
    public static final String UPLOAD_TYPE_LOCAL = "local";
    public static final String UPLOAD_TYPE_MINIO = "minio";
    public static final String UPLOAD_TYPE_OSS = "alioss";
     * 员工身份 (1:普通员工  2:上级)
    public static final Integer USER_IDENTITY_1 = 1;
    public static final Integer USER_IDENTITY_2 = 2;
     * 日期格式
    public static final String TIME_FORMAT_YMD = "yyyy-MM-dd";
    public static final String TIME_FORMAT_YMDHMS = "yyyy-MM-dd HH:mm:ss";
    public static final String TIME_FORMAT_YMDHMSSZ = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
}

2、第二步、编写一个切面

@Aspect 表示这是一个切面

@Component 告诉spring 这是一个bean ,注入

@annotation 获取定义的注解

@Pointcut 切点,

@Pointcut("@annotation(xx.AutoLog)") 表示,使用了这个注解的,就是切入点

@Around的作用

既可以在目标方法之前织入增强动作,也可以在执行目标方法之后织入增强动作;
可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标目标方法的执行;
可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值; 当需要改变目标方法的返回值时,只能使用Around方法;
虽然Around功能强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before、AfterReturing增强方法就可以解决的事情,就没有必要使用Around增强处理了。

ProceedingJoinPoint 环绕通知,主要作用找到程序执行中的可识别的点,当aop的切入点

  1. 环绕通知 ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行,这也是环绕通知和前置、后置通知方法的一个最大区别。
  2. 简单理解,环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的.
/**
 * 系统日志,切面处理类
 *
 */
@Aspect
@Component
public class AutoLogAspect {
    @Autowired
    private ISysLogService sysLogService;

    @Pointcut("@annotation(xx.AutoLog)")
    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;

        //保存日志
        saveSysLog(point, time);

        return result;
    }

    private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        SysLog sysLog = new SysLog();
        AutoLog syslog = method.getAnnotation(AutoLog.class);
        if (syslog != null) {
            //注解上的描述,操作日志内容
            sysLog.setLogContent(syslog.value());
            sysLog.setLogType(syslog.logType());

        }

        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");


        //设置操作类型
        if (sysLog.getLogType() == CommonConstant.LOG_TYPE_2) {
            sysLog.setOperateType(getOperateType(methodName, syslog.operateType()));
        }

        //请求的参数
        Object[] args = joinPoint.getArgs();
        try {
            String params = JSONObject.toJSONString(args);
            sysLog.setRequestParam(params);
        } catch (Exception e) {

        }
        try {
            //获取request
            HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
            //设置IP地址
            sysLog.setIp(IPUtils.getIpAddr(request));
        } catch (Exception e) {

        }
        //获取登录用户信息
        LoginUser sysUser = null;
        try {
            sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        } catch (Exception e) {

        }
        if (sysUser != null) {
            sysLog.setUserId(sysUser.getId());
            sysLog.setUserName(sysUser.getUserName());
            sysLog.setRealName(sysUser.getRealName());
            sysLog.setOrgId(sysUser.getNowOrgId());
            sysLog.setOrgName(sysUser.getNowOrgName());
        }
        //耗时
        sysLog.setCostTime(time);
        sysLog.setCreateTime(new Date());
        //保存系统日志
        sysLogService.save(sysLog);
    }

    /**
     * 获取操作类型
     */
    private int getOperateType(String methodName, int operateType) {
        if (operateType > 0) {
            return operateType;
        }
        if (methodName.startsWith("list")) {
            return CommonConstant.OPERATE_TYPE_LT2_1;
        }
        if (methodName.startsWith("add")) {
            return CommonConstant.OPERATE_TYPE_LT2_2;
        }
        if (methodName.startsWith("edit")) {
            return CommonConstant.OPERATE_TYPE_LT2_3;
        }
        if (methodName.startsWith("delete")) {
            return CommonConstant.OPERATE_TYPE_LT2_4;
        }
        if (methodName.startsWith("import")) {
            return CommonConstant.OPERATE_TYPE_LT2_5;
        }
        if (methodName.startsWith("export")) {
            return CommonConstant.OPERATE_TYPE_LT2_6;
        }
        return CommonConstant.OPERATE_TYPE_LT2_1;
    }

    @AfterThrowing(pointcut = "logPointCut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        SysLog sysLog = new SysLog();

        StackTraceElement[] stackTraceElements = ex.getStackTrace();
        String rootExceptionName = ex.getClass().getName();

        StringBuilder resultContent = new StringBuilder("异常类:" + rootExceptionName);

        int count = 0;
        int maxTrace = 3;
        for (StackTraceElement stackTraceElement : stackTraceElements) {
            if (stackTraceElement.getClassName().contains("com.lingxu") && count < maxTrace) {
                resultContent.append("\n出现于").append(stackTraceElement.getClassName())
                        .append("类中的").append(stackTraceElement.getMethodName())
                        .append("方法中 位于该类文件的第").append(stackTraceElement.getLineNumber())
                        .append("行)");
                count++;
                if (count == maxTrace) {
                    break;
                }
            }
        }


        sysLog.setExceptionContent(resultContent.toString());

        AutoLog syslog = method.getAnnotation(AutoLog.class);


        if (syslog != null) {
            //注解上的描述,操作日志内容
            sysLog.setLogContent(syslog.value() + "出现异常");
            sysLog.setLogType(CommonConstant.LOG_TYPE_4);
        }

        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");


        //设置操作类型
        sysLog.setOperateType(CommonConstant.OPERATE_TYPE_LT4_1);

        //请求的参数
        Object[] args = joinPoint.getArgs();
        try {
            String params = JSONObject.toJSONString(args);
            sysLog.setRequestParam(params);
        } catch (Exception e) {

        }
        try {
            //获取request
            HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
            //设置IP地址
            sysLog.setIp(IPUtils.getIpAddr(request));
        } catch (Exception e) {

        }
        try {
            //获取登录用户信息
            LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
            if (sysUser != null) {
                sysLog.setUserId(sysUser.getId());
                sysLog.setUserName(sysUser.getUserName());
                sysLog.setRealName(sysUser.getRealName());
                sysLog.setOrgId(sysUser.getNowOrgId());
                sysLog.setOrgName(sysUser.getNowOrgName());

            }
        } catch (Exception e) {
        }
        //保存系统日志
        sysLogService.save(sysLog);
    }
}

3、使用自定义注解

可以在controller或者实现类上进行注解的加入

	@AutoLog(value = "sss",logType = CommonConstant.LOG_TYPE_3, operateType = 2)
	@ApiOperation(value="记录查询日志-分页列表查询", notes="记录查询日志-分页列表查询")
	@PostMapping(value = "/queryPage")
	public Result<?> queryPage(@RequestBody SysSelectLog sysSelectLog){
		Page<SysSelectLog> page = new Page<>(sysSelectLog.getPageNo(),sysSelectLog.getPageSize());
		IPage<SysSelectLog> sysSelectLogIPage = sysSelectLogService.queryPage(page,sysSelectLog);
		return Result.ok(sysSelectLogIPage);
	}

到此这篇关于使用Aop的方式实现自动日志记录的文章就介绍到这了,更多相关Aop自动日志记录内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java线程池中的工作线程Worker类源码解析

    Java线程池中的工作线程Worker类源码解析

    这篇文章主要介绍了Java线程池中的工作线程Worker类源码解析,线程池中的工作线程是通过内部类Worker表示的,Worker继承自AbstractQueueSynchronizer,可以实现同步器的功能,需要的朋友可以参考下
    2023-12-12
  • Java超详细教你写一个银行存款系统案例

    Java超详细教你写一个银行存款系统案例

    这篇文章主要介绍了怎么用Java来写一个银行的存款系统,银行存款主要有账号和存款金额两个属性,感兴趣的朋友跟随文章往下看看吧
    2022-03-03
  • SpringBoot+Echarts实现请求后台数据显示饼状图

    SpringBoot+Echarts实现请求后台数据显示饼状图

    这篇文章主要介绍了SpringBoot+Echarts实现请求后台数据显示饼状图,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • 零基础搭建boot+MybatisPlus的详细教程

    零基础搭建boot+MybatisPlus的详细教程

    这篇文章主要介绍了零基础搭建boot+MybatisPlus,首先需要创建数据库表和创建boot项目使用mybatisplus操作数据库,本文通过示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • Java面试题 从源码角度分析HashSet实现原理

    Java面试题 从源码角度分析HashSet实现原理

    这篇文章主要介绍了Java面试题 从源码角度分析HashSet实现原理?,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • Springboot跨域处理的多种方式小结

    Springboot跨域处理的多种方式小结

    当一台服务器资源从另一台服务器(不同 的域名或者端口)请求一个资源或者接口,就会发起一个跨域 HTTP 请求,这篇文章主要介绍了Springboot跨域处理的多种方式小结,需要的朋友可以参考下
    2023-11-11
  • 深入了解Java线程池的原理使用及性能优化

    深入了解Java线程池的原理使用及性能优化

    JAVA线程池是一种管理和复用线程资源的机制,可以提高程序的效率和响应速度。本文将介绍线程池的原理、使用方法和性能优化技巧,帮助读者深入了解和应用JAVA线程池
    2023-04-04
  • SpringBoot项目使用mybatis-plus代码生成的实例详解

    SpringBoot项目使用mybatis-plus代码生成的实例详解

    mybatis-plus是mybatis的增强,不对mybatis做任何改变,涵盖了代码生成,自定义ID生成器,快速实现CRUD,自动分页,逻辑删除等功能。本文就来讲讲SpringBoot项目如何使用mybatis-plus实现代码生成,需要的可以了解一下
    2022-10-10
  • 详解Spring mvc的web.xml配置说明

    详解Spring mvc的web.xml配置说明

    本篇文章主要介绍了Spring mvc的web.xml配置说明,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-02-02
  • gataway断言工作流程源码剖析

    gataway断言工作流程源码剖析

    这篇文章主要为大家介绍了gataway断言工作流程源码剖析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01

最新评论