在实际开发中我们需要对一些方法的操作进行日志的记录,比如登陆、修改密码、删除等操作记录日志,注解标记只记录需要监控的地方。

一、添加相关依赖和配置

<!--aop-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--jpa-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--lombok-->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>
<!--mysql-->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>

在application.properties文件中添加JPA和Mysql相关配置

#mysql连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mydb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false&rewriteBatchedStatements=true
# JPA相关配置
spring.jpa.hibernate.ddl-auto=update
# 显示sql语句
spring.jpa.show-sql=true

二、自定义日志注解

我们创建一个自定义日志注解和操作类型的枚举类,自定义日志注解包含操作类型和日志描述信息。

示例代码如下:

import lombok.Getter;

/**
 * 操作类型枚举
 * 分别对应增加、删除、修改、查询、其他
 */
@Getter
public enum OperationEnum {
    ADD(1, "新增"),
    DELETE(2, "删除"),
    UPDATE(3, "修改"),
    FIND(4, "查询"),
    OTHER(5, "其他");

    private int code;
    private String msg;

    OperationEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}
package com.example.myspringboot.annotation;

import com.example.myspringboot.bean.OperationEnum;

import java.lang.annotation.*;

/**
 * 自定义日志注解
 */
@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface MyLog {

    /**
     * 日志描述信息
     */
    String desc() default "";

    /**
     * 操作类型
     */
    OperationEnum operation() default OperationEnum.OTHER;
}

三、创建日志记录实体

我们使用JPA创建日志实体类,自动在数据库中创建数据表。

示例代码如下:

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.Date;

/**
 * @author qx
 * @date 2023/06/21
 * @desc 操作日志实体
 */
@Entity
@Table(name = "t_operation_log")
@Getter
@Setter
public class OperationLog {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * 日志描述消息
     */
    private String content;

    /**
     * 日志操作类型
     */
    private String operationType;

    /**
     * 日志创建时间
     */
    private Date createDate;
}

接下来创建日志数据操作层。

示例代码如下:

package com.example.myspringboot.repository;

import com.example.myspringboot.bean.OperationLog;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OperationLogRepository extends JpaRepository<OperationLog, Long> {
}

四、定义切面

我们定义一个切面类,当在方法上添加了@MyLog注解时,切面就会自动织入,切面中的逻辑把日志的信息存储到数据库等存储介质。

示例代码如下:

import com.example.myspringboot.annotation.MyLog;
import com.example.myspringboot.bean.OperationLog;
import com.example.myspringboot.repository.OperationLogRepository;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * @author qx
 * @date 2023/06/21
 * @desc 自定义日志切面
 */
@Aspect
@Component
@Slf4j
public class MyLogAspect {

    @Autowired
    private OperationLogRepository operationLogRepository;

    /**
     * 切点
     */
    @Pointcut("@annotation(com.example.myspringboot.annotation.MyLog)")
    private void myLogPointCut() {
    }

    @Around("myLogPointCut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        MyLog myLog = method.getAnnotation(MyLog.class);

        // 设置日志记录
        OperationLog operationLog = new OperationLog();
        operationLog.setContent(myLog.desc());
        operationLog.setOperationType(myLog.operation().getMsg());
        operationLog.setCreateDate(new Date());

        // 存储日志记录到数据库
        operationLogRepository.save(operationLog);
    }
}

五、定义测试类

我们在增加和删除的方法上加上我们自定义的日志注解,当我们访问这些方法的时候把注解中的信息存储到数据库。

示例代码如下:

import com.example.myspringboot.annotation.MyLog;
import com.example.myspringboot.bean.OperationEnum;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qx
 * @date 2023/06/21
 * @desc 自定义日志注解测试
 */
@RestController
@RequestMapping("/user")
public class MyLogController {

    /**
     * 用户新增
     */
    @RequestMapping("/add")
    @MyLog(operation = OperationEnum.ADD, desc = "用户新增")
    public String add() {
        return "用户新增";
    }

    /**
     * 用户删除
     */
    @RequestMapping("/delete")
    @MyLog(operation = OperationEnum.DELETE, desc = "用户删除")
    public String delete() {
        return "用户删除";
    }

    /**
     * 用户查询
     */
    @RequestMapping("/find")
    public String find() {
        return "用户查询";
    }
}

六、测试

我们启动项目,JPA自动创建数据库表t_operation_log。

SpringBoot使用AOP自定义注解实现日志记录_spring

浏览器测试新增方法

SpringBoot使用AOP自定义注解实现日志记录_数据库_02

控制台显示sql新增执行语句

2023-06-21 11:49:19.635  INFO 11760 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-06-21 11:49:19.635  INFO 11760 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-06-21 11:49:19.637  INFO 11760 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms
Hibernate: insert into t_operation_log (content, create_date, operation_type) values (?, ?, ?)

我们刷新数据库,发现我们的日志的信息保存到了数据库。

SpringBoot使用AOP自定义注解实现日志记录_自定义_03

测试用户删除:

SpringBoot使用AOP自定义注解实现日志记录_spring_04

我们连续刷新数据库发现删除的日志同样存储到了数据库

SpringBoot使用AOP自定义注解实现日志记录_自定义_05

最后我们执行查询的方法,因为我们没有在查询的方法加上自定义日志注解,所以这个操作不会新增到数据库。

SpringBoot使用AOP自定义注解实现日志记录_数据库_06

刷新数据库没有发现新增的数据。

SpringBoot使用AOP自定义注解实现日志记录_数据库_07

七、总结

本文使用了自定义注解,通过在方法上标记注解实现了保存操作日志的功能。在实际工作中我们可以根据需求自定义不同的注解,比如方法的访问控制、身份认证等,提高aop自定义注解处理的多样性。