SpringBoot利用AOP输出请求相应日志,支持输出@RequestBody中的参数

CodeLavenderR
• 阅读 2340

添加依赖

视情况添加spring-boot-starter-aop依赖。

配置AOP切面

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

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.OutputStream;

//@Component
//@Aspect
@Slf4j
public class WebLogAspect {
    @Autowired
    protected ObjectMapper objectMapper;

    //    @Pointcut("execution(public * com.guomz.shangyitong.controller..*.*(..))" +
//            "&& !execution(public * com.guomz.shangyitong.cmn.controller.DictController.exportDict(..))" +
//            "&& !execution(public * com.guomz.shangyitong.cmn.controller.DictController.importData(..))")
    public void webLog(){
    }

    /**
     * 在切点之前织入
     * @param joinPoint
     * @throws Throwable
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 打印请求相关参数
        log.info("========================================== Start ==========================================");
        // 打印请求 url
        log.info("URL            : {}", request.getRequestURL().toString());
        // 打印 Http method
        log.info("HTTP Method    : {}", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        // 打印请求的 IP
        log.info("IP             : {}", request.getRemoteAddr());
        //输出cookie
        printCookie(request);
        // 输出url请求入参
        log.info("queryString    : {}", request.getQueryString());
        // 打印请求入参
        if (joinPoint.getArgs() != null){
            logInputArgs(joinPoint.getArgs());
        }
    }
    /**
     * 在切点之后织入
     * @throws Throwable
     */
    @After("webLog()")
    public void doAfter() throws Throwable {
        log.info("=========================================== End ===========================================");
        // 每个请求之间空一行
        log.info("");
    }
    /**
     * 环绕
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        // 打印出参
        if (result != null && !(result instanceof OutputStream)){
            log.info("Response Args  : {}", objectMapper.writeValueAsString(result));
        }
        // 执行耗时
        log.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
        return result;
    }

    /**
     * 输出cookie信息
     * @param request
     */
    protected void printCookie(HttpServletRequest request){
        if (request.getCookies() != null){
            for (Cookie cookie : request.getCookies()) {
                log.info("cookie: {}, {}", cookie.getName(), cookie.getValue());
            }
        }else {
            log.info("无cookie信息");
        }
    }

    /**
     * 输出入参
     * @param args
     * @throws JsonProcessingException
     */
    protected void logInputArgs(Object args[]) throws JsonProcessingException {
        for (Object arg : args) {
            if (arg instanceof MultipartFile ||
            arg instanceof HttpServletRequest){
                continue;
            }
            log.info("Request Args   : {}", objectMapper.writeValueAsString(arg));
        }
    }
}

在切点注解中对controller包下的全部方法进行了植入,JSONUtil为自己实现的json序列化工具。printCookie()方法为输出请求中的cookie信息,hasMultipartInput()为判断请求参数中是否包含流,包含流则无法被序列化。

注意

如果该类被多个项目作为依赖引用,则需要注意@Pointcut注解配置的类与方法是否在当前项目中存在,不存在会启动报错。可以公共提取一个切面,然后在不同项目中继承重写被标注@Pointcut的方法,进行切点的自定义。上面的示例中这个类是一个基类,在其他模块中可以再创建其他切面类去继承。
如果controller方法存在使用HttpServletResponse.getOutputStream()进行文件下载,则需要进行方法排除不进行拦截,否则会报错。
本文引用自:
Spring Boot AOP 切面统一打印请求与响应日志

点赞
收藏
评论区
推荐文章
lucien-ma lucien-ma
4年前
注解和反射
注解和反射1.注解1.1什么是注解?注解和注释的差别在于注解可以被其他程序读取1.2内置注解@Override定义在java.lang.Override中,表示一个方法声明打算重写超类中的另一个方法声明@Deprecated定义在java.lang.Deprecated中,此注解可以用于修辞方法,属性,类,表示不鼓励程序员使用这样的
Stella981 Stella981
4年前
Spring AOP @Aspect 基本用法
Spring使用的AOP注解分为三个层次:前提条件是在xml中放开了<aop:aspectjautoproxyproxytargetclass"true"/<!开启切面编程功能1、@Aspect放在类头上,把这个类作为一个切面。2、@Pointcut放在方法头上,定义一个可被别的方法引用的切入点
Stella981 Stella981
4年前
SpringAOP
Aspect切面:一个关注点的模块化,这个关注点可能会横切多个对象Joinpoint连接点:程序执行过程中的某个特定的点Advice通知:在切面的某个连接点上执行的动作Pointcut切入点:匹配连接点的断言,在AOP的通知和一个切入点表达式关联Introduction引入:在不修改类代码的前提下,为类添加新的方法
Stella981 Stella981
4年前
SpringBoot自定注解+SpringAop实现操作日志
使用自定义注解SpringAop实现操作日志记录,记录内容包括请求参数、请求方法、请求响应时间等的记录。一、在pom文件中,引入依赖包和插件。<dependency<groupIdorg.springframework.boot</groupId<artifactIdspringbootsta
Wesley13 Wesley13
4年前
Java——基于AspectJ的AOP开发
1.AspectJ简介AspectJ是一个基于Java语言的AOP框架。Spring2.0以后新增了对AdpectJ切点表达式的支持。@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。新版本Spring框架,建议使用AspectJ方式来开发AOP。使用AspectJ需要导
Easter79 Easter79
4年前
Spring中的AOP(二)——AOP基本概念和Spring对AOP的支持
AOP的基本概念    AOP从运行的角度考虑程序的流程,提取业务处理过程的切面。AOP面向的是程序运行中的各个步骤,希望以更好的方式来组合业务逻辑的各个步骤。AOP框架并不与特定的代码耦合,AOP框架能处理程序执行中特定切入点,而不与具体某个类耦合(即在不污染某个类的情况下,处理这个类相关的切点)。下面是一些AOP的一些术语:    切面(
Easter79 Easter79
4年前
SpringCloud学习笔记(五)之Feign负载均衡
是什么是一个声明式WebService客户端。使用Feign能让编写WebService客户端更加简单,使用方法是定义一个接口,然后在上面添加注解,同时也支持JAXRS标准的注解。Feign也支持可拔插式的编码器和解码器。SpringCloud对Feign进行了封装,使其支持了SpringM
Easter79 Easter79
4年前
SpringBoot自定注解+SpringAop实现操作日志
使用自定义注解SpringAop实现操作日志记录,记录内容包括请求参数、请求方法、请求响应时间等的记录。一、在pom文件中,引入依赖包和插件。<dependency<groupIdorg.springframework.boot</groupId<artifactIdspringbootsta
Easter79 Easter79
4年前
SpringBoot2.0笔记四
当搞全局捕获异常时可以使用到AOP技术,采用异常通知,也可以用AOP搞日志记录在类上面加上@EnableAsyns注解开启异步调用@Asyns,在方法上加上此注解,可以实现异步调用,底层是多线程技术,相当于加上这个注解的方法重新开启了一个单独的线程正常情况下,当A方法调用B方法时,是需要B方法执行完成,有返回结果时等待返回。这是顺序的方式从上到下
Stella981 Stella981
4年前
Spring3核心技术之AOP配置
在Spring配置文件中,所有AOP相关定义必须放在<aop:config标签下,该标签下可以有<aop:pointcut、<aop:advisor、<aop:aspect标签,配置顺序不可变。!(http://static.oschina.net/uploads/img/201511/25003650_G0NP.jpg)●
liam liam
2年前
必知的 Spring Boot 常用注解:一文读懂原理与实战
SpringBoot中有许多常用的注解,这些注解用于配置、管理和定义SpringBoot应用程序的各个方面。以下是这些注解按大类和小类的方式分类,并附有解释和示例。一、SpringBoot核心注解@SpringBootApplication解释:这是一个组