如何在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; } }
关键注意事项
请求体读取问题:读取原请求的输入流后,原流会被耗尽。如果原请求还要被后续代码使用,一定要先用
ContentCachingRequestWrapper(Spring提供)或者自己实现的缓存流包装原请求,确保可以重复读取。可变对象的深度复制:如果请求的attributes里存了自定义的可变对象,你需要自己实现这些对象的深度复制逻辑,否则还是会存在浅拷贝的问题。
按需实现方法:
HttpServletRequest有很多方法,你不需要全部实现,只需要实现你的业务代码中用到的方法即可,没用到的可以直接抛出UnsupportedOperationException。
内容的提问来源于stack exchange,提问作者Jason Foster

