拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 大聪明教你学Java | Spring Boot 使用自定义注解实作操作日志的记录

大聪明教你学Java | Spring Boot 使用自定义注解实作操作日志的记录

白鹭 - 2022-01-25 2096 0 0

前言

我们无论开发什么应用,其中都会有一个功能需求——记录操作日志,有了操作日志的记录既保证应用的完成性,也可以在因为误操作而出现系统崩溃的情况下通过操作日志进行溯源,可以说记录操作日志的功能在任何一款应用软件中都是不可或缺的,那么各位小伙伴可以想一下,如果我们要实作记录操作日志的功能,我们该怎么实作呢?

最简单粗暴的办法就是在每一个方法里增加一行代码来记录本次操作(插入操作日志表,本质就是一条 insert 陈述句),这样虽然可以实作我们所需要的功能,但是实作程序就比较麻烦了,而且容易出错误,这时候如果使用自定义注解的话就会方便很多,很大程度上简化了我们的代码,而且让代码可读性更强

那么今天就和大家分享一下如何利用自定义注解实作“记录操作日志”的功能

利用自定义注解实作操作日志的记录

实作自定义注解

我们还是先引入 Maven 依赖

<!-- SpringBoot 拦截器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

接下来我们实作一个注解类(注解在 Java 中与类、界面的宣告类似,只是所使用的关键字有所不同,宣告注解使用 @interface 关键字,在底层实作上,所有定义的注解都会自动继承 java.lang.annotation.Annotation 界面,)

import java.lang.annotation.*;

/**
 * 自定义注解-实作操作日志记录
 * 此处代码仅作参考
 * 实际开发程序中根据自己的业务功能添加所需要的栏位
 * 
 * @description: Log
 * @author: 庄霸.liziye
 * @create: 2021-12-21 12:47
 **/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
    /**
     * 模块 
     */
    public String title() default "";

    /**
     * 操作型别
     * 
     * 此处的 BusinessType 是我自己定义的列举类
     * BusinessType.OTHER 代表其他操作
     */
    public BusinessType businessType() default BusinessType.OTHER;
    
}

关于注解的定义,咱们多说几句
① 访问修饰符必须为 public,不写的话则默认为 public,
② 自定义注解中元素的型别只能是基本资料型别、String、Class、列举型别,也可以是注解型别(体现了注解的嵌套效果)以及上述型别的一位阵列,
③ 自定义注解中的 default 代表默认值,
④ 自定义注解上的 @Target 注解的作用是限定自定义注解能够被应用在哪些Java 元素上,ElementType 是一个列举型别,其原始码如下

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

⑤ 自定义注解上的 @Retention 注解用来定义其宣告周期,注解的生命周期有三个阶段,分别是:源档案阶段;编译阶段;运行阶段,RetentionPolicy 同样是一个列举型别,其原始码如下

ublic enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

⑥ 自定义注解上的 @Documented 是一个标记注解,表明该自定义注解应该被 JavaDoc 工具记录.,默认情况下 JavaDoc 的记录是不包括注解的, 但如果宣告注解时指定了 @Documented ,该注解就会被 JavaDoc 之类的工具处理,所以注解型别信息也会被生成到 JavaDoc 档案中,

下面我们就需要定义一个切面类,来进一步的实作自定义注解的功能,代码如下(这里就只贴一下重要代码)

/**
 * 自定义注解-操作日志记录处理
 * 此处代码仅作参考,开发程序中根据自己的业务功能需求添加所需要业务逻辑
 *
 * @description: Log
 * @author: 庄霸.liziye
 * @create: 2021-12-22 13:07
 **/
@Aspect
@Component
public class LogAspect
{
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    // 配置织入点-xxx代表自定义注解的存放位置,如:com.test.annotation.Log
    @Pointcut("@annotation(xxxx)")
    public void logPointCut()
    {
    }

    /**
     * 处理完请求后执行此处代码
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
    {
        handleLog(joinPoint, controllerLog, null, jsonResult);
    }

    /**
     * 如果处理请求时出现例外,在抛出例外后执行此处代码
     * 
     * @param joinPoint 切点
     * @param e 例外
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
    {
        handleLog(joinPoint, controllerLog, e, null);
    }

  /**
     * 日志处理
     */
    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
    {
        try
        {
            //此处为处理日志的具体业务逻辑
            //最终将操作信息插入至操作日志表
        }
        catch (Exception exp)
        {
            // 记录本地例外日志
            log.error("**** 出现例外 ****");
            log.error("例外信息:{}", exp.getMessage());
            exp.printStackTrace();
        }
    }
}

这里咱们主要解释一下切面类中的各个注解的作用:
① @Aspect:一个标识性的注解,表示当前类是一个切面,可以被容器读取,
② @Component:表示该类被 Spring 管理,可以理解为将该类注入进 Spring(就像向 Spring 注入 bean 一样),
③ @Pointcut:Pointcut是植入切面的触发条件,每个 Pointcut 的定义包括2部分,一是表达式,二是方法签名,方法签名必须是 public 及 void,可以将Pointcut 中的方法看作是一个被切面参考的标记符号,因为表达式不直观,因此我们可以通过方法签名的方式为此表达式命名,因此 Pointcut 中的方法只需要方法签名,而不需要在方法体内撰写实际代码,
④ @AfterReturning:后置增强,相当于AfterReturningAdvice,给方法增加该注解后表示当方法正常退出时执行此方法,
⑤ @AfterThrowing:例外抛出增强,相当于ThrowsAdvice,给方法增加该注解后表示当方法抛出例外时执行此方法,
⑥ pointcut/value 这两个属性的作用是一样的,它们都属于指定切入点对应的切入表达式,一样既可以是已有的切入点,也可直接定义切入点表达式,当指定了 pointcut 属性值后,value 属性值将会被覆写,
⑦ returning:该属性指定一个形参名,用于表示方法中可定义与此同名的形参,该形参可用于访问目标方法的回传值,需要注意的是,在方法中定义该形参时指定了型别,则限制目标方法必须回传此型别的值或没有回传值,

至此,我们的自定义注解就配置完成了,最后我们再来看一下如何使用自定义注解~

使用自定义注解

使用自定义注解就很简单了,使用方式和其他注解是相同的
在这里插入图片描述

只要我们在需要记录操作日志的方法上增加配置好的自定义注解(别忘了给自定义注解中的栏位赋值哦~)就可以使用啦,我们每次请求方法时,都会帮助我们记录本次操作,
在这里插入图片描述
P.S. 虽然定义自定义注解的程序麻烦了一些,但是磨刀不误砍柴工,合理的使用自定义注解不仅仅提升了编码效率,而且可以瞬间提升代码的逼格

小结

本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨?

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *