springboot+aop切点记录请求和响应信息

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

本篇主要分享的是springboot中结合aop方式来记录请求参数和响应的数据信息;这里主要讲解两种切入点方式,一种方法切入,一种注解切入;

首先创建个springboot测试工程并通过maven添加如下依赖:

        <!-- AOP -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!--阿里 FastJson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.44</version>
        </dependency>

先来说方法的切点方式,需要创建个名为LogAspect的组件类,然后用@Aspect注解修饰组件类,再通过设置方法切入点方式做公共日志记录,如下创建切入点:

    //切点入口 Controller包下面所有类的所有方法
    private final String pointcut = "execution(* com.platform.Controller..*(..))";

    //切点
    @Pointcut(value = pointcut)
    public void log() {
    }

这里的execution( com.platform.Controller..(..))主要的意思是:切入点入口是Controller包下面所有类的所有方法;

再来通过@Around环绕注解方法里面做请求参数和响应信息的记录,如下代码:

    @Around(value = "log()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        StringBuilder sbLog = new StringBuilder("\n");
        try {
            sbLog.append(String.format("类名:%s\r\n", proceedingJoinPoint.getTarget().getClass().getName()));

            MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
            sbLog.append(String.format("方法:%s\r\n", methodSignature.getMethod().getName()));

            Object[] args = proceedingJoinPoint.getArgs();
            for (Object o : args) {
                sbLog.append(String.format("参数:%s\r\n", JSON.toJSON(o)));
            }

            long startTime = System.currentTimeMillis();
            result = proceedingJoinPoint.proceed();
            long endTime = System.currentTimeMillis();
            sbLog.append(String.format("返回:%s\r\n", JSON.toJSON(result)));
            sbLog.append(String.format("耗时:%ss", endTime - startTime));
        } catch (Exception ex) {
            sbLog.append(String.format("异常:%s", ex.getMessage()));
        } finally {
            logger.info(sbLog.toString());
        }
        return result;
    }

此刻主要代码就完成了,再来我们配置下日志的记录方式;


首先在 resources目录增加名为logback-spring.xml的文件,其配置信息如:

<?xml version="1.0" encoding="UTF-8"?>
<!--
  ~ Author:shenniu003
  ~ Copyright (c) 2018.
  -->

<configuration debug="false" scan="true" scanPeriod="1 seconds">

    <springProperty scope="context" name="appname" source="logging.logback.appname"/>
    <springProperty scope="context" name="logLevel" source="logging.logback.level"/>
    <springProperty scope="context" name="logPath" source="logging.logback.path"/>

    <property name="logPathAll" value="${logPath}/${appname}.log"/>

    <contextName>logback</contextName>

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter" >
             <level>WARN</level>
         </filter>-->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logPathAll}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logPathAll}.%d{yyyy-MM-dd}.zip</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
            </pattern>
        </encoder>
    </appender>

    <root level="${logLevel}">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
    </root>

</configuration>

然后application.yml的配置信息如:

logging:
  config: classpath:logback-spring.xml
  logback:
    level: info #info ,debug
    path: /home/app/data/applogs/weblog
    appname: web

此刻日志和公共的aop记录类都完成了,我们需要创建个测试用例,其代码如:

    @PostMapping("/addUser")
    public ResponseEntity<MoStudent> addUser(@RequestBody MoStudent moStudent) throws Exception {
        moStudent.setNumber(UUID.randomUUID().toString());
//        throw new Exception("错误了");
        return new ResponseEntity<>(moStudent, HttpStatus.OK);
    }

最后,通过postman模拟post请求,能够得到如下的日志结果:
image


上面的方式切入点是所有方法,所有方法都记录日志可能有是不是需求想要的,此时可以通过注解的方式来标记想记录日志的方法;

先来创建个日志注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
    /**
     * 描述
     *
     * @return
     */
    String des() default "";
}

同样再来创建注解的切入点:

    //匹配方法上包含此注解的方法
    private final String annotationPointCut = "@annotation(com.platform.Aop.LogAnnotation)";

    //注解切点
    @Pointcut(value = annotationPointCut)
    public void logAnnotation() {
    }

再通过@Around注解绑定具体的操作方法:

    @Around(value = "logAnnotation()")
    public Object aroundAnnotation(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        StringBuilder sbLog = new StringBuilder("\n");
        try {
            sbLog.append(String.format("类名:%s\r\n", proceedingJoinPoint.getTarget().getClass().getName()));

            MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
            Method method = methodSignature.getMethod();
            LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
            if (logAnnotation != null && !logAnnotation.des().isEmpty()) {
                sbLog.append(String.format("说明:%s\r\n", logAnnotation.des()));
            }
            sbLog.append(String.format("方法:%s\r\n", method.getName()));

            Object[] args = proceedingJoinPoint.getArgs();
            for (Object o : args) {
                sbLog.append(String.format("参数:%s\r\n", JSON.toJSON(o)));
            }

            long startTime = System.currentTimeMillis();
            result = proceedingJoinPoint.proceed();
            long endTime = System.currentTimeMillis();
            sbLog.append(String.format("返回:%s\r\n", JSON.toJSON(result)));
            sbLog.append(String.format("耗时:%ss", endTime - startTime));
        } catch (Exception ex) {
            sbLog.append(String.format("异常:%s", ex.getMessage()));
        } finally {
            logger.info(sbLog.toString());
        }
        return result;
    }

这个方法里需要注意的是注解方式相比第一种其实就多了如下几行代码:

            Method method = methodSignature.getMethod();
            LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
            if (logAnnotation != null && !logAnnotation.des().isEmpty()) {
                sbLog.append(String.format("说明:%s\r\n", logAnnotation.des()));
            }

下面是注解测试用例:

    @LogAnnotation(des = "注解记录日志")
    @PostMapping("/addUser01")
    public ResponseEntity<MoStudent> addUser01(@RequestBody MoStudent moStudent) throws Exception {
        moStudent.setNumber(UUID.randomUUID().toString());
        return new ResponseEntity<>(moStudent, HttpStatus.OK);
    }

image


如下是LogAspect.java的所有代码:

/*
 * Author:shenniu003
 * Copyright (c) 2018.
 */

package com.platform.Aop;

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * Created by Administrator on 2018/11/5.
 * springboot+aop切点记录请求和响应信息
 */
@Component
@Aspect
public class LogAspect {

    //切点入口 Controller包下面所有类的所有方法
    private final String pointcut = "execution(* com.platform.Controller..*(..))";

    //匹配方法上包含此注解的方法
    private final String annotationPointCut = "@annotation(com.platform.Aop.LogAnnotation)";

    private Logger logger = LoggerFactory.getLogger(LogAspect.class);

    //切点
    @Pointcut(value = pointcut)
    public void log() {
    }

    @Around(value = "log()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        StringBuilder sbLog = new StringBuilder("\n");
        try {
            sbLog.append(String.format("类名:%s\r\n", proceedingJoinPoint.getTarget().getClass().getName()));

            MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
            sbLog.append(String.format("方法:%s\r\n", methodSignature.getMethod().getName()));

            Object[] args = proceedingJoinPoint.getArgs();
            for (Object o : args) {
                sbLog.append(String.format("参数:%s\r\n", JSON.toJSON(o)));
            }

            long startTime = System.currentTimeMillis();
            result = proceedingJoinPoint.proceed();
            long endTime = System.currentTimeMillis();
            sbLog.append(String.format("返回:%s\r\n", JSON.toJSON(result)));
            sbLog.append(String.format("耗时:%ss", endTime - startTime));
        } catch (Exception ex) {
            sbLog.append(String.format("异常:%s", ex.getMessage()));
        } finally {
            logger.info(sbLog.toString());
        }
        return result;
    }

    //注解切点
    @Pointcut(value = annotationPointCut)
    public void logAnnotation() {
    }

    @Around(value = "logAnnotation()")
    public Object aroundAnnotation(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        StringBuilder sbLog = new StringBuilder("\n");
        try {
            sbLog.append(String.format("类名:%s\r\n", proceedingJoinPoint.getTarget().getClass().getName()));

            MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
            Method method = methodSignature.getMethod();
            LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
            if (logAnnotation != null && !logAnnotation.des().isEmpty()) {
                sbLog.append(String.format("说明:%s\r\n", logAnnotation.des()));
            }
            sbLog.append(String.format("方法:%s\r\n", method.getName()));

            Object[] args = proceedingJoinPoint.getArgs();
            for (Object o : args) {
                sbLog.append(String.format("参数:%s\r\n", JSON.toJSON(o)));
            }

            long startTime = System.currentTimeMillis();
            result = proceedingJoinPoint.proceed();
            long endTime = System.currentTimeMillis();
            sbLog.append(String.format("返回:%s\r\n", JSON.toJSON(result)));
            sbLog.append(String.format("耗时:%ss", endTime - startTime));
        } catch (Exception ex) {
            sbLog.append(String.format("异常:%s", ex.getMessage()));
        } finally {
            logger.info(sbLog.toString());
        }
        return result;
    }
}
目录
相关文章
|
2月前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
140 6
|
2月前
|
Java 应用服务中间件 Spring
SpringBoot 响应请求是串行还是并行?
Spring Boot 在默认情况下通过 Servlet 容器的线程池实现并行处理 HTTP 请求。通过适当的线程池配置,可以进一步优化并发性能。此外,Spring Boot 提供了异步处理机制(如使用 `@Async` 注解)和反应式编程模型(Spring WebFlux),使得应用能够处理更高的并发负载。在具体项目中,可以根据需求选择合适的处理模型,以充分利用 Spring Boot 的并发处理能力。
69 21
|
2月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
101 8
|
4月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
121 5
|
4月前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
59 0
|
6月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
123 1
|
4月前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
682 1
什么是AOP面向切面编程?怎么简单理解?
|
4月前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
104 5
|
6月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
【9月更文挑战第9天】AOP(面向切面编程)通过分离横切关注点提高模块化程度,如日志记录、事务管理等。Micronaut AOP基于动态代理机制,在应用启动时为带有特定注解的类生成代理对象,实现在运行时拦截方法调用并执行额外逻辑。通过简单示例展示了如何在不修改 `CalculatorService` 类的情况下记录 `add` 方法的参数和结果,仅需添加 `@Loggable` 注解即可。这不仅提高了代码的可维护性和可扩展性,还降低了引入新错误的风险。
66 13
|
5月前
|
Java 容器
AOP面向切面编程
AOP面向切面编程
65 0