Spring AOP的实践:在你的系统中记录用户的操作日志

11 篇文章 0 订阅
1 篇文章 0 订阅

引言

当你在开发一个后台管理系统时,突然遇到需要记录用户操作日志的需求,你会采取什么样的方法?解决此问题的方法有很多种,如:在每个地方都写一遍打印日志的逻辑(这显然是不明智的)、使用代理模式(AOP底层原理)…

本文将带领你使用较理想的解决方案处理这个需求——使用AspectJ提供的AOP库实现日志的记录,并配合Java的Annotation注解来实现在控制器上记录日志。

AOP介绍

在开始前,我将简单介绍AOP的一些概念,如果你对这些概念完全陌生,建议先上网了解相关概念,再继续阅读。

AOP,面向切面编程,即Aspect Oriented Programming。

  • Aspect:切面,类似于Java中的类声明,是对系统中横跨多个类的关注点进行模块化封装

  • Joinpoint:连接点,程序执行过程中的一个“点”,例如:方法的执行或异常的处理。在Spring AOP中,一个连接点总是代表一个方法的执行。

  • Pointcut:切入点,即一组连接点的集合;

  • Advice:通知,定义了将会织入到 Joinpoint 的具体逻辑,通过@Before、@After、@Around等注解来区别在JointPoint之前、之后等位置执行代码

  • Weaving:织入,织入指的是将Advice连接到Pointcut指定的Joinpoint处的过程

  • Interceptor:拦截器,是一种实现Advice(通知)的方式;

  • Target:目标对象,即符合切入点所指定的条件被织入Advice的对象。

以上术语,你可能会感到些许困惑,不过没关系,我们开始进入到正式的Coding,一步一步实现功能后,再回来此部分看这些概念,就会豁然开朗。

Coding——开始实现日志模块

引入依赖

以SpringBoot为例,首先我们从Maven中央仓库引入AOP所需的依赖。在你的pom.xml中添加以下代码:

		<!-- SpringBoot AOP依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

定义注解

定义注解目的在于:之后需要打印日志的地方,只需要@Log(“模块名称”,“业务类型”)即可。


我们使用IDEA右键->New->Java Class->Annotation,文件名为Log.java。代码片段如下:


@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {

    /**
     * 模块名称
     */
    public String moduleName() default "";

    /**
     * 业务类型
     */
    public int businessType() default BusinessType.OTHER; 
    //此处业务类型可以自己定义,完整代码可参考文末本仓库Github
}

在Log注解中,我们定义了注解能接收的两个参数:模块名称、业务类型。并将Target目标指定为METHOD方法,即方法注解。

@Retention(RetentionPolicy.RUNTIME)表明此注解不仅被保存到class文件中,在JVM加载class文件之后,此注解仍然存在。

编写切面类

日志切面的核心代码,这个类中的代码将动态地注入到注解所在方法的横截面中,并实现日志的记录。


@Component
@Aspect
public class LogAspect {

    @Pointcut("@annotation(com.yeliheng.blogcommon.annotation.Log)")
    public void pointcut() {}

    @AfterReturning(pointcut = "pointcut()", returning = "jsonResponse")
    public void doReturningLogRecord(JoinPoint joinPoint, Object jsonResponse) {
        recordLog(joinPoint,null,jsonResponse);
    }

    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void doThrowingLogRecord(JoinPoint joinPoint, ApiException e) {
        recordLog(joinPoint,e,null);
    }

    private void recordLog(JoinPoint joinPoint, ApiException e, Object jsonResponse) {
        Log log = getAnnotation(joinPoint);
        if(log == null) {
            return;
        }
        //TODO: 记录日志的具体逻辑 如:获取各种信息,写入数据库等操作...

    }

    /**
     * 通过反射获取注解
     */
    private Log getAnnotation(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if(method != null) {
            return method.getAnnotation(Log.class);
        }
        return null;
    }
   
}

要实现切面类,我们需要在类前加入两个注解:@Component,@Aspect,以供Spring IoC容器进行管理。Spring的IoC容器读取到Aspect注解时,会自动查找带有@Aspect的Bean,然后根据@AfterReturning、@AfterThrowing等拦截器注解把AOP注入到特定的Bean中。

拦截器注解说明:

  • @AfterReturning: 当方法正常返回时会被此拦截器拦截。
  • @AfterThrowing: 当方法运行过程中有异常抛出时,会被此拦截器拦截。

在doReturningLogRecord和doThrowingLogRecord,我们都传入一个joinPoint,并在recordLog方法中通过joinPoint获取我们定义的自定义注解——@Log,并可以通过反射从连接点获取到方法名称和类名。

弄清楚了拦截器方法的作用,我们就可以在recordLog方法中编写具体的记录日志的逻辑。如:记录操作时间,操作人,操作地点等,并把这些信息写入数据库中。

开始使用

编写完切面类后,不出意外的话你的自定义注解的日志已经可以正常使用并且记录用户日志了。但是我们要如何使用它呢?


    @Log(moduleName = "新增用户",businessType = BusinessType.INSERT)
    @PostMapping
    public CommonResponse<Object> add(@Validated @RequestBody User user) {
        userService.insertUser(user);
        return CommonResponse.success();
    }

使用的方法非常简单,只需要在对应的控制器方法上,加上@Log(“模块名称”,“业务类型”),JVM就会通过自身的动态代理功能将对应的代码编译成字节码并织入到对应的横截面中。这就是AOP面向切面编程的最佳实践。

总结

本文介绍如何使用Spring AOP实现对用户操作日志的记录,如需深入了解Spring AOP运行原理,请前往Spring官网,查阅Spring手册或通过源代码进行深入分析。

完整代码参见:Github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值