自定义注解实现打印系统日志

2021-11-09  本文已影响0人  小疏林er

1 注解(Annotation)

  • Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
  • Java 语言中的类、方法、变量、参数和包等都可以被标注。注解通过反射来在运行时获取标注内容(在编译器生成类文件时,标注可以被嵌入到字节码中,Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容。)

1.1 常见注解

1.2 自定义注解

1.2.1 注解声明方式

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SysLog {
    //注解的参数 参数类型+参数名+();
    //default 代表默认值, 正常注解有参数不传会报错,如果提供了默认值则不会报错
    //默认值为-1代表不存在找不到
    //参数名为value时 使用注解时可以不用写 value=XXX

    /**
     * All: 全部通知都执行
     * BEFORE: 前置通知,主要打印入参
     * AFTER: 后置通知
     * NO_THROW: 异常通知,只有数组里面有NO_THROW时,发生异常时才不会通知,其他情况,发生异常一律通知
     * AROUND: 环绕通知,主要打印方法执行时间
     * RETURN: 返回通知:主要打印执行结果
     */
    String[] value() default {"BEFORE","AROUND","RETURN"};
}

1.2.2 常用元注解使用说明

2 AOP 面向切面编程

2.1 基本概念

在程序运行过程中,动态的将代码插入到原有的指定方法、指定位置上的思想被称之为面向切面编程。

  • advice: 增强、通知,在特定连接点执行的动作(其实就是你要插入到的代码)。
  • pointcut: 切点,一组连接点的总称,用于指定某个增强应该在何时被调用(像是对一组连接点的抽象声明,通常是execution表达式,即符合表达式的方法被称为切点)。
  • joinpoint: 连接点,在应用执行过程中能够插入切面的一个点。
  • aspect: 切面,即通知(增强)和切点的结合。

2.2 aspectj实现面向切面编程

2.2.1 定义切面

  • 切面是切点和增强的结合,使用aspectj定义切面时,需要准备一个类,来定义各种通知方法及实现。类上需要加上@Aspect注解。

2.2.2 定义切点

定义一个切点需要两部分组成:Pointcut表示式和Point签名。
Pointcut表示式通常有两种类型,一种是execution表达式,一种就是注解。
@Around("logPointCut()") 等价于@Around("@annotation(com.sler.springcloud.utils.SysLog)")

例:execution表达式:public * com.sler.springcloud.controller...(..)
public 代表访问权限
* 代表任意返回值
controller.. 代表当前包及子包
* 代表所有类
.*(..) 代表类下面所有方法,(..)代表允许任何形式的入参

    @Pointcut("@annotation(com.sler.springcloud.utils.SysLog)")  //Pointcut表示式
    public void logPointCut() {}                                 //Point签名

2.2.3 五种通知(增强)

3 开发系统日志注解

3.1 代码实现

3.1.1 自定义注解类

package com.sler.springcloud.utils;

import java.lang.annotation.*;

/**
 * 类功能:系统日志注解
 * 作者: sler
 * 创建时间: 2021/10/20 18:38
 * 描述:元注解 :@Target @Retention @Documented @Inherited
 */

/** Target:指明了修饰的这个注解的使用范围,即被描述的注解可以用在哪里。
 *
 * TYPE:类,接口或者枚举
 * FIELD:域,包含枚举常量
 * METHOD:方法
 * PARAMETER:参数
 * CONSTRUCTOR:构造方法
 * LOCAL_VARIABLE:局部变量
 * ANNOTATION_TYPE:注解类型
 * PACKAGE:包
 */
@Target(ElementType.METHOD)

/** Retention:指明修饰的注解的生存周期,即会保留到哪个阶段。 RUNTIME>CLASS>SOURCE
 *
 * SOURCE:源码级别保留,编译后即丢弃
 * CLASS:编译级别保留,编译后的class文件中存在,在jvm运行时丢弃,这是默认值
 * RUNTIME: 运行级别保留,编译后的class文件中存在,在jvm运行时保留,可以被反射调用
 */
@Retention(RetentionPolicy.RUNTIME)

/**
 * Documented:指明修饰的注解,可以被例如javadoc此类的工具文档化,只负责标记,没有成员取值。
 */
@Documented

/**
 * 子类可以继承父类注解
 */
@Inherited
public @interface SysLog {
    //注解的参数 参数类型+参数名+();
    //default 代表默认值, 正常注解有参数不传会报错,如果提供了默认值则不会报错
    //默认值为-1代表不存在找不到
    //参数名为value时 使用注解时可以不用写 value=XXX

    /**
     * All: 全部通知都执行
     * BEFORE: 前置通知,主要打印入参
     * AFTER: 后置通知
     * NO_THROW: 异常通知,只有数组里面有NO_THROW时,发生异常时才不会通知,其他情况,发生异常一律通知
     * AROUND: 环绕通知,主要打印方法执行时间
     * RETURN: 返回通知:主要打印执行结果
     */
    String[] value() default {"BEFORE","AROUND","RETURN"};
}

3.1.2 切面类

package com.sler.springcloud.utils;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * 类功能:系统日志切面
 * 作者: sler
 * 创建时间: 2021/10/20 18:41
 * 描述:
 */

/**
 * @Before: 前置通知, 在方法执行之前执行
 * @After: 后置通知, 在方法执行之后执行 。后置方法在连接点方法完成之后执行,无论连接点方法执行成功还是出现异常,都将执行后置方法。
 * @AfterRunning: 返回通知, 在方法返回结果之后执行 当连接点方法成功执行后,返回通知方法才会执行,如果连接点方法出现异常,则返回通知方法不执行。
 * @AfterThrowing: 异常通知, 在方法抛出异常之后 异常通知方法只在连接点方法出现异常后才会执行,否则不执行。
 * @Around: 环绕通知, 围绕着方法执行
 * <p>
 * 正常执行顺序 环绕前 -> 前置 -> 环绕后(异常时无) -> 后置 -> 返回通知(或异常通知,二者存其一)
 */

@Aspect
@Component
@Slf4j
public class SysLogAspect {

    /**
     * 定义切点
     * Pointcut切点包括
     *      Pointcut表示式:@Pointcut("@annotation(com.sler.springcloud.utils.SysLog)")
     *      Point签名:public void logPointCut(){}
     * 对定义好的切点进行增强时,可以使用表达式也可以使用签名
     */
    @Pointcut("@annotation(com.sler.springcloud.utils.SysLog)")
    public void logPointCut() {}

    /**
     * 环绕通知
     * @param joinPoint
     * @return
     * @throws Throwable
     */
//    @Around("logPointCut()")
    @Around("@annotation(com.sler.springcloud.utils.SysLog)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取切点方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取自定义注解
        SysLog sysLog = method.getAnnotation(SysLog.class);
        //获取注解值
        String[] value = sysLog.value();
        Object result = null;

        if (this.check("AROUND",value)){
            long beginTime = System.currentTimeMillis();    //开始计时
            //执行方法
            result = joinPoint.proceed();
            long time = System.currentTimeMillis() - beginTime; //执行时长
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = signature.getName();
            log.info("系统环绕通知:方法" + className + "." + methodName + "(),执行时间:" + time + "ms");
            return result;
        }else {
            result = joinPoint.proceed();
            return result;
        }
    }

    /**
     * 异常日志 (可以做统一返回处理)
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取自定义注解
        SysLog sysLog = method.getAnnotation(SysLog.class);
        //获取注解值
        String[] value = sysLog.value();
        if (!this.check("NO_THROW",value)){
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = signature.getName();

            log.error("系统异常通知:执行" + className + "." + methodName + "()方法发生异常,异常信息:" + e.toString());
        }
    }

    /**
     * 前置通知
     *
     * @param point
     */
    @Before("logPointCut()")
    public void beforMethod(JoinPoint point) {
        //获取切点方法
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //获取自定义注解
        SysLog sysLog = method.getAnnotation(SysLog.class);
        //获取注解值
        String[] value = sysLog.value();
        if (this.check("BEFORE",value)){
            String className = point.getTarget().getClass().getName();
            String methodName = point.getSignature().getName();
            List<Object> args = Arrays.asList(point.getArgs());

            log.info("系统前置通知:待执行方法" + className + "." + methodName + "(),入参:" + args);
        }
    }

    /**
     * 后置通知
     *
     * @param point
     */
    @After("logPointCut()")
    public void afterMethod(JoinPoint point) {
        //获取切点方法
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //获取自定义注解
        SysLog sysLog = method.getAnnotation(SysLog.class);
        //获取注解值
        String[] value = sysLog.value();
        if (this.check("AFTER",value)){
            String className = point.getTarget().getClass().getName();
            String methodName = point.getSignature().getName();

            log.info("系统后置通知:已执行方法" + className + "." + methodName+"()");
        }
    }

    /*通过returning属性指定连接点方法返回的结果放置在result变量中,在返回通知方法中可以从result变量中获取连接点方法的返回结果了。*/

    /**
     * 返回通知
     * @param point
     * @param result
     */
    @AfterReturning(value = "logPointCut()", returning = "result")
    public void afterReturning(JoinPoint point, Object result) {
        //获取切点方法
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //获取自定义注解
        SysLog sysLog = method.getAnnotation(SysLog.class);
        //获取注解值
        String[] value = sysLog.value();
        if (this.check("RETURN",value)){
            String className = point.getTarget().getClass().getName();
            String methodName = point.getSignature().getName();

            log.info("系统返回通知:已执行方法" + className + "." + methodName + "(),执行结果:" + result);
        }
    }

    private boolean check(String type,String info[]){
        boolean flag = false;
        for (String s : info) {
            if("ALL".equals(s) || type.equals(s)){
                return true;
            }
        }
        return flag;
    }

}

3.1.3 测试方法

    @SysLog
    @RequestMapping("/phone/{length}")
    public Object createData( @PathVariable int length) throws Exception {
        List<String> name = phoneUtils.getPhones(length);
        Object o = JSONArray.toJSON(name);
        return o;
    }

3.2 测试结果

执行结果.png
上一篇下一篇

猜你喜欢

热点阅读