Beyond AOP: 4 Ways to Capture Spring Boot Controller Params and Return Values – The Fastest Wins

The article compares four techniques—traditional AOP, a Filter with ContentCaching wrappers, Spring MVC Request/Response Advice, and a custom HandlerAdapter extension—for extracting request parameters and response bodies in Spring Boot 3.5, demonstrating that the HandlerAdapter approach offers the highest performance while remaining non‑intrusive.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Beyond AOP: 4 Ways to Capture Spring Boot Controller Params and Return Values – The Fastest Wins

Environment: Spring Boot 3.5.0

1. Introduction

In enterprise development, unified auditing, encryption, or logging of controller request parameters and response values is a common requirement. AOP is the most widely used solution because of its low intrusion, but under high concurrency or full‑traffic scenarios it may become a bottleneck in performance or flexibility.

2. Four implementation schemes

2.1 Traditional AOP

Uses @Aspect and @Around to intercept methods annotated with mapping annotations. The example prints the request arguments and the returned object.

@Component
@Aspect
public class RequestAspect {
    @Pointcut("@annotation(mapping) && within(com.pack..*)")
    private void pcc(GetMapping mapping) {}

    @Around("pcc(mapping)")
    public Object around(ProceedingJoinPoint pjp, GetMapping mapping) throws Throwable {
        Object[] args = pjp.getArgs();
        Object ret = pjp.proceed();
        System.err.println("AOP, 请求参数: %s, 返回值: %s".formatted(Arrays.toString(args), ret));
        return ret;
    }
}

Running result:

2.2 Filter + ContentCachingWrapper

Filter sits at the outermost layer of the servlet container and can capture the raw HTTP request and response strings, including static resources and exceptions. Because the input and output streams can be read only once, the filter must wrap them with ContentCachingRequestWrapper and ContentCachingResponseWrapper to cache the data.

@WebFilter("/api/*")
public class RequestArgFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
        String id = request.getParameter("id");
        try {
            filterChain.doFilter(requestWrapper, responseWrapper);
        } finally {
            String requestBody = new String(requestWrapper.getContentAsByteArray(),
                                            requestWrapper.getCharacterEncoding());
            String responseBody = new String(responseWrapper.getContentAsByteArray(),
                                             responseWrapper.getCharacterEncoding());
            System.err.println("Filter, 请求参数: %s, 返回值: %s".formatted(List.of(requestBody, id), responseBody));
            responseWrapper.copyBodyToResponse();
        }
    }
}

Running result:

2.3 Request/Response Advice

Spring MVC provides RequestBodyAdvice and ResponseBodyAdvice hooks that run before and after body conversion. This approach is suitable for global data encryption/decryption or uniform response wrapping, but it only works for methods annotated with @RequestBody or @ResponseBody (including @RestController).

@ControllerAdvice
public class RequestResponseAdvice extends RequestBodyAdviceAdapter
        implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType,
                           Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
                                           MethodParameter parameter, Type targetType,
                                           Class<? extends HttpMessageConverter<?>> converterType)
            throws IOException {
        String body = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
        HttpInputMessage finalInputMessage = new HttpInputMessage() {
            public HttpHeaders getHeaders() { return inputMessage.getHeaders(); }
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
            }
        };
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        attrs.getRequest().setAttribute("req_body", body);
        return super.beforeBodyRead(finalInputMessage, parameter, targetType, converterType);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                 MediaType selectedContentType,
                                 Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                 ServerHttpRequest request, ServerHttpResponse response) {
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String reqBody = (String) attrs.getRequest().getAttribute("req_body");
        System.err.println("Advice, 请求参数: %s, 返回值: %s".formatted(reqBody, body));
        return body;
    }
}

Running result:

Limitation: this method only captures bodies for @RequestBody / @ResponseBody endpoints and cannot obtain parameters from traditional form submissions or query strings.

2.4 Custom HandlerAdapter

By extending RequestMappingHandlerAdapter and overriding createInvocableHandlerMethod, we can intercept the invocation of any controller method and log the arguments and return value without altering the core logic.

public class RequestArgsHandlerAdapter extends RequestMappingHandlerAdapter {
    @Override
    protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
        return new ArgsInvocationHandler(handlerMethod);
    }

    private static class ArgsInvocationHandler extends ServletInvocableHandlerMethod {
        public ArgsInvocationHandler(HandlerMethod handlerMethod) {
            super(handlerMethod);
        }

        @Override
        protected Object doInvoke(Object... args) throws Exception {
            Object ret = super.doInvoke(args);
            System.err.println("HandlerAdapter, 请求参数: %s, 返回值: %s".formatted(Arrays.toString(args), ret));
            return ret;
        }
    }
}

This approach adds only logging and does not modify underlying processing. It works for all application scenarios and delivers the highest performance among the four approaches.

3. Conclusion

All four methods can obtain controller request parameters and response values, but they differ in intrusion level, supported request types, and runtime overhead. The custom HandlerAdapter solution provides the best overall performance while remaining non‑intrusive, making it the preferred choice for high‑throughput enterprise services.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performanceAOPSpring BootFilterRequestBodyAdviceHandlerAdapter
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.