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