SpringBoot教程(二十四) | SpringBoot集成AOP实现日志记录

news2024/9/22 11:40:45

SpringBoot教程(二十四) | SpringBoot集成AOP实现日志记录

  • (一)AOP 概要
    • 1. 什么是 AOP ?
    • 2. 为什么要用 AOP?
    • 3. AOP一般用来干什么?
    • 4. AOP 的核心概念
  • (二)Spring AOP
    • 1. 简述
    • 2. 相关注解
    • 3. 执行顺序 (细节)
    • 4. @Pointcut 切入点的不同表达式 示例
  • (三) AOP如何添加日志记录
    • 1. 引入AOP依赖
    • 2. 自定义注解
    • 3. 切面类 (仅供 讲解示例 使用)
    • 4. 测试一(切入点用execution 表达式)
    • 5. 测试二(切入点用自定义注解方式)
    • `6. 项目上实际用切面类 (正式用这个哦!!!)`
  • (四)AOP如何修改入参、返参

(一)AOP 概要

1. 什么是 AOP ?

AOP是Aspect Oriented Programming的缩写,意为面向切面编程
这是一种通过预编译方式和运行期间动态代理实现程序功能统一维护的技术。
相比传统的面向对象编程(OOP),AOP更关注于将系统中的公共功能(如日志记录、安全控制、事务处理、异常处理等)从业务逻辑中分离出来,形成独立的模块,以便在不影响业务逻辑代码的情况下,对这些公共功能进行集中管理和维护。

2. 为什么要用 AOP?

  1. 降低耦合度:通过将公共功能从业务逻辑中分离出来,AOP可以显著降低系统各模块之间的耦合度,提高系统的可维护性和可扩展性。
  2. 提高代码复用性:公共功能的独立化使得这些功能可以在多个地方重复使用,而无需在每个业务逻辑中都重复编写相同的代码。
  3. 便于集中管理:AOP允许开发者将系统中的公共功能集中管理,便于统一维护和升级。
  4. 提高开发效率:通过使用AOP,开发者可以更加专注于业务逻辑的实现,而无需花费过多时间在公共功能的编写和维护上。

3. AOP一般用来干什么?

  1. 日志记录:在方法调用前后记录日志信息,帮助开发者进行性能分析和故障排查。
  2. 安全控制:在方法调用前进行权限检查,确保只有具有相应权限的用户才能执行该方法。
  3. 事务管理:在方法调用前后管理事务的开启、提交和回滚,确保数据的一致性和完整性。
  4. 异常处理:在方法调用过程中捕获并处理异常,提供友好的错误消息给用户。
  5. 性能监控:对方法调用的性能进行监控和分析,帮助开发者优化系统性能。
  6. 缓存优化:通过缓存方法调用的结果来提高系统性能,减少不必要的计算和资源消耗。

4. AOP 的核心概念

名词概念理解
通知(Advice)拦截到连接点之后所要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类我们要实现的功能,如日志记录,性能统计,安全控制,事务处理,异常处理等等,说明什么时候要干什么
连接点(Joint Point)被拦截到的点,如被拦截的方法、对类成员的访问以及异常处理程序块的执行等等,自身还能嵌套其他的 Joint PointSpring 允许你用通知的地方,方法有关的前前后后(包括抛出异常)
切入点(Pointcut)对连接点进行拦截的定义指定通知到哪个方法,说明在哪干
切面(Aspect)切面类的定义,里面包含了切入点(Pointcut)和通知(Advice)的定义切面就是通知和切入点的结合
目标对象(Target Object)切入点选择的对象,也就是需要被通知的对象;由于 Spring AOP 通过代理模式实现,所以该对象永远是被代理对象业务逻辑本身
织入(Weaving)把切面应用到目标对象从而创建出 AOP 代理对象的过程。织入可以在编译期、类装载期、运行期进行,而 Spring 采用在运行期完成切点定义了哪些连接点会得到通知
引入(Introduction )可以在运行期为类动态添加方法和字段,Spring 允许引入新的接口到所有目标对象引入就是在一个接口/类的基础上引入新的接口增强功能
AOP 代理(AOP Proxy )Spring AOP 可以使用 JDK 动态代理或者 CGLIB 代理,前者基于接口,后者基于类通过代理来对目标对象应用切面

(二)Spring AOP

1. 简述

AOP 是 Spring 框架中的一个核心内容。在 Spring 中,AOP 代理可以用 JDK 动态代理或者 CGLIB 代理 CglibAopProxy 实现。Spring 中 AOP 代理由 Spring 的 IOC 容器负责生成和管理,其依赖关系也由 IOC 容器负责管理。

2. 相关注解

注解说明
@Aspect将一个 java 类定义为切面类
@Pointcut定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,
也可以是一个注解等
@Before在切入点开始处切入内容
@After在切入点结尾处切入内容
@AfterReturning在切入点 return 内容之后处理逻辑
@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
@Order(100)AOP 切面执行顺序, @Before 数值越小越先执行,@After 和 @AfterReturning 数值越大越先执行

其中 @Before、@After、@AfterReturning、@Around、@AfterThrowing 都属于通知(Advice)。

3. 执行顺序 (细节)

  • 正常情况下: @Around -> @Before -> 目标方法 -> @AfterReturning -> @After -> @Around
  • 异常情况下: @Around -> @Before -> 目标方法 -> @AfterThrowing-> @After


为什么会存在@Around在前又在后?
原因是被 Object result = proceedingJoinPoint.proceed() 这一段代码所影响的;
这段代码上面输出是在@Before之前,下面的输出是在@After之后

4. @Pointcut 切入点的不同表达式 示例

当然,以下是一些不同类型的切入点表达式(Pointcut Expressions)的示例,这些示例通常用于AOP(面向切面编程)框架中,如Spring AOP。

1. execution 表达式

这是最常用的切入点表达式,用于匹配方法执行的连接点。

// 匹配com.example.service包及其子包中所有类的所有方法
execution(* com.example.service..*.*(..))

// 匹配com.example.service.UserService类中所有的public方法
execution(public * com.example.service.UserService.*(..))

// 匹配所有返回类型为String,且方法名以find开头的public方法
execution(public String com.example..*.find*(..))

2. within 表达式

用于匹配连接点所在的Java类或包。

// 匹配com.example.service包及其子包中所有类的所有方法
within(com.example.service..*)

// 精确匹配com.example.service.UserService类中的所有方法
within(com.example.service.UserService)

注意:within表达式通常用于类型匹配,而不是方法签名匹配。

3. this 和 target 表达式

thistarget表达式用于匹配代理对象或目标对象。它们通常用于基于对象类型的过滤,而不是方法签名。

// 匹配代理对象实现了MyInterface接口的所有连接点
this(com.example.MyInterface)

// 匹配目标对象实现了MyInterface接口的所有连接点
target(com.example.MyInterface)

注意:这些表达式在Spring AOP中可能不直接支持,因为Spring AOP是基于代理的,并且thistarget的区分在JDK动态代理和CGLIB代理中可能有所不同。但在AspectJ等更强大的AOP框架中,这些表达式是支持的。

4. args 表达式

args表达式用于匹配方法参数。

// 匹配所有第一个参数为String类型的方法
args(String, ..)

// 匹配所有参数中包含至少一个String类型的方法
args(.., String, ..)

// 精确匹配第一个参数为特定类型的方法
args(com.example.MyType, ..)

注意:args表达式中的参数类型是按顺序匹配的,但可以使用..来匹配任意数量的额外参数。

5. @annotation、@within、@target 和 @args 表达式

这些表达式基于注解来匹配连接点。

// 匹配所有被@Transactional注解标注的方法
@annotation(org.springframework.transaction.annotation.Transactional)

// 匹配所有在类级别被@Transactional注解标注的类中的方法
@within(org.springframework.transaction.annotation.Transactional)

// 匹配所有目标对象(不是代理对象)被@Service注解标注的类中的方法
@target(org.springframework.stereotype.Service)

// 匹配所有至少有一个参数被@Valid注解标注的方法
@args(javax.validation.Valid, ..)

这些表达式提供了强大的灵活性,允许开发者基于注解来定义切面的应用范围。

请注意,具体的语法和支持程度可能会根据你所使用的AOP框架(如Spring AOP、AspectJ等)而有所不同。上述示例主要基于Spring AOP和AspectJ的通用语法。

(三) AOP如何添加日志记录

1. 引入AOP依赖

在Spring Boot中引入AOP就跟引入其他模块一样,非常简单,只需要在pom.xml中加入如下依赖:

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

2. 自定义注解

由于面向切面的切入点(Pointcut)支持多种写法,
我这边也用了注解形式的写法,因此就自定义了以下这个注解,供后面测试使用

package com.example.reactboot.aop;

import java.lang.annotation.*;

/**
 * 自定义注解类
 */
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档

public @interface  Aoplog {
    String value() default "";
}

3. 切面类 (仅供 讲解示例 使用)

为了把@Before、@After、@AfterReturning、@AfterThrowing、@Around都讲解一下,该处的切面类我在这边把日志记录的逻辑挪到了@Before、@After、@Around中

实际项目中,其实只需要在@Around里面去实现日志记录即可(因为在这里才能记录方法执行时间、入参、返参的修改)下面目录 6.项目上实际用切面类 有提供

package com.example.reactboot.aop;


import com.google.gson.Gson;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 系统日志:切面处理类
 *
 * @Aspect:声明该类为一个注解类;
 * @Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为某个 package 下的方法,也可以是自定义注解等;
 * <p>
 * 切点定义好后,就是围绕这个切点做文章了:
 * @Before: 在切点之前,织入相关代码;
 * @After: 在切点之后,织入相关代码;
 * @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
 * @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
 * @Around: 在切入点前后织入代码,并且可以自由的控制何时执行切点;
 */
@Aspect
@Component
@Order(1)
public class WebLogAspect {

    private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    /**
     * execution 表达式
     * 可以基于方法的返回类型、包名、类名、方法名以及参数类型等信息来精确地匹配方法.
     * 以下这个例子:匹配了 com.example.reactboot 包及其子包中所有类的所有方法。
     */
    //@Pointcut("execution(* com.example.reactboot.*.*(..))")
    @Pointcut("execution(public * com.example.reactboot.controller..*.*(..))")
    public void webLog() {
    }

    /**
     * @annotation 表达式
     * 用于匹配被指定注解标注的方法。
     * 以下这个例子:匹配了所有被 com.example.reactboot.aop.Aoplog 注解标注的方法。
     */
    @Pointcut("@annotation(com.example.reactboot.aop.Aoplog)")
    public void aopLog() {
    }


    /**
     * 在切点之前织入
     * @param joinPoint
     * @throws Throwable
     *
     * 以下这个例子:使用了execution 的内容
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 打印请求相关参数
        logger.info("========== Start ==========");
        // 打印请求 url
        logger.info("请求URL: {}", request.getRequestURL().toString());
        // 打印 Http method
        logger.info("请求方法: {}", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        logger.info("全路径以及执行方法 Class Method: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        // 打印请求的 IP
        logger.info("请求IP: {}", request.getRemoteAddr());
        // 打印请求入参
        logger.info("入参Request Args: {}", new Gson().toJson(joinPoint.getArgs()));
    }

    /**
     * 在切点之后织入
     *
     * @throws Throwable
     */
    @After("webLog()")
    public void doAfter() throws Throwable {
        logger.info("========== End ==========");
        // 每个请求之间空一行
        logger.info("");
    }


    /**
     * 切点返回内容后
     *
     * @throws Throwable
     */
    @AfterReturning("webLog()")
    public void afterReturning() {
        logger.info("===@AfterReturning========= 切点返回内容后执行 ==========");
    }


    /**
     * 切点抛出异常后
     *
     * @throws Throwable
     */
    @AfterThrowing("webLog()")
    public void afterThrowing() {
        logger.info("===@AfterThrowing========= 切点抛出异常后执行 ==========");
    }


    /**
     * 环绕
     * 环绕执行,就是在调用目标方法之前和调用之后,都会执行一定的逻辑
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        // 打印出参
        logger.info("出参Response Args  : {}", result);
        // 执行耗时
        logger.info("执行耗时Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
        return result;
    }
}

4. 测试一(切入点用execution 表达式)

以上的切面处理类,使用的 webLog 方法,用的为execution 表达式

控制层 代码

package com.example.reactboot.controller;

import com.example.reactboot.aop.Aoplog;
import com.example.reactboot.aop.WebLogAspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 单纯的@Controller,请求的资源面向的就是页面
 * 而 @RestController,请求的资源面向的是对象或者字符串
 */
@RestController
public class HelloController {

    private final static Logger logger = LoggerFactory.getLogger(HelloController.class);

    @RequestMapping("/index")
    public String sayHello(){
        logger.info("我是index接口");
        return "index";
    }
}

请求该接口后,控制台显示打印如下操作
是有切面日志输出的

在这里插入图片描述

5. 测试二(切入点用自定义注解方式)

先把以上切面类(WebLogAspect )里面的webLog()全部换成aopLog() 再进行测试

控制层 代码

我新写了一个 xiaoming 的接口,在它上面加上了@Aoplog注解

package com.example.reactboot.controller;

import com.example.reactboot.aop.Aoplog;
import com.example.reactboot.aop.WebLogAspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 单纯的@Controller,请求的资源面向的就是页面
 * 而 @RestController,请求的资源面向的是对象或者字符串
 */
@RestController
public class HelloController {

    private final static Logger logger = LoggerFactory.getLogger(HelloController.class);


    @RequestMapping("/index")
    public String sayHello(){
        logger.info("我是index接口");
        return "index";
    }

    @Aoplog(value = "xiaoming")
    @RequestMapping("/xiaoming")
    public String xiaoming(){
        logger.info("我是xiaoming接口");
        return "xiaoming";
    }
}

两个接口都请求后,控制台显示打印如下操作
只有加了注解的才会有切面日志输出

在这里插入图片描述

6. 项目上实际用切面类 (正式用这个哦!!!)

正式项目中,直接在@Around中完成日志记录的操作

实体类

package com.example.reactboot.aop;

import java.io.Serializable;
import java.util.Date;

/**
 * 操作日志
 * @author 
 * @since 
 */
public class JgCompanyOperateLog implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    private String unid;


    /**
     * 接口名称
     */
    private String interfaceName;


    /**
     * 菜单名称
     */
    private String menu;


    /**
     * 访问内容
     */
    private String content;


    /**
     * 操作结果 0:失败,1:成功
     */
    private Boolean sucessFlag;


    /**
     * 访问时间
     */
    private Date operateTime;


    /**
     * 类名
     */
    private String className;


    /**
     * 方法名
     */
    private String methodName;


    /**
     * 参数
     */
    private String params;


    /**
     * 请求地址URL
     */
    private String requestUrl;


    /**
     * 请求ip
     */
    private String requestIp;


    /**
     * 请求耗时
     */
    private Integer timeConsum;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getParams() {
        return params;
    }

    public void setParams(String params) {
        this.params = params;
    }

    public String getRequestUrl() {
        return requestUrl;
    }

    public void setRequestUrl(String requestUrl) {
        this.requestUrl = requestUrl;
    }

    public String getRequestIp() {
        return requestIp;
    }

    public void setRequestIp(String requestIp) {
        this.requestIp = requestIp;
    }

    public Integer getTimeConsum() {
        return timeConsum;
    }

    public void setTimeConsum(Integer timeConsum) {
        this.timeConsum = timeConsum;
    }

    public String getUnid() {
        return unid;
    }

    public void setUnid(String unid) {
        this.unid = unid;
    }

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public String getMenu() {
        return menu;
    }

    public void setMenu(String menu) {
        this.menu = menu;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Boolean getSucessFlag() {
        return sucessFlag;
    }

    public void setSucessFlag(Boolean sucessFlag) {
        this.sucessFlag = sucessFlag;
    }

    public Date getOperateTime() {
        return operateTime;
    }

    public void setOperateTime(Date operateTime) {
        this.operateTime = operateTime;
    }

    @Override
    public String toString() {
        return "JgCompanyOperateLog{" +
                "unid=" + unid +
                ", interfaceName=" + interfaceName +
                ", menu=" + menu +
                ", content=" + content +
                ", sucessFlag=" + sucessFlag +
                ", operateTime=" + operateTime +
                "}";
    }
}

切面类

package com.example.reactboot.aop;


import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
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.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.UUID;

/**
 * 系统日志:切面处理类
 *
 * @Aspect:声明该类为一个注解类;
 * @Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为某个 package 下的方法,也可以是自定义注解等;
 * <p>
 * 切点定义好后,就是围绕这个切点做文章了:
 * @Before: 在切点之前,织入相关代码;
 * @After: 在切点之后,织入相关代码;
 * @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
 * @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
 * @Around: 在切入点前后织入代码,并且可以自由的控制何时执行切点;
 */
@Aspect
@Component
@Order(1)
public class WebLogAspect {

    private final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    /**
     * execution 表达式
     * 可以基于方法的返回类型、包名、类名、方法名以及参数类型等信息来精确地匹配方法.
     * 以下这个例子:匹配了 com.example.reactboot 包及其子包中所有类的所有方法。
     */
    //@Pointcut("execution(* com.example.reactboot.*.*(..))")
    @Pointcut("execution(public * com.example.reactboot.controller..*.*(..))")
    public void webLog() {
    }

    /**
     * @annotation 表达式
     * 用于匹配被指定注解标注的方法。
     * 以下这个例子:匹配了所有被 com.example.reactboot.aop.Aoplog 注解标注的方法。
     */
    @Pointcut("@annotation(com.example.reactboot.aop.Aoplog)")
    public void aopLog() {
    }

    /**
     * 切点之前
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before("webLog()")
    public void before(JoinPoint joinPoint) {
        logger.info("============ 切点之前(@Before)==========");
    }

    /**
     * 切点之后
     *
     * @throws Throwable
     */
    @After("webLog()")
    public void after() {
        logger.info("============ 切点后执行(@After) ==========");
    }

    /**
     * 切点返回内容后
     *
     * @throws Throwable
     */
    @AfterReturning("webLog()")
    public void afterReturning() {
        logger.info("============ 切点返回内容后执行(@AfterReturning) ==========");
    }

    /**
     * 切点抛出异常后
     *
     * @throws Throwable
     */
    @AfterThrowing("webLog()")
    public void afterThrowing() {
        logger.info("============ 切点抛出异常后执行(@AfterThrowing) ==========");
    }

    /**
     * 环绕
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("============ 环绕(@Around) ==========");
        return logAround(joinPoint);
    }

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private Object logAround(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = null;
        Exception exception = null;
        try {
            result = point.proceed();
        } catch (Exception exp) {
            exception = exp;
        }
        //目标方法完成时间
        long time = System.currentTimeMillis() - beginTime;
        saveLog(point, result, exception, time);
        if (exception != null) {
            throw exception;
        }

        return result;
    }

    /**
     *
     * @param joinPoint
     * @param result 目标方法返回结果
     * @param exception 目标方法返回异常
     * @param time 目标方法完成时间
     */
    private void saveLog(ProceedingJoinPoint joinPoint, Object result, Exception exception, long time) {
        JgCompanyOperateLog dto = new JgCompanyOperateLog();
        dto.setUnid(String.valueOf(UUID.randomUUID()));
        dto.setTimeConsum(Math.toIntExact(time));
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("============ 执行point.proceed之后的操作(@Around) ==========");
        // 请求地址URL
        logger.info("URL:{}" ,request.getRequestURL().toString());
        // 请求方法
        logger.info("HTTP Method : {}" , request.getMethod());
        // 请求IP
        logger.info("IP:{}" , request.getRemoteAddr());
        dto.setRequestUrl(request.getRequestURL().toString());
        dto.setMethodName(request.getMethod());
        dto.setRequestIp(request.getRemoteAddr());
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //请求的 类名、方法名
            String className = joinPoint.getTarget().getClass().getName();
            String signName = signature.getDeclaringTypeName();
            if (!signName.equalsIgnoreCase(className)) {
                signName += "|" + className;
            }
            logger.info("类名 : {}" , className);
            logger.info("方法名 : {}" , signName);
            logger.info("接口名称 : {}" , signature.getName());
            logger.info("访问时间 : {}" , new Date());
            dto.setClassName(className);
            dto.setMethodName(signName);
            dto.setOperateTime(new Date());
            String methodName = signature.getName();
            dto.setInterfaceName(methodName);

            //请求的参数
            Object[] args = joinPoint.getArgs();
            if (args != null && args.length > 0) {
                dto.setParams(serial(args));
            }

        } catch (Exception e) {
            dto.setContent(e.toString());
        }
        logger.info("存储的日志对象 : {}",dto.toString());
        //进行数据库保存
        //jgCompanyOperateLogFeign.saveJgCompanyOperateLog(dto);
    }

    private static String serial(Object obj) {
        try {
            //用于序列化Java对象为JSON格式字符串
            return MAPPER.writeValueAsString(obj);
        } catch (Exception ex) {
            return obj.toString();
        }
    }

}


(四)AOP如何修改入参、返参

主要是用到环绕@Around

/**
     * 环绕
     * 环绕执行,就是在调用目标方法之前和调用之后,都会执行一定的逻辑
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        
        long startTime = System.currentTimeMillis();
        logger.info("@Around中的{}", startTime);
        
        //获取目标方法的入参
        Object[] args = proceedingJoinPoint.getArgs(); 
        for (int i = 0; i < args.length; i++) {
            logger.info("argsName: "+args[i]); //输出目标方法的参数
            if(i==0){
                args[i]="无语123";
            }
        }
        // 这个proceed就需要把入参设置进去
        Object result = proceedingJoinPoint.proceed(args);
        
         //根据原方法返回值的类型,进行修改
        if (result instanceof String) {
            result = "我把你的值给改了,哈哈哈哈"; 
        }
        if (result instanceof UserBean) {
            UserBean entity = (UserBean) result;
            entity.setAddress("我把你的值给改了,哈哈哈哈");
            result = entity; 
        }
        // 打印出参
        logger.info("@Around中出参Response Args  : {}", result);
        // 执行耗时
        logger.info("@Around中执行耗时Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
        return result;
    }

接口请求为http://localhost:9021/loginIn?name=项目&age=22

返回结果为
可以看到 name 入参被改了,同时返回值的address被加上了值
在这里插入图片描述

参考文章
【1】Spring Boot AOP 切面统一打印请求与响应日志
【2】Spring Boot 2.X(八):Spring AOP 实现简单的日志切面
【3】在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
【4】springboot项目使用切面记录用户操作日志
【5】Spring Boot中使用AOP统一处理Web请求日志

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2058623.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

外部环境连接kafka

修改配置文件外部环境连接kafka 1、kafka的docker官方镜像地址2、kafka官方介绍的三种连接方式3、方式一&#xff1a;Default configs默认配置4、方式二&#xff1a;File input&#xff08;文件输入&#xff1a;外部配置文件替换docker容器内的配置文件&#xff09;4.1、首先查…

PHP导出生成PDF文件开源组件:mPDF使用详情

最近在使用php要实现一个把旅游线路导出成pdf文件下载&#xff0c;在全网搜索了一遍有几个常用的开源组件&#xff0c;在PHP中生成PDF文件&#xff0c;比如FPDF、TCPDF、mPDF等。在对比了一圈后就 mPDF开源地址&#xff1a; https://github.com/mpdf/mpdf mPDF版本说明 PHP…

【学术会议征稿】2024年计算机与信息安全国际会议(WCCIS 2024)

2024年计算机与信息安全国际会议&#xff08;WCCIS 2024&#xff09; 2024 World Conference of Computer and Information Security 由马来亚大学主办&#xff0c;2024年计算机与信息安全国际会议 (WCCIS 2024) 将于2024年9月20-22日在马来西亚召开。会议旨在为从事计算机与…

一种用于治疗性肽生成的多模态对比扩散模型

文章介绍了一种名为Multi-Modal Contrastive Diffusion (MMCD)的新模型&#xff0c;该模型旨在通过融合肽的序列和结构信息来生成治疗性肽。MMCD利用了一个扩散框架&#xff0c;并在每个扩散时间步中采用了跨模态和模态内的对比学习策略&#xff0c;以捕捉序列和结构之间的一致…

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE啦&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好习惯&#…

消费品行业经销商渠道数据同步及管理究竟如何执行?

在端到端的时代&#xff0c;消费品行业供应链主要由厂商、经销商和终端构成。然而&#xff0c;厂商在对经销商管理过程中&#xff0c;常常被库存问题所困扰。为确保经销商缺货时能随时供货&#xff0c;厂商往往提前备足产品&#xff0c;维持高库存量。但一旦经销商实际卖货能力…

测量 Redis 服务器的固有延迟

redis-cli --intrinsic-latency redis-cli --intrinsic-latency 命令用于测量 Redis 服务器的固有延迟。 固有延迟指的是 Redis 服务器处理一个命令所需的最短时间&#xff0c;不包括网络延迟。通过这个测量&#xff0c;我们可以了解 Redis 服务器本身的性能&#xff0c;而不…

图像生成模型基础——Stable Diffusion模型介绍

随着人工智能技术的飞速发展&#xff0c;图像生成技术也取得了显著进步。扩散模型&#xff08;Stable Diffusion&#xff09;因其高效性和稳定性而得到广泛关注&#xff0c;目前的大多数生成模型都是以扩散模型为基础进行改进得到。首先简单介绍一下传统人工智能模型和生成模型…

网络安全防渗透实战指南【策略、代码与最佳实践】

网络安全防渗透实战指南【策略、代码与最佳实践】 引言 随着互联网的迅猛发展&#xff0c;网络安全问题日益突出。渗透攻击作为网络攻击的一种常见手段&#xff0c;给企业和个人带来了巨大的威胁和损失。因此&#xff0c;如何有效防止渗透攻击成为网络安全领域的重要课题。本…

企业高性能web服务器知识点合集

文章目录 nginx源码编译安装平滑升级及版本回滚平滑升级版本回滚 服务启动脚本核心配置全局配置参数优化调整root与alias自定义错误日志自定义错误页面检测文件是否存在长链接配置下载服务器的配置 nginx高级配置nginx状态页面压缩功能变量内置变量自定义变量 nginx rewrite指令…

Prometheus 服务发现

1、基于文件的服务发现 基于文件的服务发现是仅仅略优于静态配置的服务发现方式&#xff0c;它不依赖于任何平台或第三方服务&#xff0c;因而也是最为简单和通用的实现方式。 Prometheus Server 会定期从文件中加载 Target 信息&#xff0c;文件可使用 YAML 和 JSON 格式&…

实战项目:自主HTTP服务器

0. 项目介绍 本项目实现的是一个HTTP服务器&#xff0c;项目中将会通过基本的网络套接字读取客户端发来的HTTP请求并进行分析&#xff0c;最终构建HTTP响应并返回给客户端完成了HTTP服务器后端的处理逻辑&#xff0c;主要完成的是GET和POST请求方法&#xff0c;以及CGI机制的搭…

深度学习设计模式之享元设计模式

文章目录 前言一、介绍二、特点三、详细介绍1.核心组成2.代码示例3.优缺点优点缺点 4.使用场景 总结 前言 享元设计模式主要用于减少创建对象的数量&#xff0c;以减少内存占用&#xff0c;提高性能。 一、介绍 享元设计模式&#xff08;Flyweight Pattern&#xff09;是一种…

c语言练习题1

1.输出Helloword /*输出Helloword*/ #include<stdio.h> int main() {printf("Hello word!");return 0; }2.整型变量的定义与使用 /*整型变量的定义与使用*/ #include <stdio.h> int main() {int a;int b;a 10;b 20;int c a b;int d a - b;printf(…

ETAS工具链自动化实战指南<一>

----自动化不仅是一种技术&#xff0c;更是一种思维方式&#xff0c;它将帮助我们在快节奏的工作环境中保持领先&#xff01; 目录 往期推荐 场景一&#xff1a;SWC 之间 port自动连接 命令示例 参数说明 场景二&#xff1a;SWC与ECU 自动映射 命令示例 参数说明 场景三&…

叉车刷卡系统,IC卡授权驾驶,提高叉车管理效率!

叉车作为仓储物流行业的主力军&#xff0c;长期占据着物流运输的主导地位。但由于厂区内叉车数量庞大&#xff0c;作为重型的特种设备&#xff0c;往往很容易发生碰撞事故。数量庞大且容易发生事故的叉车很难集中管理、叉车运输环境的复杂加之管理员不可能24小时全面监管工作。…

图表数据自动化软件有哪些?图表数据自动化怎么做

在数字化时代&#xff0c;数据的呈现方式越来越多样化&#xff0c;图表成为了表达复杂信息、数据趋势和分析结果的有效工具。然而&#xff0c;随着数据量的激增&#xff0c;手动创建和更新图表变得既耗时又低效。因此&#xff0c;图表数据自动化软件的应用变得尤为重要。这些软…

如何在没有密码的情况下解锁Oppo手机?5 种简单的方法

保护智能手机隐私的一种绝佳方法是设置复杂的锁屏密码或图案。一些OPPO手机的所有者在更改图案或密码后&#xff0c;在一夜之间失去了对其图案或密码的内存。事实上&#xff0c;OPPO用户遇到的众多问题包括忘记密码或锁定屏幕。遗憾的是&#xff0c;没有多少人知道无需密码即可…

在Chrome浏览器中安装JSON显示插件

步骤1&#xff1a; 在浏览器中打开https://github.com&#xff0c;在顶部的搜索栏中输入“chrome json”并开始搜索 输入关键字时&#xff0c;使用浏览器名称与“json”作为关键字&#xff0c;且中间使用空格进行分隔。 步骤2&#xff1a; 在搜索结果中选择第1个项目 在githu…

UE5用蓝图实现物体A始终朝向物体B |Find Look at Rotation|

非常常用的蓝图节点 |Find Look at Rotation|&#xff1a;获取 物体A 到 物体B 的Rotator。 Tick中将算出的Rotator设置给物体A&#xff0c;即可实现永远朝向物体B