解决SpirngMVC使用RESTFUL时,HiddenHttpMethodFilter无法解析enctype=“multipart/form-data类型表单隐藏的请求方法

当使用RESTFUL风格,表达的方法为非GET以及POST时,需要使用HiddenHttpMethodFilter来对隐藏的方法进行解析。通常就是直接调用getParameter方法解析出隐藏域来值,然后传给Controller就行了。

但是,当表单中有文件上传时,表单实际上变为了一个输入流,需要进过特殊处理(解析)后,才能被HiddenHttpMethodFilter所识别,然后确定方法类型。而解析输入流的东西,就是CommonsMultipartResolver。

使用SpringMVC时,通常在配置文件中配置CommonsMultipartResolver为一个Bean,然后它完成对Form的解析,解析出普通的上传域以及MultipartFile类型的上传域(也就是file),自动绑定到相应的类中。

如果在Form中,隐藏了name="_method" value="PUT/DELETE/..."这个域,我们原本希望HiddenHttpMethodFilter来解析为指定的方法,但是,此时必须先经过CommonsMultipartResolver解析出相应的域后,HiddeHttpMethodFilter才能找到name="_method"这个隐藏域,进行处理。

SpringMVC的执行流程中,过滤器起作用,比如HiddenHttpMethodFilter过滤器配置的拦截路径为所有的请求,那么它会在请求发送到DispatcherServlet的时候,就进行解析。但是没有经过CommonsMultipartResolver的解析(它是在DisptacherServlet通过HandlerAdapter来调用相应Handler的过程中,进行参数的解析绑定时才起作用的),此时HidedenHttpMethodFilter解析不出来什么鸟东西,请求的方法还是为post方法,就会找不到相应的Handler。这就像是死锁。。。

怎么办呢?我们可以在HiddenHttpMethodFilter之前,再配置一个Filter,先把参数解析了不就好了嘛。

于是乎就配置一个MultipartFilter这个过滤器,指定一个初始化参数:multipartResolverBeanName,它的值为我们在spirng配置文件中配置好的CommonsMultipartResolver这个Bean的id就好了。

然而并非如此。因为在MultipartFilter进行过滤的时候,调用的Bean,是在Spring的根容器中配置的Bean。我们配置的普通的Spirng配置文件,只是DispatcherServlet的配置文件,并不是根容器的配置文件。(其实DispatcherServlet只是一个普通Servlet,我们可以简单的认为它也有一个自己的上下文环境,起着Spring容器的作用)。

这里简单说一下,上下文环境(context),有ServletContext、DispatcherServlet的上下文、以及spring根上下文webApplicationContext。在web项目中,ServletContext包含着webApplicationContext以及Dispatcher的Context,同时,web Application Context也是Dispatcher的context的父容器。

spring在执行ApplicationContext的个体Bean时,如果在自己的context上下文中找不到Bean,就会到父容器中去找。但是父容器不会找子容器。

所以,我们需要把CommonsMultipartResolver配置在WebApplicationContext中。单独写一个配置文件:root-context.xml:

<!--配置文件上传解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--指定文件上传的总大小不超过20M-->
        <property name="maxUploadSize" value="20000000"/>
        <property name="defaultEncoding" value="utf-8"/>
    </bean>

在web.xml中,配置ServletContext的初始化参数:contextConfigLocation,仔配置一个监听器:ContextLoadLinstener。

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/root-context.xml</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>


    <filter>
        <filter-name>MultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
        <init-param>
            <param-name>multipartResolverBeanName</param-name>
            <param-value>multipartResolver</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>MultipartFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/applicationContext.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

当Servlet容器加载的时候,容器会读web.xml中的listener元素以及context-param元素。

紧接着,容器创建ServletContext,整个web项目通用。然后容器根据读取的listener以及初始化参数,进行相应的处理。

如此,就解决了问题。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值