当使用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以及初始化参数,进行相应的处理。
如此,就解决了问题。