JAVA通过AOP自定义注解记录日志
JAVA通过AOP自定义注解记录日志
- 背景
- 一、自定义注解
- 二、定义一个切面
- 三、记录日志的实体类
- 四、使用该注解
背景
需求:系统的操作日志、审计日志。在日常的管理还是维护中都会起到很大的作用。
解决办法:可以在需要的方法中对日志进行保存操作,但是对业务代码入侵性大。
或者使用切面针对控制类进行处理,但是灵活度不高。因此决定使用自定义注解 + 切面来针对方法进行日志记录。
目前日志主要记录的有三方面:
- 请求的入参,出参
- 关于业务上的操作
- 异常日常日志的打印
一、自定义注解
创建自定义注解 @AuditLog
:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuditLog {// 1.操作描述String action() default "";// 2.操作类型(增删改查)OperateEnum type() default OperateEnum.MODIFY;// 3.是否记录参数boolean isRecord() default true;
}
操作类型枚举类:新增、删除、修改、查询。
public enum OperateEnum {ADD,DELETE,MODIFY,SAVE_OR_MODIFY,SELECT;
}
二、定义一个切面
创建一个切面类AuditAspect
,用于捕获带有@AuditLog
注解的方法调用并记录日志:
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;/*** 日志记录切面*/
@Slf4j
@Aspect
@Component
public class AuditAspect {@Pointcut(value = "@annotation(com.bocloud.devops.eaas.api.log.AuditLog)")public void logMethodPointCut() {}@Around("logMethodPointCut()")public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {UserLogRecordDO userLogRecordDO = bulidRequestParams(point);//先执行业务try {Object result = point.proceed();if (result != null) {userLogRecordDO.setResultMsg(JSONUtil.toJsonStr(result));}return result;} catch (BusinessException e) {userLogRecordDO.setResultCode(e.getCode());userLogRecordDO.setResultMsg(e.getMessage());throw e;} catch (Exception e) {userLogRecordDO.setResultCode("");userLogRecordDO.setResultMsg("后端未知异常");throw e;} finally {//操作日志入库try {//XXXService.save(userLogRecordDO);} catch (Exception e) {log.warn("记录用户操作异常:{}", e.getMessage());}}}/*** 记录方法和方法入参* @param point* @return*/protected UserLogRecordDO bulidRequestParams(ProceedingJoinPoint point) {MethodSignature methodSignature = (MethodSignature) point.getSignature();Method method = methodSignature.getMethod();AuditLog annotation = method.getAnnotation(AuditLog.class);UserLogRecordDO userLogRecordDO = new UserLogRecordDO();userLogRecordDO.setAction(annotation.action());userLogRecordDO.setType(annotation.type().name());userLogRecordDO.setMethodName(method.getDeclaringClass().getSimpleName() + "." + method.getName());try {// 处理入参Parameter[] parameters = methodSignature.getMethod().getParameters();HashMap<String, Object> paramMap = new HashMap<>();Object[] args = point.getArgs();for (int i = 0; i < parameters.length; i++) {AuditLog auditLog = parameters[i].getAnnotation(AuditLog.class);if (auditLog != null) {continue;}Class<?> type = parameters[i].getType();if (ServletResponse.class.isAssignableFrom(type) || ServletRequest.class.isAssignableFrom(type)) {continue;}String name = parameters[i].getName();paramMap.put(name, args[i]);}userLogRecordDO.setMethodParams(JSONUtil.toJsonStr(paramMap));return userLogRecordDO;} catch (Exception e) {log.warn("构建入参异常:{}", e.getMessage());}return userLogRecordDO;}
}
-
logMethodPointCut(): 这个方法定义了一个切入点(pointcut),它使用@annotation注解来匹配带有com.bocloud.devops.eaas.api.log.AuditLog注解的方法。这意味着切面将在这些被标记为@AuditLog的方法执行前后生效。
-
recordSysLog(ProceedingJoinPoint point): 这是一个环绕通知方法,用于包围切入点方法的执行。它负责记录系统日志和审计信息。具体的操作包括:
- 构建请求参数并创建一个UserLogRecordDO对象,其中包括操作描述、操作类型、方法名等信息。
- 调用point.proceed()来执行切入点方法,捕获方法的执行结果,将结果信息记录到UserLogRecordDO中。
- 处理可能抛出的BusinessException异常,记录异常信息。
- 在finally块中尝试将操作日志入库,但在此示例中,入库的部分被注释掉。
-
bulidRequestParams(ProceedingJoinPoint point): 这个方法用于构建方法的参数信息,并将其记录到UserLogRecordDO对象中。它包括方法名、操作描述、操作类型以及方法的入参信息。此方法通过反射分析方法参数和参数上的AuditLog注解,以构建参数信息的JSON表示。
此切面的主要目的是在标记了@AuditLog注解的方法执行前后记录操作日志信息。在实际应用中,您需要确保数据库操作以及记录日志的部分(在finally块中)按照实际需求进行配置。此示例代码中的入库部分被注释掉,您需要根据自己的需求实现相应的数据持久化逻辑。
三、记录日志的实体类
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.Data;/*** @program: eaas-center* @description:* @author: yanchao* @create: 2023-10-10 23:09**/
@Data
@ApiOperation("操作日志记录表")
public class UserLogRecordDO {@ApiModelProperty("主键")private String id;@ApiModelProperty("行为描述")private String action;@ApiModelProperty("执行方法")private String methodName;@ApiModelProperty("执行入参")private String methodParams;@ApiModelProperty("操作类型")private String type;@ApiModelProperty("响应编码")private String resultCode;@ApiModelProperty("结果描述")private String resultMsg;}
四、使用该注解
/*** 通过id查询中心维护表** @param id 主键* @return 单条数据*/@GetMapping("{id}")@AuditLog(action = "通过id查询中心维护表", type = OperateEnum.SELECT)public Result<EaasCentralMaintainQueryDetailRes> detail(@PathVariable("id") Integer id{return Result.success(this.eaasCentralMaintainService.detail(id));}
这样即可在数据库查看相应的操作日志记录。