基于AOP实现操作日志记录
文章目录
前言
在springboot项目中,往往需要在用户完成某些操作(例如:增,删,改)时,能够将相关操作信息记录下来,形成一个操作日志。
操作日志信息包含:
操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长
所记录的日志信息包括当前接口的操作人是谁操作的,什么时间点操作的,以及访问的是哪个类当中的哪个方法,在访问这个方法的时候传入进来的参数是什么,访问这个方法最终拿到的返回值是什么,以及整个接口方法的运行时长是多长时间。
接下来将介绍如何利用AOP来实现操作日志的生成。
一、AOP
1.介绍
- AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。
对于一个项目来讲,里面会包含很多的业务模块,每个业务模块又包含很多增删改查的方法,如果如果有一些功能需要我们要在每一个模块下的业务方法中,去实现,就会让程序员的工作变得非常繁琐。
而AOP面向方法编程,就可以做到在不改动这些原始方法的基础上,针对特定的方法进行功能的增强。
AOP的作用:在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性: 解耦)
比如说,我们要想完成统计各个业务方法执行耗时的需求,我们只需要定义一个模板方法,将记录方法执行耗时这一部分公共的逻辑代码,定义在模板方法当中,在这个方法开始运行之前,来记录这个方法运行的开始时间,在方法结束运行的时候,再来记录方法运行的结束时间,中间就来运行原始的业务方法,最后用结束时间减去开始时间即是执行这个方法所消耗的时间。
2.AOP核心概念
下面介绍一下AOP的几个核心概念:
1. 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
连接点指的是可以被aop控制的方法。例如:入门程序当中所有的业务方法都是可以被aop控制的方法。
在SpringAOP提供的JoinPoint当中,封装了连接点方法在执行时的相关信息。
例:
//环绕通知
@Around("@annotation(com.ly.springbootdemotlias.anno.Log)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//获取目标类名
String name = pjp.getTarget().getClass().getName();
log.info("目标类名:{}",name);
//目标方法名
String methodName = pjp.getSignature().getName();
log.info("目标方法名:{}",methodName);
//获取方法执行时需要的参数
Object[] args = pjp.getArgs();
log.info("目标方法参数:{}", Arrays.toString(args));
//执行原始方法
Object returnValue = pjp.proceed();
return returnValue;
}
2. 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
但是在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑,也就是共性的功能。
Spring中AOP的通知类型:
- @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行
- @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
- @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行
注:只有@Around类型的通知需要用joinPoint.proceed();方法来调用连接点方法,且返回类型为Object
3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
在通知当中,我们所定义的共性功能到底要应用在哪些方法上?此时就涉及到了切入点pointcut概念。切入点指的是匹配连接点的条件。通知仅会在切入点方法运行时才会被应用。
在aop的开发当中,我们通常会通过一个切入点表达式来描述切入点:
execution
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
excution切入点表达式的语法规则:
- 方法的访问修饰符可以省略
- 返回值可以使用
*
号代替(任意返回值类型) - 包名可以使用
*
号代替,代表任意包(一层包使用一个*
) - 使用
..
配置包名,标识此包以及此包下的所有子包 - 类名可以使用
*
号代替,标识任意类 - 方法名可以使用
*
号代替,表示任意方法 - 可以使用
*
配置参数,一个任意类型的参数 - 可以使用
..
配置参数,任意个任意类型的参数 - 根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。
@annotation
如果我们要匹配多个无规则的方法,比如:list()和 delete()这两个方法。这个时候我们基于execution这种切入点表达式来描述就不是很方便了。而在之前我们是将两个切入点表达式组合在了一起完成的需求,这个是比较繁琐的。
我们可以借助于另一种切入点表达式annotation来描述这一类的切入点,从而来简化切入点表达式的书写。
实现步骤:
- 编写自定义注解
- 在业务类要做为连接点的方法上添加自定义注解
创建自定义注解
例:
@Retention(RetentionPolicy.RUNTIME)//运行时有效
@Target(ElementType.METHOD)//作用在方法上
public @interface Log {
}
在通知方法注解中用@annotation(注解全类名)声明这个通知方法的作用注解
例:
@Around("@annotation(com.ly.springbootdemotlias.anno.Log)")
最后在需要做为连接点的方法上添加自定义注解即可。
4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
当通知和切入点结合在一起,就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪个原始方法,在什么时候执行什么样的操作。
切面所在的类,我们一般称为切面类(被@Aspect注解标识的类)
(注:切面类还需要加上@Component注解)
5. 目标对象:Target,通知所应用的对象
目标对象指的就是通知所应用的对象,我们就称之为目标对象。
二、基于AOP实现操作日志记录
1.准备工作
首先需要导入AOP依赖
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后需要创建日志记录数据库和实体类还有在Mapper层实现插入数据方法
实体类
//操作日志实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id; //主键ID
private Integer operateUser; //操作人ID
private LocalDateTime operateTime; //操作时间
private String className; //操作类名
private String methodName; //操作方法名
private String methodParams; //操作方法参数
private String returnValue; //操作方法返回值
private Long costTime; //操作耗时
}
Mapper接口
@Mapper
public interface OperateLogMapper {
//插入日志数据
@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
public void insert(OperateLog log);
}
2.创建自定义注解和切面类
因为我们要记录的操作日志是对于数据的增,删,改所以采用@annotation来指定要为连接点的方法。
而且在涉及到消耗时间的字段,所以需要采用@Around通知类型
自定义注解类
@Retention(RetentionPolicy.RUNTIME)//运行时有效
@Target(ElementType.METHOD)//作用在方法上
public @interface Log {
}
切面类
@Aspect
@Component
public class LogAsper {
@Autowired
private OperateLogMapper operateLogMapper;
@Autowired
private HttpServletRequest request;
@Around("@annotation(com.ly.springbootdemotlias.anno.Log)")//匹配加了Log注解的方法
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
//方法开始前的操作
//执行目标方法
Object result = joinPoint.proceed();
//方法结束后的操作
return result;
}
}
3.实现日志记录
首先需要需要记录的操作接口方法前加上@Log注解
接下来在切面类中实现操作日志记录:
@Aspect
@Component
public class LogAsper {
@Autowired
private OperateLogMapper operateLogMapper;
@Autowired
private HttpServletRequest request;
@Around("@annotation(com.ly.springbootdemotlias.anno.Log)")//匹配加了Log注解的方法
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
OperateLog operateLog = new OperateLog();
//获取操作人id,通过获取并解析jwt令牌
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJwt((jwt));
Integer operateUser =(Integer) claims.get("id");
operateLog.setOperateUser(operateUser);
//获取操作时间
operateLog.setOperateTime(LocalDateTime.now());
//获取操作类名
operateLog.setClassName(joinPoint.getTarget().getClass().getName());
//获取操作方法名
operateLog.setMethodName(joinPoint.getSignature().getName());
//获取操作方法参数
operateLog.setMethodParams(Arrays.toString(joinPoint.getArgs()));
//获取开始时间
long begin = System.currentTimeMillis();
//获取操作方法返回值
Object result = joinPoint.proceed();
String returnValue = JSONObject.toJSONString(result);
operateLog.setReturnValue(returnValue.toString());
//获取操作结束时间
long end = System.currentTimeMillis();
//获取操作耗时
operateLog.setCostTime((end-begin));
operateLogMapper.insert(operateLog);
return result;
}
}
总结
以上就是对于Spring项目中AOP的使用介绍,并且基于AOP实现了操作日志记录的功能,希望本文能给大家带来帮助。
标签:Springboot,切入点,AOP,注解,日志,操作,方法 From: https://blog.csdn.net/ASL2915/article/details/140117927