SpringBoot使用AOP打印http接口请求和响应

发布于:2025-08-04 ⋅ 阅读:(18) ⋅ 点赞:(0)
打印效果
  • 正常响应时的日志输出
[2025-08-02 23:58:36.717][http-nio-8181-exec-1][org.learn.goodnight.aop.HttpLogAspect][INFO] - 收到请求[UserController.login], 来源IP:127.0.0.1, 请求URL:/user/login, HTTP方法:POST, 请求参数:{"username":"Job","password":"123"}
[2025-08-02 23:58:36.721][http-nio-8181-exec-1][org.learn.goodnight.aop.HttpLogAspect][INFO] - 请求成功[UserController.login], 响应结果:{"code":"00000000","message":"登录成功!","username":"Job"}
[2025-08-03 00:00:27.326][http-nio-8181-exec-3][org.learn.goodnight.aop.HttpLogAspect][INFO] - 收到请求[UserController.queryNickname], 来源IP:127.0.0.1, 请求URL:/user/queryNickname, HTTP方法:GET, 请求参数:["xx",18]
[2025-08-03 00:00:27.336][http-nio-8181-exec-3][org.learn.goodnight.aop.HttpLogAspect][INFO] - 请求成功[UserController.queryNickname], 响应结果:{"code":"00000000","message":"查询昵称成功!","username":"xx","age":18,"nickname":"周博"}
[2025-08-03 00:00:29.825][http-nio-8181-exec-4][org.learn.goodnight.aop.HttpLogAspect][INFO] - 收到请求[UserController.queryAge], 来源IP:127.0.0.1, 请求URL:/user/queryAge/job, HTTP方法:GET, 请求参数:job
[2025-08-03 00:00:29.827][http-nio-8181-exec-4][org.learn.goodnight.aop.HttpLogAspect][INFO] - 请求成功[UserController.queryAge], 响应结果:{"code":"00000000","message":"查询年龄成功!","username":"job","age":18}

  • 出现异常时的日志输出
[2025-08-03 00:17:30.949][http-nio-8181-exec-1][org.learn.goodnight.aop.HttpLogAspect][INFO] - 收到请求[UserController.login], 来源IP:127.0.0.1, 请求URL:/user/login, HTTP方法:POST, 请求参数:{"username":"Job","password":"123"}
[2025-08-03 00:17:30.952][http-nio-8181-exec-1][org.learn.goodnight.aop.HttpLogAspect][INFO] - 请求失败[UserController.login], 出现异常:
java.lang.ArithmeticException: / by zero
...
[2025-08-03 00:17:30.957][http-nio-8181-exec-1][org.learn.goodnight.exception.GlobalExceptionHandler][ERROR] - 全局异常拦截器生效, 请求[UserController.login, URL:/user/login, HTTP方法:POST]时出现异常, 响应结果:{"code":"99999999","message":"系统异常"}, 异常是
java.lang.ArithmeticException: / by zero
...
0.依赖
<!--web依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.7.13</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--日志打印-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <version>2.7.13</version>
</dependency>
<!-- aop依赖 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
  <version>2.7.13</version>
</dependency>
<!-- hutools依赖,用来格式化json -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.38</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
1.Controller层
import org.learn.goodnight.aop.HttpLog;
import org.learn.goodnight.pojo.UserDTO;
import org.learn.goodnight.pojo.UserVO;
import org.learn.goodnight.service.SleepService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("user")
public class UserController {
    @Autowired
    SleepService sleepService;

    @PostMapping("login")
    @HttpLog
    public UserVO login(@RequestBody UserDTO userDTO){
        UserVO userVO = new UserVO();
        int i = 1/0;
        userVO.setCode("00000000").setMessage("登录成功!").setUsername(userDTO.getUsername());
        return userVO;
    }

    @GetMapping("queryNickname")
    @HttpLog
    public UserVO queryNickname(@RequestParam String username, Integer age){
        UserVO userVO = new UserVO();
        userVO.setCode("00000000").setMessage("查询昵称成功!").setUsername(username).setAge(age).setNickname("周博");
        return userVO;
    }

    @GetMapping("queryAge/{username}")
    @HttpLog
    public UserVO queryAge(@PathVariable String username){
        UserVO userVO = new UserVO();
        userVO.setCode("00000000").setMessage("查询年龄成功!").setUsername(username).setAge(18);
        return userVO;
    }
}
2.自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpLog {
}
3.切面类
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

@Configuration
@EnableAspectJAutoProxy
@Aspect
@Slf4j
public class HttpLogAspect {

    private static final String POINT_CUT = "@annotation(org.learn.goodnight.aop.HttpLog)";

    @Pointcut(POINT_CUT)
    public void pointCut() {}

    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Signature signature = joinPoint.getSignature();
        //joinPoint.getArgs().length==1?joinPoint.getArgs()[0] 可以避免入参为@RequestBody时,打印出的参数最外层是[]。
        String jsonParam = JSONUtil.toJsonStr(joinPoint.getArgs().length == 1 ? joinPoint.getArgs()[0] : joinPoint.getArgs());
        log.info("收到请求[{}.{}], 来源IP:{}, 请求URL:{}, HTTP方法:{}, 请求参数:{}",
                signature.getDeclaringType().getSimpleName(),
                signature.getName(),
                request.getRemoteAddr(),
                request.getRequestURI(),
                request.getMethod(),
                jsonParam);
    }
    @AfterReturning(pointcut = "pointCut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        Signature signature = joinPoint.getSignature();
        log.info("请求成功[{}.{}], 响应结果:{}",signature.getDeclaringType().getSimpleName(), signature.getName(), JSONUtil.toJsonStr(result));
    }
    @AfterThrowing(pointcut = "pointCut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        Signature signature = joinPoint.getSignature();
        log.info("请求失败[{}.{}], 出现异常:",signature.getDeclaringType().getSimpleName(), signature.getName(),e);
    }
}
4.添加全局异常处理器

添加全局异常处理器后,出现的异常会被异常处理器拦截,然后标准化响应输出给客户端。由于@AfterThrowing只能打印出异常,无法打印返回给客户端的响应信息。所以我们需要在异常处理类中打印是哪个controller出现的异常,以及响应给客户端的内容是什么。

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.learn.goodnight.pojo.Response;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.HandlerMethod;

import javax.servlet.http.HttpServletRequest;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public Response handleException(HttpServletRequest request, HandlerMethod handlerMethod, Exception ex){
        Response response = Response.builder().code("99999999").message("系统异常").build();
        String className = handlerMethod.getBeanType().getSimpleName();  // Controller 类名
        String methodName = handlerMethod.getMethod().getName();   // Controller 方法名
        log.error("全局异常拦截器生效, 请求[{}.{}, URL:{}, HTTP方法:{}]时出现异常, 响应结果:{}, 异常是",
                className, methodName, request.getRequestURI(), request.getMethod(), JSONUtil.toJsonStr(response), ex);
//        ex.printStackTrace();
        return response;
    }
}
附log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--status="OFF"-->
<!--monitorInterval="30" 每30秒自动检测配置文件变化-->
<Configuration status="OFF" monitorInterval="30">
    <Appenders>
        <!-- 控制台输出 -->
        <Console name="Console">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}][%t][%c][%p] - %m%n"/>
        </Console>
        <!-- 滚动文件输出(按大小和日期分割) -->
        <!--fileName用作当前正在写入的活跃日志文件路径, filePattern仅用于生成归档日志文件路径-->
        <RollingFile name="File" fileName="logs/app.log"
                     filePattern="logs/app-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout>
                <Pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}][%t][%c][%p] - %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="File"/>
        </Logger>

        <!--控制mybatis sql打印, debug级别及以下会打印sql-->
        <!--additivity="false",日志会打印两遍,一遍由root打印, 一遍由此Logger打印-->
        <Logger name="org.learn.goodnight.dao" level="debug" additivity="false">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="File"/>
        </Logger>

        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="File"/>
        </Root>
    </Loggers>
</Configuration>

网站公告

今日签到

点亮在社区的每一天
去签到