AOP方式配置日志,打印uri、url、入参参数名、入参参数值、返回结果。

本文介绍了如何在Spring应用中通过Maven配置依赖,使用javassist处理参数并结合Gson库,实现统一的日志打印。重点展示了AOP切面的配置和使用,以及如何在前后方法调用前后获取和打印参数信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、maven的pom配置,javassist和gson的jar依赖。

<!--GSON  add by yxt 2020.10.15 -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.6.2</version>
</dependency>

<!--javassist  add by yxt 2020.10.15 -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.20.0-GA</version>
</dependency>

2、java的aop配置代码:


package com.brilliance.avictc.wealth.filter;

import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

import java.lang.reflect.Method;

import com.google.gson.Gson;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @describe: 统一处理打印入参到日志
 * @date: 2020-10-15
 * @author: yxt
 */
@Component  //声明组件
@Aspect //  声明切面
@ComponentScan  //组件自动扫描
@EnableAspectJAutoProxy //spring自动切换JDK动态代理和CGLIB
public class LoggerByAop {

    /**
     *自定义日志
     */
    private Logger logger = LoggerFactory.getLogger(LoggerByAop.class);

    /**
     * Before : 在方法执行前进行切面
     * execution : 定义切面表达式,多个用||分隔
     * public * com.eparty.ccp.*.impl..*.*(..)
     * *  *..*.*.controller..*.*(..)
     * public :匹配所有目标类的public方法,不写则匹配所有访问权限
     * 第一个* :方法返回值类型,*代表所有类型
     * 第二个* :包路径的通配符
     * 第三个..* :表示impl这个目录下所有的类,包括子目录的类
     * 第四个*(..) : *表示所有任意方法名,..表示任意参数
     * @param point 切面
     */
    @Before("execution(public * com.brilliance.avictc.wealth.controller.*.*(..)) || execution(* com.brilliance.avictc.wealth.service.UAPApiService.*(..))||execution(public * com.brilliance.avictc.wealth.property.controller.*.*(..))")
    public void before(JoinPoint point) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime",startTime);
        this.printMethodParams(point);
    }

    /**
     * 打印类method的名称以及参数
     * @param point 切面
     */
    public void printMethodParams(JoinPoint point){
        if(point == null){
            return;
        }
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        // long costTime = System.currentTimeMillis() - (long) request.getAttribute("startTime");
        //String url = request.getRequestURL().toString();
        String uri = request.getRequestURI();
        // logger.info("uri为:{}___{},————————总耗时:{}",uri,endTime-startTime);
         // Signature 包含了方法名、申明类型以及地址等信息
        String class_name = point.getTarget().getClass().getName();
        String method_name = point.getSignature().getName();
        //重新定义日志
        logger = LoggerFactory.getLogger(point.getTarget().getClass());
         // 获取方法的参数值数组。
        Object[] method_args = point.getArgs();
        try {
             // 获取方法参数名称
            Map<Object, Object> paramMap = new HashMap<>();
            Map<String, String[]>maps =  request.getParameterMap();
            if(maps != null){
                for (Map.Entry<String, String[]> entry : maps.entrySet()) {
                    paramMap.put(entry.getKey(),Arrays.toString(entry.getValue()));
//                param = entry.getKey() + ":"+Arrays.toString(entry.getValue())+";";
                }
            }
            String param = paramMap.toString();
            //String[] paramNames = getFieldsName(class_name, method_name);
            String[] paramNamesCrm = getFieldsName(class_name, method_name);
             // 打印方法的参数名和参数值
            String paramCrm = logParam(paramNamesCrm, method_args);
            if("com.brilliance.avictc.wealth.service.UAPApiService".equals(class_name)){
                logger.info("..... 调用crm接口入参信息 .....:uri:{},{}.{}:{}" , uri, class_name, method_name, paramCrm);
            }else{
                logger.info("***** 客户端接口入参信息 *****:uri:{},{}.{}:{}",uri, class_name, method_name, param);
            }

        } catch (Exception e) {
            logger.error("切面获取入参的参数名和参数值时,发生异常,异常信息为:"+e);
        }
    }

    /**
     * 使用javassist来获取方法参数名称
     * @param class_name    类名
     * @param method_name   方法名
     * @return
     * @throws Exception
     */
    private String[] getFieldsName(String class_name, String method_name) throws Exception {
        Class<?> clazz = Class.forName(class_name);
        String clazz_name = clazz.getName();
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(clazz);
        pool.insertClassPath(classPath);

        CtClass ctClass = pool.get(clazz_name);
        CtMethod ctMethod = ctClass.getDeclaredMethod(method_name);
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if(attr == null){
            return null;
        }
        String[] paramsArgsName = new String[ctMethod.getParameterTypes().length];
        int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
        for (int i=0;i<paramsArgsName.length;i++){
            paramsArgsName[i] = attr.variableName(i + pos);
        }
        return paramsArgsName;
    }

    /**
     * 判断是否为基本类型:包括String
     * @param clazz clazz
     * @return  true:是;     false:不是
     */
    private boolean isPrimite(Class<?> clazz){
        if (clazz.isPrimitive() || clazz == String.class){
            return true;
        }else {
            return false;
        }
    }


    /**
     * 打印方法参数值  基本类型直接打印,非基本类型需要重写toString方法
     * @param paramsArgsName    方法参数名数组
     * @param paramsArgsValue   方法参数值数组
     */
    private String logParam(String[] paramsArgsName,Object[] paramsArgsValue){
        String resultStr = "";
        if(ArrayUtils.isEmpty(paramsArgsName) || ArrayUtils.isEmpty(paramsArgsValue)){
            logger.info("该方法没有参数");
            return resultStr;
        }
        // StringBuffer buffer = new StringBuffer();
        Map<String, Object> argsMap = new HashMap<>();
        for (int i=0;i<paramsArgsName.length;i++){
            //参数名
            String name = paramsArgsName[i];
            //参数值
            Object value = paramsArgsValue[i];
            argsMap.put(name,value);
            // buffer.append(name +" = ");
//            if(isPrimite(value.getClass())){
//                buffer.append(value + "  ,");

//            }else {
//                buffer.append(value.toString() + "  ,");
//            }
        }
        resultStr = argsMap.toString();
        return resultStr;
    }

    // 统一切点,对com.hangtian.admin.controller及其子包中所有的类的所有方法切面
    @Pointcut("execution(public * com.brilliance.avictc.wealth.controller.*.*(..)) || execution(* com.brilliance.avictc.wealth.service.UAPApiService.*(..))||execution(public * com.brilliance.avictc.wealth.property.controller.*.*(..))")
     public void Pointcut() {
    }

    @AfterReturning(value="Pointcut()",returning="rvt")
     public void after(JoinPoint joinPoint,Object rvt){
         MethodSignature sig1 = (MethodSignature)joinPoint.getSignature();
         Method method1 = sig1.getMethod();
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        long costTime = System.currentTimeMillis() - (long) request.getAttribute("startTime");
         if(null != rvt && null != method1.getDeclaringClass()){
             try{
                 if("com.brilliance.avictc.wealth.service.UAPApiService".equals(method1.getDeclaringClass().getName())){
                     logger.info("----- 调用crm接口返回结果 -----:time:{},{}.{}:{}" ,costTime, method1.getDeclaringClass().getName(),method1.getName(),new Gson().toJson(rvt));
                 }else{
                     logger.info("===== 客户端返结果为 =====:time:{},{}.{} :{}",costTime, method1.getDeclaringClass().getName(),method1.getName(),new Gson().toJson(rvt));
                 }
             }catch (Exception e){
                 logger.error("切面获取返回值时,发生异常,异常信息为:"+e);
             }
         }
    }
}

 

<think>我们正在使用Spring Boot AOP实现接口请求日志记录。根据引用内容,我们可以总结出以下步骤: 1. 添加依赖:在`pom.xml`中添加Spring Boot AOP依赖。 2. 创建切面类:使用`@Aspect`注解标记的类,定义切面。 3. 定义切点:通过表达式指定哪些方法需要记录日志(例如,所有Controller中的方法)。 4. 编写通知:在方法执行前后(或异常时)记录日志,可以使用`@Before`、`@After`、`@Around`等注解。 5. 获取请求信息:在通知方法中,可以通过`JoinPoint`获取方法参数等信息,也可以注`HttpServletRequest`来获取请求信息。 6. 记录日志:将获取的信息输出到日志文件或数据库。 下面我们详细说明每个步骤。 ### 1. 添加依赖 在`pom.xml`中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` ### 2. 创建切面类 创建一个类,用`@Aspect`和`@Component`注解标记,这样Spring就会将其识别为一个切面并管理。 ### 3. 定义切点 使用`@Pointcut`注解定义切点表达式,例如: ```java @Pointcut("execution(* com.example.demo.controller..*.*(..))") public void controllerPointcut() {} ``` 这个切点表示`com.example.demo.controller`包下所有类的所有方法。 ### 4. 编写通知 我们通常使用`@Around`通知,因为它可以在方法执行前后都进行操作,并且可以控制方法是否执行。但也可以使用`@Before`和`@After`。 ### 5. 获取请求信息 我们可以通过`JoinPoint`获取方法签参数等信息。同时,为了获取HTTP请求信息,我们可以注`HttpServletRequest`。 ### 6. 记录日志 在通知方法中,我们可以使用日志(如SLF4J)记录日志,也可以将日志数据库(如引用[2]所述)。 下面是一个完整的切面类示例,记录每个Controller方法的请求信息(包括请求时间、方法参数等)和耗时: ```java import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.Arrays; @Aspect @Component public class RequestLogAspect { private static final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class); // 定义切点:controller包下的所有方法 @Pointcut("execution(* com.example.demo.controller..*.*(..))") public void controllerPointcut() {} @Around("controllerPointcut()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); // 获取请求信息 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 记录请求信息 logger.info("URL: {}", request.getRequestURL().toString()); logger.info("HTTP Method: {}", request.getMethod()); logger.info("IP: {}", request.getRemoteAddr()); logger.info("Class Method: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); logger.info("Args: {}", Arrays.toString(joinPoint.getArgs())); // 执行方法 Object result = joinPoint.proceed(); // 计算耗时 long endTime = System.currentTimeMillis(); logger.info("Response: {}", result); // 注意:如果返回结果很大,可能不适合打印,可以酌情处理 logger.info("Time Cost: {} ms", endTime - startTime); return result; } } ``` 注意:上面的示例中,我们打印返回结果,这在生产环境中可能会因为返回结果过大而不适合,所以可以根据实际情况调整。 ### 扩展:记录到数据库 如果需要将日志记录到数据库,可以按照引用[2]中的方法设计数据库表,并在切面中调用Service层将日志信息保存到数据库。 ### 总结 通过以上步骤,我们可以在Spring Boot应用中利用AOP实现接口请求日志记录。这种方法非侵式,只需添加一个切面类,即可统一处理所有Controller的请求日志。 §§相关问题§§ 1. 如何优化AOP日志记录的性能? 2. 如何避免记录敏感信息的日志? 3. 如何将日志记录到数据库?</think>### Spring Boot AOP 实现接口请求日志记录方法 以下是使用 Spring Boot AOP 实现接口请求日志记录的完整步骤: --- #### 1. **添加依赖** 在 `pom.xml` 中添加 AOP 依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` --- #### 2. **创建切面类** 定义切面类处理日志记录逻辑: ```java import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.time.LocalDateTime; @Aspect @Component public class RequestLogAspect { // 定义切点:拦截所有Controller层方法 @Pointcut("execution(* com.example.controller..*.*(..))") public void requestLogPointcut() {} // 前置通知:记录请求信息 @Before("requestLogPointcut()") public void logRequest(JoinPoint joinPoint) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // 构建日志信息 String log = String.format("Request | Time: %s | IP: %s | Method: %s | URI: %s | Args: %s", LocalDateTime.now(), request.getRemoteAddr(), request.getMethod(), request.getRequestURI(), Arrays.toString(joinPoint.getArgs())); System.out.println(log); // 实际项目中替换为Logger } // 后置通知:记录响应信息 @AfterReturning(pointcut = "requestLogPointcut()", returning = "result") public void logResponse(JoinPoint joinPoint, Object result) { String log = String.format("Response | Time: %s | Method: %s | Result: %s", LocalDateTime.now(), joinPoint.getSignature().getName(), result); System.out.println(log); // 实际项目中替换为Logger } } ``` --- #### 3. **关键配置说明** - **切点表达式**:`execution(* com.example.controller..*.*(..))` 表示拦截 `com.example.controller` 包下所有类的所有方法。 - **获取请求信息**:通过 `RequestContextHolder` 获取当前 HTTP 请求对象。 - **日志内容**: - **请求日志**:时间、客户端 IP、HTTP 方法、URI参数。 - **响应日志**:时间、方法返回值。 --- #### 4. **扩展功能** ##### (1) 记录到数据库(引用[2]) 设计日志表结构: ```sql CREATE TABLE api_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, ip VARCHAR(20), method VARCHAR(10), uri VARCHAR(255), args TEXT, result TEXT, create_time DATETIME ); ``` 在切面中注 `JdbcTemplate` 或 `Repository` 保存日志。 ##### (2) 添加 Trace ID(引用[5]) 实现分布式链路追踪: ```java // 在请求头中添加 traceId String traceId = UUID.randomUUID().toString(); MDC.put("traceId", traceId); // 使用 SLF4J 的 MDC // 日志格式中增加 %X{traceId} // 示例:logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%X{traceId}] %-5level %logger{36} - %msg%n ``` ##### (3) 排除敏感参数 使用自定义注解过滤敏感字段: ```java @Before("requestLogPointcut()") public void logRequest(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { if (args[i] instanceof UserDTO) { args[i] = ((UserDTO) args[i]).maskPassword(); // 脱敏处理 } } // 记录脱敏后的参数 } ``` --- #### 5. **优化建议** 1. **性能优化**: - 异步记录日志:使用 `@Async` 注解异步保存日志。 - 批量插:攒批处理数据库写(如每 10 条批量插)[^2]。 2. **动态开关**: 通过配置中心动态启停日志: ```java @Before("requestLogPointcut() && @annotation(enableLogging)") public void logIfEnabled(JoinPoint joinPoint, EnableLogging enableLogging) { if (enableLogging.value()) { ... } } ``` --- #### **总结** 通过 AOP 实现接口日志记录的核心步骤: 1. 添加 `spring-boot-starter-aop` 依赖。 2. 定义切面类,使用 `@Pointcut` 定位拦截范围。 3. 通过 `@Before` 记录请求信息,`@AfterReturning` 记录响应信息。 4. 结合 `HttpServletRequest` 和 `JoinPoint` 获取请求上下文。 5. 扩展功能:数据库存储、链路追踪、敏感信息过滤。 > 此方法避免了业务代码耦合,统一处理日志逻辑[^1][^3],适用于监控、审计、调试等场景[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值