1、HiddenHttpMethodFilter 与装饰模式

①简介

在 HTML 中,GET 和 POST 请求可以天然实现,但是 DELETE 和 PUT 请求无法直接做到。SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求

②HiddenHttpMethodFilter 源码要点

[1]默认请求参数名常量

  1. publicstaticfinalStringDEFAULT_METHOD_PARAM="_method";

之所以会提供这个成员变量和配套的 setXxx() 方法,是允许我们在配置 Filter 时,通过初始化参数来修改这个变量。如果不修改,默认就是前面常量定义的值。

[3]以常量值为名称获取请求参数img001.png

③原始请求对象的包装

[1]困难

  • 包装对象必须和原始对象是同一个类型
  • 保证同一个类型不能通过子类继承父类实现
    • 子类对象:希望改变行为、属性的对象
    • 父类对象:随着 Servlet 容器的不同,各个容器对 HttpServletRequest 接口给出的实现不同。如果继承了 A 容器给出的实现类,那么将来就不能再迁移到 B 容器。
  • 只能让包装对象和被包装对象实现相同接口
    • 虽然使用动态代理技术大致上应该能实现,但是一旦应用代理就必须为被包装的对象的每一个方法都进行代理,操作过于繁琐。
  • 如果我们自己创建一个类实现 HttpServletRequest 接口
    • 困难1:我们其实并不知道具体该怎么做
    • 困难2:抽象方法实在太多

      [2] HttpServletRequestWrapper 类

HttpMethodRequestWrapper 类就是 HiddenHttpMethodFilter 的一个内部类,在 HttpMethodRequestWrapper 类中有如下行为实现了对原始对象的包装:

  • 继承了官方包装类:HttpServletRequestWrapper
  • 在构造器中将原始 request 对象传给了父类构造器
  • 将我们指定的新请求方式传给了成员变量
  • 重写了父类(官方包装类)的 getMethod() 方法
  • 外界想知道新包装对象的请求方式时,会来调用被重写的 getMethod() 方法,从而得到我们指定的请求方式

    1. /**
    2. * Simple {@link HttpServletRequest} wrapper that returns the supplied method for
    3. * {@link HttpServletRequest#getMethod()}.
    4. */
    5. private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
    6. private final String method;
    7. public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
    8. // 在构造器中将原始 request 对象传给了父类构造器
    9. super(request);
    10. // 将我们指定的新请求方式传给了成员变量
    11. this.method = method;
    12. }
    13. @Override
    14. public String getMethod() {
    15. return this.method;
    16. }
    17. }

    ④装饰者模式

装饰者模式也是二十三种设计模式之一,属于结构型模式,主要特点就是借助原始对象实现和原始对象一样的接口,同时通过重写父类方法修改被包装对象的行为。

2、PUT 请求

以下操作需要在已有的 SpringMVC 环境基础上执行:

①web.xml

  1. <filter>
  2. <filter-name>hiddenHttpMethodFilter</filter-name>
  3. <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>hiddenHttpMethodFilter</filter-name>
  7. <url-pattern>/*</url-pattern>
  8. </filter-mapping>

②表单

  • 要点1:原请求方式必须是 post
  • 要点2:新的请求方式名称通过请求参数发送
  • 要点3:请求参数名称必须是_method
  • 要点4:请求参数的值就是要改成的请求方式

    1. <!-- 原请求方式必须是 post -->
    2. <form th:action="@{/emp}" method="post">
    3. <!-- 通过表单隐藏域携带一个请求参数 -->
    4. <!-- 请求参数名:_method -->
    5. <!-- 请求参数值:put -->
    6. <input type="hidden" name="_method" value="put" />
    7. <button type="submit">更新</button>
    8. </form>

    ③handler 方法

    1. // 映射请求地址:URL + 请求方式
    2. @RequestMapping(value = "/emp", method = RequestMethod.PUT)
    3. public String updateEmp() {
    4. logger.debug("现在执行的是 updateEmp() 方法");
    5. return "target";
    6. }

    ④请求方式 Filter 对字符集 Filter 的影响

    [1]结论

    当 web.xml 中两个 Filter 并存,一定要让 CharacterEncodingFilter 先执行

    [2]原因

  • 在 CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集的

  • request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作
  • 而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:

    String paramValue = request.getParameter(this.methodParam);

3、DELETE 请求

前面为了转换 PUT 请求所配置的环境仍然要继续使用。