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

如何在Java中深度复制(克隆)HttpServletRequest?

如何深度复制Java中的HttpServletRequest

我完全懂这种“明知不是最佳实践但不得不做”的窘境——重构代码和改一堆单元测试的成本确实顶不住。既然你已经试过HttpServletRequestWrapper但发现它只是浅拷贝(本质上还是代理原请求对象,原请求变了包装后的也跟着变),那咱们来聊聊怎么实现真正的深度复制。

核心思路:手动复制所有需要的请求属性

因为HttpServletRequest既不支持序列化也不支持克隆,所以只能手动把原请求里的关键属性(参数、头、请求体、方法、URI等)复制到一个新的对象里,确保每个可变的集合/数组都创建独立的副本,而不是引用原对象的实例。

方案1:自定义HttpServletRequest实现类(完全独立)

这种方式是创建一个全新的HttpServletRequest实现,把原请求的所有需要的属性都复制进去,完全不依赖原请求对象。好处是彻底和原请求解耦,原请求的任何变化都不会影响复制后的对象。

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;

public class DeepCopiedHttpServletRequest implements HttpServletRequest {
    // 基本请求属性
    private final String method;
    private final String requestURI;
    private final String remoteAddr;
    // 深度复制的参数(每个数组都创建新副本)
    private final Map<String, String[]> parameterMap;
    // 深度复制的请求头(每个List都创建新副本)
    private final Map<String, List<String>> headerMap;
    // 缓存请求体为byte数组,支持重复读取
    private final byte[] requestBody;

    public DeepCopiedHttpServletRequest(HttpServletRequest original) throws IOException {
        // 复制基本类型属性(直接赋值即可,因为基本类型是值传递)
        this.method = original.getMethod();
        this.requestURI = original.getRequestURI();
        this.remoteAddr = original.getRemoteAddr();

        // 深度复制请求参数
        this.parameterMap = new HashMap<>();
        Map<String, String[]> originalParams = original.getParameterMap();
        for (Map.Entry<String, String[]> entry : originalParams.entrySet()) {
            // 复制数组,避免引用原数组
            this.parameterMap.put(entry.getKey(), Arrays.copyOf(entry.getValue(), entry.getValue().length));
        }

        // 深度复制请求头
        this.headerMap = new HashMap<>();
        Enumeration<String> headerNames = original.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            Enumeration<String> originalValues = original.getHeaders(name);
            // 把枚举转成List并创建新副本
            List<String> valueList = Collections.list(originalValues);
            this.headerMap.put(name, new ArrayList<>(valueList));
        }

        // 复制请求体:用IOUtils读取原输入流为byte数组
        // 注意:读取原输入流后,原请求的输入流会被耗尽!如果原请求还要被后续使用,先包装成ContentCachingRequestWrapper
        this.requestBody = IOUtils.toByteArray(original.getInputStream());
    }

    // 实现HttpServletRequest的核心方法,返回我们复制的属性
    @Override
    public String getMethod() {
        return method;
    }

    @Override
    public String getRequestURI() {
        return requestURI;
    }

    @Override
    public String getRemoteAddr() {
        return remoteAddr;
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        // 返回不可修改的副本,防止外部修改破坏我们的复制结果
        return Collections.unmodifiableMap(parameterMap);
    }

    @Override
    public String getParameter(String name) {
        String[] values = parameterMap.get(name);
        return values != null && values.length > 0 ? values[0] : null;
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] values = parameterMap.get(name);
        return values != null ? Arrays.copyOf(values, values.length) : null;
    }

    @Override
    public Enumeration<String> getHeaderNames() {
        return Collections.enumeration(headerMap.keySet());
    }

    @Override
    public String getHeader(String name) {
        List<String> values = headerMap.get(name);
        return values != null && !values.isEmpty() ? values.get(0) : null;
    }

    @Override
    public Enumeration<String> getHeaders(String name) {
        List<String> values = headerMap.get(name);
        return values != null ? Collections.enumeration(values) : Collections.emptyEnumeration();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new ServletInputStream() {
            private final ByteArrayInputStream bis = new ByteArrayInputStream(requestBody);

            @Override
            public boolean isFinished() {
                return bis.available() == 0;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {
                // 如果不需要异步读取,直接抛出不支持异常即可
                throw new UnsupportedOperationException("Async read operations are not supported");
            }

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

    // 其他不需要深度复制的方法,可以直接抛出异常或者返回默认值,根据你的业务需求来实现
    @Override
    public HttpSession getSession() {
        throw new UnsupportedOperationException("Session access is not supported in copied request");
    }

    @Override
    public HttpSession getSession(boolean create) {
        throw new UnsupportedOperationException("Session access is not supported in copied request");
    }

    // 更多方法如getServletContext、getAuthType等,根据需要实现,不需要的就抛出异常
}

方案2:继承HttpServletRequestWrapper(减少代码量)

如果不想实现HttpServletRequest的所有方法,可以继承HttpServletRequestWrapper,只重写那些需要深度复制的方法,把对应的属性缓存起来。这种方式更简洁,因为父类已经实现了大部分方法,我们只需要覆盖需要修改的部分。

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.web.util.ContentCachingRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;

public class DeepCopyRequestWrapper extends HttpServletRequestWrapper {
    private Map<String, String[]> parameterMap;
    private Map<String, List<String>> headerMap;
    private byte[] requestBody;
    private Map<String, Object> attributeMap;

    public DeepCopyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 先缓存原请求的body,避免读取后原流不可用(用Spring的ContentCachingRequestWrapper更方便)
        ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(request);
        this.requestBody = cachedRequest.getContentAsByteArray();

        // 深度复制请求参数
        this.parameterMap = new HashMap<>();
        Map<String, String[]> originalParams = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : originalParams.entrySet()) {
            this.parameterMap.put(entry.getKey(), Arrays.copyOf(entry.getValue(), entry.getValue().length));
        }

        // 深度复制请求头
        this.headerMap = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            Enumeration<String> originalValues = request.getHeaders(name);
            this.headerMap.put(name, new ArrayList<>(Collections.list(originalValues)));
        }
    }

    // 重写参数相关方法,返回我们的深度副本
    @Override
    public Map<String, String[]> getParameterMap() {
        return Collections.unmodifiableMap(parameterMap);
    }

    @Override
    public String getParameter(String name) {
        String[] values = parameterMap.get(name);
        return values != null && values.length > 0 ? values[0] : null;
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] values = parameterMap.get(name);
        return values != null ? Arrays.copyOf(values, values.length) : null;
    }

    // 重写请求头相关方法
    @Override
    public Enumeration<String> getHeaderNames() {
        return Collections.enumeration(headerMap.keySet());
    }

    @Override
    public String getHeader(String name) {
        List<String> values = headerMap.get(name);
        return values != null && !values.isEmpty() ? values.get(0) : null;
    }

    @Override
    public Enumeration<String> getHeaders(String name) {
        List<String> values = headerMap.get(name);
        return values != null ? Collections.enumeration(values) : Collections.emptyEnumeration();
    }

    // 重写输入流方法,返回缓存的body
    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new ServletInputStream() {
            private final ByteArrayInputStream bis = new ByteArrayInputStream(requestBody);

            @Override
            public boolean isFinished() {
                return bis.available() == 0;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {
                throw new UnsupportedOperationException("Async read operations are not supported");
            }

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

    // 如果需要深度复制request attributes,重写getAttribute相关方法
    @Override
    public Object getAttribute(String name) {
        if (attributeMap == null) {
            // 初始化时复制所有属性,注意:如果属性是可变对象,需要递归深度复制!
            attributeMap = new HashMap<>();
            Enumeration<String> attrNames = super.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = attrNames.nextElement();
                Object attrValue = super.getAttribute(attrName);
                // 这里根据实际情况处理:简单类型直接复制,自定义对象需要自己实现克隆逻辑
                attributeMap.put(attrName, deepCopyAttribute(attrValue));
            }
        }
        return attributeMap.get(name);
    }

    private Object deepCopyAttribute(Object value) {
        // 示例:处理常见简单类型和集合
        if (value instanceof String || value instanceof Number || value instanceof Boolean) {
            return value;
        } else if (value instanceof List) {
            return new ArrayList<>((List<?>) value);
        } else if (value instanceof Map) {
            return new HashMap<>((Map<?, ?>) value);
        }
        // 其他自定义对象,需要你自己实现深度复制逻辑(比如拷贝构造方法、序列化反序列化等)
        return value;
    }
}

关键注意事项

  1. 请求体读取问题:读取原请求的输入流后,原流会被耗尽。如果原请求还要被后续代码使用,一定要先用ContentCachingRequestWrapper(Spring提供)或者自己实现的缓存流包装原请求,确保可以重复读取。

  2. 可变对象的深度复制:如果请求的attributes里存了自定义的可变对象,你需要自己实现这些对象的深度复制逻辑,否则还是会存在浅拷贝的问题。

  3. 按需实现方法:HttpServletRequest有很多方法,你不需要全部实现,只需要实现你的业务代码中用到的方法即可,没用到的可以直接抛出UnsupportedOperationException。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:27:52