Java项目中基于AOP注解方式实现接口日志记录

最近要对系统进行改造,要对业务中的一些接口进行记录,包括接口调用时间,参数,操作类型等,最后决定以注解的方式去实现,不会对原来的业务逻辑产生影响。使用AOP和自定义注解是一种非常强大且优雅的方法。通过AOP(Aspect-Oriented Programming,面向切面编程)和自定义注解,我们可以将日志记录逻辑与业务逻辑分离,从而提高代码的可维护性和可扩展性。本文将介绍如何结合AOP和自定义注解来实现接口日志记录、接口耗时等信息,并展示其在实际项目中的应用。

  1. 首先自定义一个注解,自定义注解是Java语言提供的一种元数据(metadata)机制,它允许我们在代码中添加额外的信息和标记。
package com.dev.common.annotation;

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

/**
 * @Version 1.0
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {

    /**
     * 传入需要获取的参数,可在default设置默认值
     */
    String moduleName() default "内容管理";

    /**
     * 操作类型
     */
    String operateType();
}

  1. 定义一个切面类,在AOP切面中根据自定义的注解标记来触发相应的行为。加上@Component注解,@Aspect注解加入配置管理,可以通过Hutool提供的方法获取客户端浏览器、操作系统,使用Hutool需要引入其依赖
<dependency>
   <groupId>cn.hutool</groupId>
   <artifactId>hutool-all</artifactId>
   <version>5.8.0.M2</version>
</dependency>
package com.dev.common.service;

import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.dev.common.annotation.OperationLog;
import com.dev.common.utils.IpUtil;
import com.dev.common.utils.ParamsUtil;
import com.dev.service.CommonService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description 操作日志处理类
 * @Version 1.0
 */
@Aspect
@Component
@Slf4j
public class OperateLogAspect {

    @Resource
    private LogDao logDao;

    /** 计算操作消耗时间 */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    /**
     * 处理请求前执行
     */
    @Before(value = "@annotation(operationLog)")
    public void boBefore(JoinPoint joinPoint, OperationLog operationLog)
    {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     * @param operationLog 注解
     */
    @AfterReturning(pointcut = "@annotation(operationLog)")
    public void doAfterReturning(JoinPoint joinPoint, OperationLog operationLog)
    {
        handleLog(joinPoint,operationLog,null);
    }

    /**
     * 拦截异常操作,方法异常时执行
     *
     * @param joinPoint 切点
     * @param operationLog 注解
     * @param e 异常
     */
    @AfterThrowing(value = "@annotation(operationLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperationLog operationLog, Exception e)
    {
        handleLog(joinPoint, operationLog,e);
    }

    /**
     * 封装日志
     * @param joinPoint
     * @param operationLog
     * @param e
     */
    public void handleLog(final JoinPoint joinPoint,OperationLog operationLog,final Exception e) {

        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
            //获取请求参数
            String reqParam = preHandleParams(joinPoint, request);
            String userIp = IpUtil.getIpAddrPlus(request);
            String uaStr = request.getHeader("User-Agent");
            UserAgent userAgent = UserAgentUtil.parse(uaStr);
            //计算耗时
            long cost = System.currentTimeMillis() - TIME_THREADLOCAL.get();
            Map<String,Object> operLogMap = new HashMap<>();
            operLogMap.put("clientIp",userIp);
            operLogMap.put("requestUrl",request.getRequestURI());
            operLogMap.put("requestParam",reqParam);
            operLogMap.put("clientBrowser", userAgent.getBrowser());
            operLogMap.put("clientSystem", userAgent.getPlatform());
            // 获取自定义注解的参数
            operLogMap.put("moduleName",operationLog.moduleName());
            operLogMap.put("requestDuration",cost);
            // 默认成功
            operLogMap.put("isSuccess",1);
            operLogMap.put("errorMessage","");
            // 失败回填0,错误原因
            if(e != null){
                operLogMap.put("isSuccess",0);
                operLogMap.put("errorMessage",e.getMessage());
            }
            // 插入日志
            logDao.insertLog(operLogMap);
        } catch (Exception exp) {
            exp.printStackTrace();
        } finally {
            TIME_THREADLOCAL.remove();
        }
    }

	/**
     * 获取请求参数
     *
     * @param joinPoint
     * @param request
     * @return 参数字符串
     */
    private String preHandleParams(JoinPoint joinPoint, HttpServletRequest request) {
        // 签名
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Annotation[] annotations = targetMethod.getAnnotations();
        Map<String, Object> params = new HashMap<>();
        for (int i = 0; i < annotations.length; i++) {
            // 获取被注解方法接收的参数
            if (annotations[i].annotationType().equals(OperationLog.class)) {
                params = requestParamMap(request);
                break;
            }
        }
        return params.toString();
    }

	/**
	 * 从请求中获取参数信息并转成 Map
	 * @param request
	 * @return Map
	 */
	@SuppressWarnings("unchecked")
	public static Map<String, Object> requestParamMap(HttpServletRequest request) {
		java.util.Iterator iter = request.getParameterMap().entrySet().iterator();
		Map<String, Object> map = new LinkedHashMap<String, Object>();
		while (iter.hasNext()) {
			Map.Entry entry = (Map.Entry) iter.next();
			String key = StringUtil.strFiltrate(entry.getKey().toString());
			String[] checkboxValues = request.getParameterValues(key);
			String value = StringUtil.nullToEmpty(request.getParameter(key));
			// 对&符号转义
			value = value.replace("&amp;", "&");
			if (checkboxValues != null && checkboxValues.length > 1) {
				String checkboxV = "";
				for (int i = 0; i < checkboxValues.length; i++) {
					checkboxV += checkboxValues[i] + ",";
				}
				if (!"".equals(checkboxV)) {
					value = checkboxV.substring(0, checkboxV.length() - 1);
				}
			}
			map.put(key, StringUtil.strFiltrate(value));
		}
		return map;
	}


}

如果是SpringMVC项目还需在配置文件中需要开启AspectJ注解支持

<?xml encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:mvc="http://www.springframework.org/schema/mvc"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xmlns:p="http://www.springframework.org/schema/p"
	   xmlns:util="http://www.springframework.org/schema/util" 	
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/util/spring-util-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	
	
	<aop:aspectj-autoproxy/>

</beans>
  1. 在你的方法接口上加上自定义注解,传入一些参数,就可以使用了.
   @GetMapping("/hello")
    @OperationLog(moduleName = "文章管理",operateType = "插入")
    public Result Hello(HttpServletRequest request){
        HashMap<Object, Object> result = new HashMap<>();
        result.put("code",200);
        result.put("msg","success");
        return Result.success(result);
    }

   @GetMapping("/test")
    @OperationLog(moduleName = "测试管理",operateType = "更新")
    public Result Hello(HttpServletRequest request){
        HashMap<Object, Object> result = new HashMap<>();
        result.put("code",200);
        result.put("msg","success");
        return Result.success(result);
    }

以上就是使用AOP注解记录接口日志的基本用法,后面再写接口只需要添加上这个注解就行了,是不是很方便呢!希望本文对你理解AOP、自定义注解以及接口日志记录有所帮助。

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 24
    评论
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值