SpringBoot切面,打印参数日志

本文介绍了一种使用AOP(面向切面编程)进行日志记录的方法,通过Spring框架实现,详细展示了如何在不修改业务代码的情况下,对请求进行统一的日志记录,包括请求IP、URL、类名、方法名及参数。

根据个人需要修改

具体代码如下:

package com.***.dfp.rtsync.common.aspect;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
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;

/**
 * Description 日志切片
 *
 * @author Bob
 * @date 2020/8/17
 **/
@Component
@Aspect
@Order
@Slf4j
public class CompassAspect {

    /**
     * @description 切入点
     * @author Bob
     * @date 2020/8/25
     */
    @Pointcut("@within(org.springframework.web.bind.annotation.RestController) @within(org.springframework.stereotype.Controller)")
    public void logPointCut() {

    }

    /**
     * @description Advice方式
     * @author Bob
     * @date 2020/8/25
     */
    @Around("logPointCut()")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        String url = request.getRequestURI().toString();
        String ip = request.getRemoteAddr();
        String className = pjp.getTarget().getClass().getName();
        String methodName = pjp.getSignature().getName();
        log.info("##################ip:{},url:{},class:{},method:{},args:{}", ip, url, className, methodName, handlerParameter(pjp));
        try {
            long start = System.currentTimeMillis();
            Object result = pjp.proceed();
            long runTime = System.currentTimeMillis() - start;
            log.info("##################runTime:{},result:{}", runTime, JSON.toJSONString(result));
            return result;
        } catch (RuntimeException e) {
            e.printStackTrace();
            return "系统开小差了";
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return "系统异常";
        }
    }

    /**
     * @description 参数组装
     * @author Bob
     * @date 2020/8/25
     */
    private String handlerParameter(ProceedingJoinPoint point) {
        StringBuilder stringBuilder = new StringBuilder();
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        String[] parameterNames = methodSignature.getParameterNames();
        Object[] args = point.getArgs();
        int i = 0;
        for (Object pojo : args) {
            stringBuilder.append(parameterNames[i]).append(":").append(pojo).append(",");
        }
        return stringBuilder.toString();
    }
}

参考:https://blog.youkuaiyun.com/lvhonglei1987/article/details/93874445

<think>我们正在讨论的是在Spring Boot切面中获取方法参数的方法。根据用户的问题和提供的引用,我们可以总结出以下几种方法: 1. **通过JoinPoint对象获取**(最常用): - 在通知方法(如@Before, @After等)中添加JoinPoint参数。 - 使用`JoinPoint.getArgs()`获取方法参数数组。 - 可以遍历数组或按索引获取具体参数。 2. **通过ProceedingJoinPoint对象获取**(环绕通知): - 在环绕通知(@Around)中使用ProceedingJoinPoint,它是JoinPoint的子接口,提供了`proceed()`方法执行目标方法。 - 同样使用`getArgs()`获取参数。 3. **获取带有注解的参数**: - 如果方法参数使用了自定义注解,可以通过在通知方法中直接获取带有特定注解的参数。 4. **获取请求相关的参数**(在Web环境下): - 在Web应用中,可以通过RequestContextHolder获取HttpServletRequest,进而获取请求参数(如引用[1]中的示例)。 5. **获取方法参数的名称**(需要启用调试信息): - 通过`JoinPoint.getSignature()`获取方法签名,然后获取参数名称(需要编译时保留调试信息,或使用Spring的ParameterNameDiscoverer)。 6. **处理泛型参数**: - 如引用[5]所示,如果参数是泛型对象,需要解析泛型类型,可以通过`MethodParameter`类来协助。 下面我们详细说明这些方法: ### 1. 通过JoinPoint获取参数(通用方法) 这是最基础也是最常用的方法。在通知方法中声明一个`JoinPoint`类型的参数,然后通过它的`getArgs()`方法获取参数数组。 示例代码(来自引用[1],稍作修改): ```java @Aspect @Component public class LogAspectHandler { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Before("pointCut()") public void doBefore(JoinPoint joinPoint) { // 获取方法参数 Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { logger.info("第{}个参数为:{}", i, args[i]); } } } ``` ### 2. 通过ProceedingJoinPoint(环绕通知) 在环绕通知中,我们使用`ProceedingJoinPoint`对象,它同样有`getArgs()`方法,并且允许我们在执行目标方法前后进行操作。 示例代码: ```java @Around("pointCut()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Object[] args = proceedingJoinPoint.getArgs(); // 修改参数(如果需要) // args[0] = "new value"; // 执行目标方法,并获取返回值 Object result = proceedingJoinPoint.proceed(args); return result; } ``` ### 3. 获取带有注解的参数 如果方法参数使用了注解(如Spring MVC的`@RequestParam`、`@RequestBody`,或自定义注解),我们可以通过指定注解类型来获取参数。 示例:获取带有`@RequestBody`注解的参数 ```java @Before("pointCut()") public void logRequestBody(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); Annotation[][] parameterAnnotations = method.getParameterAnnotations(); Object[] args = joinPoint.getArgs(); for (int i = 0; i < parameterAnnotations.length; i++) { for (Annotation annotation : parameterAnnotations[i]) { if (annotation instanceof RequestBody) { Object requestBody = args[i]; logger.info("RequestBody参数: {}", requestBody); } } } } ``` 或者使用更简洁的方式(需要Java 8+): ```java @Before("pointCut()") public void logRequestBody(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Parameter[] parameters = signature.getMethod().getParameters(); IntStream.range(0, parameters.length) .filter(i -> parameters[i].isAnnotationPresent(RequestBody.class)) .forEach(i -> logger.info("RequestBody参数: {}", args[i])); } ``` ### 4. 在Web环境下获取请求参数 如果切面应用在Web控制器上,我们可以获取HttpServletRequest对象,从而读取请求参数、请求体等(如引用[1]中的示例)。 示例代码(复制自引用[1]): ```java @Before("pointCut()") public void doBefore(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 获取请求url String url = request.getRequestURL().toString(); // 获取请求ip String ip = request.getRemoteAddr(); logger.info("用户请求的url为:{},ip地址为:{}", url, ip); } ``` ### 5. 获取方法参数的名称 默认情况下,由于Java字节码中不保留参数名称(除非编译时添加`-parameters`选项),我们可能无法直接获取参数名。但可以通过以下方式: - 在Spring Boot中,可以通过`DefaultParameterNameDiscoverer`获取(需要编译时保留调试信息或使用`-parameters`编译选项)。 示例: ```java @Before("pointCut()") public void logParameterNames(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); String[] parameterNames = parameterNameDiscoverer.getParameterNames(method); Object[] args = joinPoint.getArgs(); for (int i = 0; i < parameterNames.length; i++) { logger.info("参数名: {}, 值: {}", parameterNames[i], args[i]); } } ``` ### 6. 处理泛型参数 如引用[5]所示,如果方法参数是一个包含泛型的对象(例如一个封装了请求体和请求头的对象),我们可以通过反射获取泛型类型。 示例:假设有一个方法参数类型为`Request<BodyType>`,我们需要获取其中的`BodyType`。 ```java @Before("pointCut()") public void handleGenericParameter(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); for (Object arg : args) { if (arg instanceof Request) { // 获取泛型类型 Type genericType = ((ParameterizedType) arg.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; // 如果知道具体类型,可以转换 if (genericType instanceof Class) { Class<?> bodyClass = (Class<?>) genericType; // 处理... } } } } ``` 注意:这种方法需要参数实例化时保留了泛型信息(比如通过子类明确指定了泛型类型)。如果只是使用`Request<BodyType>`而没有具体实现类,可能无法获取。 ### 注意事项 - **同类调用问题**:如果目标方法被同类中的其他方法调用,那么切面不会生效(因为Spring AOP基于代理)。解决方法见引用[2]中的“同类方法调用问题解决方案”。 - **参数修改**:在环绕通知中,我们可以修改参数数组并传递给`proceed`方法。但在前置通知中,修改参数数组不会影响实际传入目标方法的参数(因为数组是副本)。如果要修改参数,需要在环绕通知中进行。 - **性能**:获取参数名称等操作可能涉及反射,对性能有影响,应避免在高频操作中使用。 ### 相关问题 1. 在Spring AOP中,如何修改目标方法的参数值? 2. 如何解决同类方法调用导致切面不生效的问题? 3. 在切面中如何获取目标方法的返回值和异常信息? 4. 如何为切面方法编写单元测试?(参考引用[3]) 5. 在Spring Boot中如何配置使编译后的字节码保留参数名称? </think>### 📌 Spring Boot切面获取方法参数的完整指南 在Spring Boot切面中获取方法参数主要有以下几种方法,根据引用内容整理如下: #### 1. **通过JoinPoint对象获取(最常用)** 在通知方法(如`@Before`、`@After`等)中添加`JoinPoint`参数,使用`getArgs()`获取所有参数数组: ```java @Before("pointCut()") public void doBefore(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); // 获取所有参数 Signature signature = joinPoint.getSignature(); String methodName = signature.getName(); // 获取方法名 // 示例:打印所有参数 for (int i = 0; i < args.length; i++) { logger.info("参数 {}: {}", i, args[i]); } } ``` *适用场景*:通用参数获取,无需知道具体参数名[^1]。 #### 2. **通过方法签名获取带类型信息** 通过`MethodSignature`可获取参数名和类型信息: ```java @Before("pointCut()") public void logParams(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String[] paramNames = signature.getParameterNames(); // 参数名数组 Class<?>[] paramTypes = signature.getParameterTypes(); // 参数类型数组 // 匹配参数名与值 Object[] args = joinPoint.getArgs(); for (int i = 0; i < paramNames.length; i++) { logger.info("参数 {} (类型:{}): {}", paramNames[i], paramTypes[i].getSimpleName(), args[i]); } } ``` *注意*:需编译时开启`-parameters`选项保留参数名[^2]。 #### 3. **获取带注解的参数** 若参数有自定义注解(如`@RequestBody`),可直接定位: ```java @Before("@annotation(org.springframework.web.bind.annotation.GetMapping)") public void handleAnnotatedParams(JoinPoint joinPoint) { Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); Annotation[][] paramAnnotations = method.getParameterAnnotations(); for (int i = 0; i < paramAnnotations.length; i++) { for (Annotation ann : paramAnnotations[i]) { if (ann instanceof RequestBody) { Object requestBody = joinPoint.getArgs()[i]; logger.info("RequestBody参数: {}", requestBody); } } } } ``` #### 4. **Web环境下获取HTTP请求参数** 在Controller切面中获取请求信息: ```java @Before("execution(* com.example.controller.*.*(..))") public void logRequest(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 获取URL和IP String url = request.getRequestURL().toString(); String ip = request.getRemoteAddr(); // 获取查询参数 String queryString = request.getQueryString(); logger.info("请求URL: {}, IP: {}, 参数: {}", url, ip, queryString); } ``` *适用场景*:记录接口访问日志[^1][^4]。 #### 5. **处理泛型参数(复杂场景)** 当参数是泛型对象时(如引用[5]的JSON结构),需特殊处理: ```java @Before("execution(* com.example.service.*.*(..))") public void handleGenericParam(JoinPoint joinPoint) { Object arg = joinPoint.getArgs()[0]; if (arg instanceof RequestWrapper) { RequestWrapper<?> wrapper = (RequestWrapper<?>) arg; Object body = wrapper.getBody(); // 获取泛型body logger.info("请求体类型: {}", body.getClass().getSimpleName()); } } ``` ### ️ 常见问题解决方案 1. **切面不生效**: - 检查是否添加`@EnableAspectJAutoProxy` - 确保切面类有`@Component`注解 - 避免同类方法自调用(使用`AopContext.currentProxy()`)[^2] 2. **参数名为null**: ```gradle // build.gradle 配置 tasks.withType(JavaCompile) { options.compilerArgs << "-parameters" } ``` 3. **嵌套泛型解析**: ```java ParameterizedType type = (ParameterizedType) field.getGenericType(); Class<?> actualType = (Class<?>) type.getActualTypeArguments()[0]; ``` ### 📝 最佳实践建议 1. **参数日志模板**: ```java logger.info("调用{}.{}(), 参数: {}", signature.getDeclaringTypeName(), signature.getName(), Arrays.toString(args)); ``` 2. **敏感参数过滤**: ```java Arrays.stream(args) .map(arg -> (arg instanceof Password) ? "***" : arg) .toArray(); ``` 3. **性能优化**:缓存`MethodSignature`避免重复反射 --- ### 🔍 相关问题 1. 如何在Spring AOP中修改方法参数值? 2. 获取参数名时为什么得到`arg0`、`arg1`?如何解决? 3. 切面中如何处理Jackson序列化的泛型嵌套对象? 4. 如何为切面方法编写单元测试?(参考引用[3]) 5. 同类方法调用时切面失效的根本原因是什么?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值