【Springboot】基于AOP实现操作日志记录

基于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切入点表达式的语法规则:

  1. 方法的访问修饰符可以省略
  2. 返回值可以使用*号代替(任意返回值类型)
  3. 包名可以使用*号代替,代表任意包(一层包使用一个*
  4. 使用..配置包名,标识此包以及此包下的所有子包
  5. 类名可以使用*号代替,标识任意类
  6. 方法名可以使用*号代替,表示任意方法
  7. 可以使用 * 配置参数,一个任意类型的参数
  8. 可以使用.. 配置参数,任意个任意类型的参数
  9. 根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。

    @annotation
    ​  如果我们要匹配多个无规则的方法,比如:list()和 delete()这两个方法。这个时候我们基于execution这种切入点表达式来描述就不是很方便了。而在之前我们是将两个切入点表达式组合在了一起完成的需求,这个是比较繁琐的。
    ​  我们可以借助于另一种切入点表达式annotation来描述这一类的切入点,从而来简化切入点表达式的书写。

实现步骤:

  1. 编写自定义注解
  2. 在业务类要做为连接点的方法上添加自定义注解

创建自定义注解
例:


@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实现了操作日志记录的功能,希望本文能给大家带来帮助。

  • 33
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值