SpringBoot用AOP+自定义注解实现操作日志写入数据库

本文介绍了如何在SpringBoot中利用AOP和自定义注解来实现操作日志的记录,并将这些日志写入数据库。首先讲解了操作日志业务处理的需求,然后详细阐述了自定义注解的创建过程,接着讨论了AOP切面的原理,并展示了如何编写日志切面类。最后,通过在业务方法上添加自定义注解,实现了当请求触发时,操作日志自动写入数据库。


前言

主要以将用户操作日志写入数据库的例子简单记录AOP结合自定义注解的运用

一、操作日志业务处理

根据数据库日志表和具体业务要求,写好操作日志实体类和操作日志业务层处理逻辑类OperationLogService

二、自定义注解

1.了解自定义注解

(1) 自定义注解使用@interface 用来声明,此时它自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节;

(2) 在自定义注解时,不能继承其他的注解或接口;

(3) 自定义注解中的每一个方法实际上是声明的一个配置参数。其中,方法的名称就是参数的名称,返回值类型就是参数的类型(**注意** : 返回值类型只能是8种基本类型、Class、String、Enum、annotations);

(4) 自定义注解参数要求:
参数只能用public或default(默认)这两个访问权修饰,省略权限修饰词就是默认权限修饰词default;
参数类型只能用8种基本数据类型和 String,Enum,Class,annotations等数据类型,以及以上这些类型的数组;
如果只有一个参数成员,最好把参数名称设为"value",即方法名是value ;
注解参数必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解参数的值不可为 null。所以,常用空字符串或 0 作为默认值。因此每个注解的声明中,所有参数都都具有相应的值,那如何表示某个元素不存在呢,我们习惯定义一些特殊的值来表示(如空字符串或者负数);
参数的默认值可以通过 default 来声明,如 : public String name() default " "

(5) 相关元注解:
@Target 定义注解的使用位置,默认可以使用在任何元素上
ElementType.CONSTRUCTOR : 用于构造器
ElementType.FIELD : 成员变量、对象、属性(包括enum实例)
ElementType.LOCAL_VARIABLE: 用于局部变量
ElementType.METHOD : 用于方法
ElementType.PACKAGE : 用于包
ElementType.PARAMETER : 用于参数
ElementType.TYPE : 用于类、接口(包括注解类型)或enum声明

@Retention 定义注解被保留策略,生命周期
RetentionPolicy.SOURCE : 注解只保留在源文件中,在编译成class文件的时候被遗弃
RetentionPolicy.CLASS : 注解被保留在class中,但是在jvm加载运行的时候被抛弃,这个是默认的声明周期
RetentionPolicy.RUNTIME : 注解在jvm加载运行的时候仍被保留,因此它能通过反射被读取到

@Documented 表示是否将注解信息添加在Java文档中

@Inherited 表示这个自定义注解是被继承的,如果写在了父类的声明部分,那么其子类的声明部分自动拥有该注解

2.编写自定义注解

import java.lang.annotation.*;

/**
 * @author nianyu
 * 自定义操作日志注解(用于操作日志存入数据库操作日志表)
 */
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLogAnnotate {

	/**
     *操作类型
     */
    String operatorType() default "";

    /**
     * 操作请求
     */
    String operatorRequest() default "";

}

三、AOP切面

1.了解AOP切面

基本概念:
(1) Aspect(切面): Aspect 声明类似于 Java 中的类声明, 切面是通知和切点的结合;

(2) Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point;

(3)Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方;

(4) Advice(通知):Advice描述了切面何时以及如何执行增强处理 ,通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码,他定义了在 Pointcut 里面定义的程序点具体要做的操作;

相关注解:
@Aspect: 定义切面类,把当前类标识为一个切面供容器读取;

@Pointcut: 切点
Pointcut是植入Advice的触发条件;Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码;

@Around:环绕增强;

@AfterReturning:后置增强,方法正常退出时执行;

@Before:前置增强,方法之前执行;

@AfterThrowing:抛出异常执行;

@After: final增强,不管是抛出异常或者正常退出都会执行;

1.编写日志切面类

/**
 * 操作日志切面类
 *
 * @author nianyu
 */

@Aspect
@Component
public class OperationLogAspect {

	/**
     * OperationLogService 日志写入数据库的业务处理类
     */
    @Resource
    OperationLogService operationLogService;

    /**
     * 定义切点 @Pointcut
     * 在注解的位置切入代码
     * com.nianyu.log.annotation.OperationLogAnnotate 是注解所在路劲
     */
    @Pointcut("@annotation(com.nianyu.log.annotation.OperationLogAnnotate)")
    public void logPointCut() {
    }

	/**
     * 用户操作完后保存日志到数据库
     * 在注解的位置切入代码
     * com.nianyu.log.annotation.OperationLogAnnotate 是注解所在路径
     * @param ret 用户执行操作方法的返回结果,封装的类型为Result
     */
    @AfterReturning(value = "logPointCut() && @annotation(logger)", returning = "ret")
    public void saveOperationLog(JoinPoint joinPoint, OperationLogAnnotate logger, Result ret) {
     
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //创建一个操作日志实体类存放此次操作信息
        OperationLog operationLog = new OperationLog();
        try {
        	//获取用户id
            operationLog .setOperatorId(UserUtil.getCurrentUserId().toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //获取操作请求
        operationLog.setOperatorRequest(logger.operatorRequest());
        //获取操作类型
        operationLog.setOperatorType(logger.operatorType()); 
        //获取ip
        operationLog.setOperatorIp(request.getRemoteAddr());
        //操作时间
        operationLog.setOperateDate(LocalDateTime.now());
        //获取类名方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        operationLog.setOperatorMethod(className + "." + methodName + "()");
        //获取uri
        String requestURI = request.getRequestURI();
        operationLog.setOperatorUri(requestURI);
        //若有返回结果,封装返回信息
        if (null != ret) {
            operationLog.setOperatorReturnCode(ret.getCode());
            operationLog.setOperatorReturnMsg(ret.getMsg());
        }
        //将操作日志写入数据库
        operationLogService.addOperationLog(operationLog );
    }

四、操作日志写入数据库(使用自定义注解)

在需要存入数据库的操作方法上加上自定义注解

@PostMapping("/add")
@OperationLogAnnotate(operatorType="add",operatorRequest="增加XXXXX")
public Result addUser(@RequestBody User user){
    
     return tsstService.addUser(user);
}

启动项目,访问请求,查看数据库是否有刚才的操作记录

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值