为啥我们要包装 HttpServletRequest ? api 提供了一个 HttpServletRequestWrapper 但我们从包装请求中获得了啥?

Posted

技术标签:

【中文标题】为啥我们要包装 HttpServletRequest ? api 提供了一个 HttpServletRequestWrapper 但我们从包装请求中获得了啥?【英文标题】:Why do we wrap HttpServletRequest ? The api provides an HttpServletRequestWrapper but what do we gain from wrapping the request?为什么我们要包装 HttpServletRequest ? api 提供了一个 HttpServletRequestWrapper 但我们从包装请求中获得了什么? 【发布时间】:2017-10-26 05:08:15 【问题描述】:

使用 HttpServletRequestWrapper 包装 HttpServletRequest 的目的是什么?这样做有什么好处?

【问题讨论】:

AFAIK 这是篡改请求参数(例如更改其名称)的唯一方法,而无需向 Web 流添加额外的跃点。 这也有助于多次读取请求。 【参考方案1】:

HttpServletRequest 是 HTTP 特定 servlet 请求的接口。通常,您会在 servlet filters 或 servlets 中获得此接口的实例。

有时您想在某个时候调整原始请求。使用HttpServletRequestWrapper,您可以包装原始请求并覆盖一些方法,使其行为略有不同。

示例:

您有一堆 servlet 和 JSP,它们需要某种格式的请求参数。例如。日期格式为yyyy-MM-dd

现在还需要支持不同格式的日期,例如具有相同功能的dd.MM.yyyy。假设没有中心字符串到日期函数(它是一个继承的遗留应用程序),您必须找到 servlet 和 JSP 中的所有位置。

作为替代方案,您可以实现一个 servlet 过滤器。您映射过滤器,以便对您的 servlet 和 JSP 的所有请求都将通过此过滤器。

过滤器的目的是检查日期参数的格式并在必要时将其重新格式化为旧格式。 servlet 和 JSP 总是以预期的旧格式获取日期字段。无需更改它们。

这是您的过滤器的骨架:

public void doFilter(ServletRequest request, ServletResponse response, 
    FilterChain chain) throws IOException, ServletException 
  HttpServletRequest adjustedRequest = adjustParamDates((HttpServletRequest) request);
  chain.doFilter(adjustedRequest, response);

我们获取原始请求,并在 adjustParamDates() 方法中处理请求并将其传递到过滤器链中。

现在,我们将如何实现adjustParamDates()

private HttpServletRequest adjustParamDates(HttpServletRequest req) 
  // ???

我们需要一个新的接口HttpServletRequest 实例,它的行为与原始实例req 完全相同。但是getParameter()getParameterMap()getParameterNames()getParameterValues()这四种方法不应该对原始参数起作用,而是对调整后的参数集起作用。接口HttpServletRequest 的所有其他方法的行为应与原始方法相同。

所以我们可以做类似的事情。我们创建一个HttpServletRequest 的实例并实现所有方法。大部分方法实现都非常简单,调用原始请求实例的对应方法即可:

private HttpServletRequest adjustParamDates(final HttpServletRequest req) 
  final Map<String, String[]> adjustedParams = reformatDates(req.getParameterMap());
  return new HttpServletRequest() 
    public boolean authenticate(HttpServletResponse response) 
      return req.authenticate(response);
    

    public String changeSessionId() 
      return req.changeSessionId();
    

    public String getContextPath() 
      return req.getContextPath();
    

    // Implement >50 other wrapper methods
    // ...

    // Now the methods with different behaviour:
    public String getParameter(String name) 
      return adjustedParams.get(name) == null ? null : adjustedParams.get(name)[0];
    

    public Map<String, String[]> getParameterMap() 
      return adjustedParams;
    

    public Enumeration<String> getParameterNames() 
      return Collections.enumeration(adjustedParams.keySet());
    

    public String[] getParameterValues(String name) 
      return adjustedParams.get(name);
    
  );

有超过 50 种方法可以实现。它们中的大多数只是原始请求的包装器实现。我们只需要四个自定义实现。但是我们必须把所有这些方法都写下来。

所以这里考虑到HttpServletRequestWrapper 类。这是一个默认的包装器实现,它采用原始请求实例并将接口HttpServletRequest 的所有方法实现为调用原始请求的相应方法的简单包装器方法,就像我们上面所做的那样。

通过继承HttpServletRequestWrapper,我们只需用自定义行为覆盖四个参数方法。

private HttpServletRequest adjustParamDates(final HttpServletRequest req) 
  final Map<String, String[]> adjustedParams = reformatDates(req.getParameterMap());
  return new HttpServletRequestWrapper(req) 
    public String getParameter(String name) 
      return adjustedParams.get(name) == null ? null : adjustedParams.get(name)[0];
    

    public Map<String, String[]> getParameterMap() 
      return adjustedParams;
    

    public Enumeration<String> getParameterNames() 
      return Collections.enumeration(adjustedParams.keySet());
    

    public String[] getParameterValues(String name) 
      return adjustedParams.get(name);
    
  );

【讨论】:

这个答案需要在一本书的某个章节中。很好的解释和示例解释。轻微建议,希望看到魔法对象的完整“导入”语句。 非常有帮助的答案!最后的代码sn-p是否也演示了Java中闭包(在变量adjustedParams上)的概念? 谢谢。是的,我想你可以说这是对adjustedParams 的某种闭包,它在匿名类中使用。这也是为什么这个变量被声明为final的原因。否则会出现编译错误。

以上是关于为啥我们要包装 HttpServletRequest ? api 提供了一个 HttpServletRequestWrapper 但我们从包装请求中获得了啥?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们将字符串数组作为参数传递给 main() 方法,为啥不传递任何集合类型或包装类型或原始类型?

Java:为啥需要包装类?

为啥我们不应该将每个包含的 Android XML 布局包装在一个 <merge> 对中?

为啥我的所有 SQL 查询都必须使用 ` 符号包装?

为啥 lambda auto& 参数选择 const 重载?

为啥我必须将每个 Thread.sleep() 调用包装在 try/catch 语句中? [复制]