目录
前言
在Java中,使用AOP(面向切面编程)来记录日志是一种常见且高效的方法。以下是一个详细的步骤指南,展示了如何在Java项目中使用Spring AOP来记录日志。
概述
AOP(Aspect-Oriented Programming)是一种编程范式,旨在提高模块化,允许开发者将横切关注点(如日志记录、事务管理、权限控制等)与业务逻辑代码分离。在Spring框架中,AOP是通过动态代理实现的。
引入依赖
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 日志库,如Logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
创建自定义注解
为了更灵活地控制哪些方法需要记录日志,可以创建一个自定义注解。这使得你可以通过简单的注解标记需要记录日志的方法或类。
代码如下:
/**
* <p>
* 自定义操作日志注解
* </p>
*
* @custom.date 2024/12/30 18:04
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {
/**
* <p>
* 一级模块
* </p>
*
* @return 一级模块
* @custom.date 2024/12/30 18:04
*/
String oneModule();
/**
* <p>
* 二级模块
* </p>
*
* @return 二级模块
* @custom.date 2024/12/30 18:04
*/
String twoModule() default "";
/**
* <p>
* 操作类型
* </p>
*
* @return 操作类型
* @custom.date 2024/12/30 18:04
*/
String type();
/**
* <p>
* 操作说明
* </p>
*
* @return 操作说明
* @custom.date 2024/12/30 18:04
*/
String desc() default "";
}
编写AOP切面
接下来,你需要编写一个AOP切面来拦截带有`@LogOperation`注解的方法,并在这些方法执行前后记录日志。
代码如下:
/**
* <p>
* 操作日志、接口调用日志、异常日志切面
* </p>
*
* @custom.date 2024/12/30 18:04
*/
@Slf4j
@Aspect
@Component
@Order(value = OrderConstant.AOP_LOG)
public class LogAspect {
/**
* 操作日志服务类
*/
@Resource
private IOaLogSysOperationService oaLogSysOperationService;
/**
* 异常日志服务类
*/
@Resource
private IOaLogExceptionService oaLogExceptionService;
/**
* <p>
* 操作日志切入点
* </p>
*
* @custom.date 2024/12/30 18:04
*/
@Pointcut("@annotation(com.example.config.annotation.OperateLog)")
public void operateLogPointCut() {
}
/**
* <p>
* 接口调用日志切入点
* </p>
*
* @custom.date 2024/12/30 18:04
*/
/* @Pointcut("@annotation(com.example.config.annotation.ApiCallLog)")
public void apiCallLog() {
}*/
/**
* <p>
* 异常日志切入点
* </p>
*
* @custom.date 2024/12/30 18:04
*/
@Pointcut("execution(* com.example.controller..*.*(..))")
public void exceptionLogPointCut() {
}
/**
* <p>
* 记录用户操作日志
* </p>
*
* @param joinPoint 切入点
* @custom.date 2024/12/30 18:04
*/
@Before(value = "operateLogPointCut()")
public void saveOperateLog(JoinPoint joinPoint) {
HttpServletRequest request = ContextUtils.getRequest();
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = className + "#" + method.getName();
// 操作日志
OaLogSysOperation.OaLogSysOperationBuilder builder = OaLogSysOperation.builder();
// 获取操作
OperateLog operateLog = method.getAnnotation(OperateLog.class);
if (operateLog != null) {
String oneModule = operateLog.oneModule();
String twoModule = operateLog.twoModule();
String operateType = operateLog.type();
String operateDesc = operateLog.desc();
// 操作模块
builder.oneLevelModule(oneModule);
builder.twoLevelModule(twoModule);
// 操作类型
builder.operType(operateType);
// 操作描述
builder.operDescription(operateDesc);
}
// 请求参数
Object[] args = joinPoint.getArgs();
// 转换请求参数
Map<String, Object> reqParamMap = request != null ? MapUtils.convertParamMap(request.getParameterMap()) : Maps.newHashMap();
if (reqParamMap.isEmpty()) {
if (ArrayUtils.isNotEmpty(args)) {
// 遍历方法参数
for (int i = 0; i < args.length; i++) {
Parameter[] parameters = method.getParameters();
String paramName = parameters[i].getName();
Object paramValue = args[i];
reqParamMap.put(paramName, paramValue != null ? JSON.toJSON(paramValue) : null);
}
}
}
builder.reqParam(reqParamMap.isEmpty() ? "" : JSON.toJSONString(reqParamMap));
builder.operUserId(SpringSecurityUtils.getCurrentActiveUser() == null ? null : SpringSecurityUtils.getCurrentActiveUser().getUserId());
builder.operUserName(SpringSecurityUtils.getCurrentActiveUser() == null ? null : SpringSecurityUtils.getCurrentActiveUser().getUserName());
builder.operMethod(methodName);
builder.uri(Objects.requireNonNull(request).getRequestURI());
builder.sourceIp(AccessObjectUtils.getClientAddress(request));
builder.targetIp(NetUtils.getLocalIp());
builder.time(new Date());
builder.id(KeyUtils.getLongKey());
this.oaLogSysOperationService.save(builder.build());
}
/**
* <p>
* 记录接口调用日志
* </p>
*
* @param joinPoint 切入点
* @return 方法返回值
* @throws Throwable 异常
* @custom.date 2024/12/30 18:04
*/
/* @Around(value = "apiCallLog()")
public Object saveApiCallLog(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ContextUtils.getRequest();
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 操作日志
DapLogInterfaceCall.DapLogInterfaceCallBuilder builder = DapLogInterfaceCall.builder();
// 获取操作
ApiCallLog apiCallLog = method.getAnnotation(ApiCallLog.class);
if (apiCallLog != null) {
String name = apiCallLog.name();
String version = apiCallLog.version();
// 接口名称
builder.interfaceName(name);
// 接口版本
builder.interfaceVersion(version);