• SpringBoot通过自定义注解实现日志打印


    目录

    前言:

    正文

    一.Spring AOP

    1.JDK动态代理

    2.Cglib动态代理

    使用AOP主要的应用场景:

    SpringBoot通过自定义注解实现日志打印

    一.Maven依赖

    二.ControllerMethodLog.class自定义注解

    三.Spring AOP切面方法的执行顺序

    四.ControllerMethodLogAspect.class:用于打印日志的切面定义类


    前言:


    在我们日常的开发过程中通过打印详细的日志信息能够帮助我们很好地去发现开发过程中可能出现的​​Bug​​​,特别是在开发​​Controller​​​层的接口时,我们一般会打印出​​Request​​​请求参数和​​Response​​响应结果,但是如果这些打印日志的代码相对而言还是比较重复的,那么我们可以通过什么样的方式来简化日志打印的代码呢?

    正文


    一.Spring AOP


    Spring AOP 即面向切面,是对​​OOP​​​面向对象的一种延伸。
    ​​​AOP​​​机制可以让开发者把业务流程中的通用功能抽取出来,单独编写功能代码。在业务流程执行过程中,​​Spring​​框架会根据业务流程要求,自动把独立编写的功能代码切入到流程的合适位置。

    Spring AOP的实现方式

    1.JDK动态代理

    • 类对象必须实现接口
    • ​​JDK​​​动态代理,背后是借助​​Java​​​多态的特性,因为​​JDK​​​动态代理生成的​​class​​​文件已经继承了​​Proxy​​​,而​​Java​​​是单继承的,不能继承目标对象,只能实现目标对象(涉及向上转型),所以是基于​​JDK​​动态代理是基于接口的。

    ​​JDK​​动态代理主要涉及两个类:

    • ​​InvocationHandler​​是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
    • ​​Proxy​​​ 利用 ​​InvocationHandler​​ 动态创建 一个符合某一接口的实例,生成目标类的代理对象。

    2.Cglib动态代理

    • ​​Cglib​​​是一个强大的高性能,高质量的代码生成类库, 可以在运行期扩展 ​​Java​​​ 类与实现 ​​Java​​​ 接口,​​CgLib​​​ 封装了​​asm​​​,可以再运行期动态生成新 的 ​​class​​。

    特别要注意的是:

    • 目标类实现接口的情况下使用​​JDK​​​动态代理,没有实现接口的情况下使用​​Cglib​​动态代理。
    • 可以使用​​ProxyTargetClass = true​​​,强制所有都使用​​Cglib​​动态代理。
    • ​​Cglib​​​所创建的动态代理对象在实际运行时候的性能要比​​JDK​​​动态代理高不少,有研究表明,大概要高10倍;但是​​Cglib​​​在创建对象的时候所花费的时间却比​​JDK​​动态代理要多很多,有研究表明,大概有8倍的差距;
    • 对于​​singleton​​​的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用​​Cglib​​​动态代理,反正,则比较适用​​JDK​​动态代理。

    使用AOP主要的应用场景:

    • ​​Authentication​​ 权限检查
    • ​​Caching​​ 缓存
    • ​​Context passing​​ 内容传递
    • ​​Error handling​​ 错误处理
    • ​​Lazy loading​​ 延迟加载
    • ​​Debugging​​ 调试
    • ​​logging, tracing, profiling and monitoring​​日志记录,跟踪,优化,校准
    • ​​Performance optimization​​性能优化,效率检查
    • ​​Persistence​​ 持久化
    • ​​Resource pooling​​资源池
    • ​​Synchronization​​同步
    • ​​Transactions​​ 事务管理

    SpringBoot通过自定义注解实现日志打印


    一.Maven依赖

    1. org.projectlombok
    2. lombok
    3. 1.18.2
    4. true
    5. org.springframework.boot
    6. spring-boot-starter-aop


    二.ControllerMethodLog.class自定义注解

    ​​@Retention​​: 用来修饰注解,是注解的注解,称为元注解。
    ​​@Target​​:用来说明对象的作用范围
    ​​@Documented​​:用来做标记使用

    1. /**
    2. * 自定义注解用于打印Controller层方式日志
    3. */
    4. @Documented
    5. @Retention(RetentionPolicy.RUNTIME)
    6. @Target({ElementType.METHOD})
    7. public @interface ControllerMethodLog {
    8. }



    这里特别讲一下​​@Retention​​,按生命周期来划分可分为3类:

    • ​​RetentionPolicy.SOURCE​​​:注解只保留在源文件,当​​Java​​​文件编译成​​class​​文件的时候,注解被遗弃(运行时去动态获取注解信息);
    • ​​RetentionPolicy.CLASS​​​:注解被保留到​​class​​​文件,但​​jvm​​​加载​​class​​文件时候被遗弃,这是默认的生命周期(在编译时进行一些预处理操作);
    • ​​RetentionPolicy.RUNTIME​​​:注解不仅被保存到​​class​​​文件中,​​jvm​​​加载​​class​​文件之后,仍然存在(做一些检查性的操作);

    这3个生命周期分别对应于:​​Java​​​源文件(​​.java​​​文件) —> ​​.class​​文件 —> 内存中的字节码。

    三.Spring AOP切面方法的执行顺序


    这里简单介绍一下,切面的执行方法和其执行顺序:

    • ​​@Around​​ 通知方法将目标方法封装起来
    • ​​@Before​​ 通知方法会在目标方法调用之前执行
    • ​​@After​​ 通知方法会在目标方法返回或者异常后执行
    • ​​@AfterReturning​​ 通知方法会在目标方法返回时执行
    • ​​@Afterthrowing​​ 通知方法会在目标方法抛出异常时执行

    这里以一个返回正常的情况为例:(异常替换最后一步即可)

    四.ControllerMethodLogAspect.class:用于打印日志的切面定义类

    注意要在启动类扫描这个​​class​​​,并且添加 ​​@EnableAspectJAutoProxy(proxyTargetClass = true)​​

    1. @Slf4j
    2. @Component
    3. @Aspect
    4. public class ControllerMethodLogAspect {
    5. @Pointcut("@annotation(com.xiyuan.demo.annotation.ControllerMethodLog)")
    6. public void pointCut() {
    7. }
    8. /**
    9. * 在切点运行前执行该方法
    10. */
    11. @Before("pointCut()")
    12. public void doBefore(JoinPoint joinPoint) {
    13. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    14. Method method = signature.getMethod();
    15. ControllerMethodLog annotation = method.getAnnotation(ControllerMethodLog.class);
    16. if (Objects.isNull(annotation)) {
    17. return;
    18. }
    19. String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
    20. log.info("start {}:入参:{}", methodName, JSON.toJSONString(joinPoint.getArgs()));
    21. }
    22. /**
    23. * 在切点运行后,无异常时执行该方法
    24. *
    25. * @param joinPoint
    26. * @param result
    27. */
    28. @AfterReturning(value = "pointCut()", returning = "result")
    29. public void afterReturn(JoinPoint joinPoint, Object result) {
    30. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    31. Method method = signature.getMethod();
    32. ControllerMethodLog annotation = method.getAnnotation(ControllerMethodLog.class);
    33. if (Objects.isNull(annotation)) {
    34. return;
    35. }
    36. String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
    37. log.info("end {}:响应:{}", methodName, JSON.toJSONString(result));
    38. }
    39. }


    验证
    getUserById:根据id获取用户的信息

    1. @GetMapping("/getUserById")
    2. @ApiOperation(value = "根据用户id获取用户")
    3. @ControllerMethodLog
    4. public ResponseResult getUserById(@RequestParam(name = "id", required = true) String id) {
    5. UserInfoPojo userInfoPojo = userService.getUserById(id);
    6. return ResponseResult.success(userInfoPojo, ConstantsUtil.QUERY_SUCCESS);
    7. }


    Swagger接口信息如下:

    IDEA控制台打印信息如下:

  • 相关阅读:
    ③【List】Redis常用数据类型: List [使用手册]
    Intel汇编语言程序设计(第7版)第八章编程练习题答案
    TCP Window Full & TCP Zero Window
    无线物理层安全大作业
    【javaweb】学习日记Day9 - Mybatis 基础操作
    最小生成树算法
    开发中常用的版本管理工具有哪些?
    Leetcode 11. 盛最多水的容器
    MATLAB算法实战应用案例精讲-【数据分析】数据仓库-数据治理
    7.前端笔记-CSS-元素显示模式
  • 原文地址:https://blog.csdn.net/qq_42428269/article/details/132757658