一、需求
将后台管理系统中的增、删、改相关接口的操作日志记录到数据库表中
二、分析
项目中的增删改相关的方法很多,所以一个一个处理很繁琐,可以把这部分记录操作日志的重复性的逻辑代码抽取出来定义在一个通知方法中,通过AOP面向切面编程的方式,在不改动原始功能的基础上来对原始的功能进行增强。
三、AOP解决
方式一
1、引入AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、定义Aspect类
在类上声明@Aspect和@Component注解。
@Component
:将该类标记为 Spring 容器中的一个 Bean,使其能够被 Spring 自动扫描和管理。
@Aspect
:声明该类是一个切面类,表示它包含横切关注点(如日志记录、事务管理等)。AOP 框架会根据这个注解来识别并应用该类中的通知(Advice)。
@Component
@Aspect
public class OperatorLogAspect {
}
}
3、@Around
@Around
:定义一个环绕通知(Around Advice),表示在目标方法执行前后都可以插入逻辑。
execution(* com.zxy.controller.*.add*(..))
:匹配 com.zxy.controller
包下所有以 add
开头的方法,无论返回值类型和参数列表如何。更新和删除同理。
@Component
@Aspect
public class OperatorLogAspect {
@Around("execution(* com.zxy.controller.*.add*(..))" +
"|| execution(* com.zxy.controller.*.update*(..))" +
"|| execution(* com.zxy.controller.*.delete*(..))")
}
4、环绕通知方法
ProceedingJoinPoint
:这是 AOP 中的一个接口,表示当前正在执行的连接点(即被拦截的方法)。ProceedingJoinPoint
提供了对目标方法的访问,允许你在方法执行前后插入逻辑,并且可以通过proceed
()
方法继续执行目标方法。
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
}
5、记录操作日志的逻辑
joinPoint.proceed()
:调用目标方法并返回其结果。
result.getClass().getName()
:获取目标方法的返回值类型,并将其作为类名记录到日志中。
joinPoint.getSignature().getName()
:获取目标方法的名称,并将其记录到日志中。
joinPoint.getArgs()
:获取目标方法的参数数组,并将其转换为字符串形式,记录到日志中。
@Autowired
private LogMapper logMapper;
//计算花费时间
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long costTime = endTime - startTime;
Log log = new Log();
//当前时间
log.setOperateTime(LocalDateTime.now().toString());
//类名
log.setClassName(result.getClass().getName());
//方法名
log.setMethodName(joinPoint.getSignature().getName());
//方法参数
Object[] args = joinPoint.getArgs();
log.setMethodParams(Arrays.toString(args));
//返回值
log.setReturnValue(result.toString());
//花费时间
log.setCostTime((int) costTime);
//操作人ID
log.setOperateEmpId((Integer) UserThreadLocal.getUser().get("id"));
//操作人名称
log.setOperateEmpName((String) UserThreadLocal.getUser().get("name"));
logMapper.insert(log);
UserThreadLocal.removeUser();
return result;
方式二
1、自定义注解@MyAnnotation
自定义注解,用于标识哪些方法需要记录日志。
@Target(ElementType.METHOD)
:表示该注解只能用于方法上。
@Retention(RetentionPolicy.RUNTIME)
:表示该注解在运行时可用,这样 AOP 框架可以在运行时读取注解信息。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
2、定义AOP记录日志的切面类
@annotation()
:这是基于注解的切入点表达式,表示只拦截那些被 自定义
注解标注的方法。
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperateLogMapper operateLogMapper;
// 环绕通知
@Around("@annotation(com.zxy.util.MyAnnotation)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
..........
}
3、在需要记录日志的Controller层的方法上,加上自定义的注解
显然第二种方式比第一种方式操作更繁琐。
execution切入点表达式
根据我们所指定的方法的描述信息来匹配切入点方法,这种方式也是最为常用的一种方式
如果我们要匹配的切入点方法的方法名不规则,或者有一些比较特殊的需求,通过execution切入点表达式描述比较繁琐。
annotation 切入点表达式
基于注解的方式来匹配切入点方法。这种方式虽然多一步操作,我们需要自定义一个注解,但是相对来比较灵活。我们需要匹配哪个方法,就在方法上加上对应的注解就可以了