You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Struts2拦截器中获取客户端发送的原始JSON数据

解决Struts2中记录客户端原始JSON请求体的问题

我明白你的痛点:Struts2已经把JSON正常绑定到JavaBean了,但出错时想查客户端发的原始JSON却找不到——这是因为Struts2默认不会保存原始请求体,而且请求的输入流只能被读取一次,JSON插件解析完成后就无法再获取了。下面给你一套可行的解决方案:

核心问题原因

当Angular用HttpClient.post发送JSON时,请求的Content-Type是application/json,Struts2的JSON拦截器会优先读取请求输入流,把JSON转换成对应的JavaBean完成绑定,而输入流只能被读取一次,所以你在后续拦截器或日志工具里,根本拿不到原始的JSON数据。

解决方案:自定义可重复读取的请求包装器+前置拦截器

我们需要在Struts2解析请求之前,先读取并保存原始JSON,同时包装请求让输入流可以被重复读取,这样既不影响Struts2的绑定逻辑,又能拿到原始数据。

步骤1:实现可重复读取的HttpServletRequestWrapper

这个类会把请求体缓存起来,让输入流可以多次读取:

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class RepeatableRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public RepeatableRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 读取并缓存原始请求体
        InputStream inputStream = request.getInputStream();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inputStream.read(buffer)) != -1) {
            byteArrayOutputStream.write(buffer, 0, len);
        }
        body = byteArrayOutputStream.toByteArray();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return byteArrayInputStream.available() == 0;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {}

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
    }

    // 获取原始JSON字符串
    public String getRequestBody() {
        return new String(body, StandardCharsets.UTF_8);
    }
}

步骤2:修改自定义拦截器,前置读取原始JSON

确保这个拦截器在Struts2的JSON拦截器之前执行,这样才能在解析前拿到原始数据:

public class CustomInterceptor extends AbstractInterceptor {
    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        HttpServletRequest request = ServletActionContext.getRequest();
        String contentType = request.getContentType();
        
        // 只处理JSON类型的请求
        if (contentType != null && contentType.contains("application/json")) {
            // 包装请求,缓存原始JSON
            RepeatableRequestWrapper wrappedRequest = new RepeatableRequestWrapper(request);
            String rawJson = wrappedRequest.getRequestBody();
            
            // 把原始JSON存到request属性里,方便日志工具获取
            request.setAttribute("RAW_REQUEST_JSON", rawJson);
            
            // 替换请求,让Struts2后续能正常解析
            ServletActionContext.setRequest(wrappedRequest);
        }
        
        // 继续执行后续拦截器和Action逻辑
        return invocation.invoke();
    }
}

步骤3:更新日志工具,添加原始JSON记录

修改你的getValoresRequestToString方法,从request属性中取出原始JSON:

public static String getValoresRequestToString(HttpServletRequest request) {
    String retorno = "";
    
    // 优先添加原始JSON数据
    String rawJson = (String) request.getAttribute("RAW_REQUEST_JSON");
    if (rawJson != null && !rawJson.isBlank()) {
        retorno += "\n*RAW_REQUEST_JSON*: " + rawJson;
    }
    
    // 保留原来的请求属性遍历逻辑
    Enumeration<String> nombresAtributos = request.getAttributeNames();
    while (nombresAtributos.hasMoreElements()) {
        String nombreAtributo = nombresAtributos.nextElement();
        retorno = retorno + "\nREQUEST:: " + nombreAtributo + " :" + request.getAttribute(nombreAtributo);
    }
    return retorno;
}

步骤4:配置拦截器顺序(关键!)

在struts.xml里把自定义拦截器放在JSON拦截器前面,否则JSON拦截器先读了输入流,我们就拿不到数据了:

<interceptors>
    <interceptor name="customInterceptor" class="com.yourpackage.CustomInterceptor"/>
    <interceptor-stack name="appDefaultStack">
        <!-- 自定义拦截器先执行 -->
        <interceptor-ref name="customInterceptor"/>
        <!-- JSON拦截器放在后面 -->
        <interceptor-ref name="json"/>
        <!-- 其他默认拦截器 -->
        <interceptor-ref name="defaultStack"/>
    </interceptor-stack>
</interceptors>

<!-- 全局或Action级别使用这个栈 -->
<default-interceptor-ref name="appDefaultStack"/>

注意事项

  • 只针对application/json类型的请求处理,避免非JSON请求的不必要性能开销。
  • 如果你的系统有超大JSON请求(比如几MB以上),可以考虑用临时文件缓存请求体,避免内存溢出。
  • 确保你的自定义拦截器在所有解析请求体的拦截器之前执行,除了JSON拦截器,其他可能读取输入流的拦截器也要注意顺序。

这样修改后,当服务器处理请求出错时,你的日志工具就能拿到客户端发送的原始JSON数据了。

内容的提问来源于stack exchange,提问作者Pevi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 15:57:45