上一篇文章介绍了Spring MVC如何处理静态资源文件,本文讲解如何使用Spring MVC做文件上传,附带深入一下Spring MVC的ModelAndView。增加一个Controller,叫FileUploadController:
- package org.springframework.samples.mvc.fileupload;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- @Controller
- @RequestMapping("/fileupload")
- publicclass FileUploadController {
- @RequestMapping(method=RequestMethod.GET)
- publicvoid fileUploadForm() {
- }
- }
package org.springframework.samples.mvc.fileupload;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/fileupload")
public class FileUploadController {
@RequestMapping(method=RequestMethod.GET)
public void fileUploadForm() {
}
}
这个类和HelloWorld中的Controller类就有点差别了,首先类名上加入了@RequestMapping注解,这样在做HandlerMapping时,SpringMVC会将类名和方法名的@RequestMapping连接起来,而本例方法名前并没有具体路径(当然也可以写),因此最终映射的URL还是"/fileupload",另外一点就是在方法级@RequestMapping的注解增加了一个RequestMothod.GET,意思是只有以GET方式提交的"/fileupload"URL请求才会进入fileUploadForm()方法,其实根据HTTP协议,HTTP支持一系列提交方法(GET,POST,PUT,DELETE),同一个URL都可以使用这几种提交方式,事实上SpringMVC正是通过将同一个URL的不同提交方法对应到不同的方法上达到RESTful。
访问http://localhost:8080/web/fileupload,后台报错:
- 2012-03-2019:12:44.557:WARN::/web/fileupload
- javax.servlet.ServletException: Circular view path [fileupload]: would dispatch back to the current handler URL [/web/fileupload] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
- at org.springframework.web.servlet.view.InternalResourceView.prepareForRendering(InternalResourceView.java:292)
- ...
2012-03-20 19:12:44.557:WARN::/web/fileupload
javax.servlet.ServletException: Circular view path [fileupload]: would dispatch back to the current handler URL [/web/fileupload] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
at org.springframework.web.servlet.view.InternalResourceView.prepareForRendering(InternalResourceView.java:292)
...
正如Hint中指出的,没有指定一个View,大家可能会有疑问,HelloWorld中不是可以将结果返回到浏览器么?注意HelloWorld中的@ResponseBody跟View没有任何关系,HelloWorld中其实也是返回了一个ModelAndView,但这个View为null,并且在返回前,已经将结果通过HttpServletResponse发送给浏览器了。
那么怎么指定一个View呢?通常我们会想到一个默认的View,即如果没有写特殊的View,所有的结果都将转到这个默认的View上,最后将这个View推到浏览器展示。在servlet-context.xml中增加一个配置:
- <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
- <beans:beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <beans:propertyname="prefix"value="/WEB-INF/views/"/>
- <beans:propertyname="suffix"value=".jsp"/>
- </beans:bean>
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <beans:property name="prefix" value="/WEB-INF/views/" /> <beans:property name="suffix" value=".jsp" /> </beans:bean>
配置了一个Bean,对应的类为InternalResourceViewResolver,并设置了prefix属性为"/WEB-INF/views",suffix属性为".jsp",这个类为视图解析器(view resolver),支持InternalResourceView(比如Servlet和JSP)及其子类如JstlView,默认的是InternalResourceView,如果有JSTL API存在的话,就是JstlView。
现在有一个默认的View了,这个View将view名字解析到/WEB-INF/views/目录下对应的jsp文件。
哪里有view名字?当带有@RequestMapping的方法返回void时,@RequestMapping映射的URL路径即view名字。
所以当使用GET方式访问http://localhost:8080/web/fileupload时,最终会找WEB-INF/views/目录下有没有fileupload.jsp文件,有则将此jsp文件显示在浏览器上,没有则报如下错误:
- ERROR: PWC6117: File "/Users/stephansun/Documents/workspace/samples/samples-web/src/main/webapp/WEB-INF/views/fileupload.jsp" not found
ERROR: PWC6117: File "/Users/stephansun/Documents/workspace/samples/samples-web/src/main/webapp/WEB-INF/views/fileupload.jsp" not found
fileUploadForm.jsp文件为:
- <%@ taglib uri="http://java.sun.com/jsp/jstl/core"prefix="c" %>
- <html>
- <head>
- <title>fileupload | mvc-showcase</title>
- <linkhref="<c:url value="/resources/form.css" />" rel="stylesheet"type="text/css"/>
- <scripttype="text/javascript"src="<c:url value="/resources/jquery/1.6/jquery.js" />"></script>
- <scripttype="text/javascript"src="<c:url value="/resources/jqueryform/2.8/jquery.form.js" />"></script>
- </head>
- <body>
- <divid="fileuploadContent">
- <h2>File Upload</h2>
- <p>
- See the <code>org.springframework.samples.mvc.fileupload</code> package for the @Controller code
- </p>
- <formid="fileuploadForm"action="fileupload"method="POST"enctype="multipart/form-data"class="cleanform">
- <divclass="header">
- <h2>Form</h2>
- <c:iftest="${not empty message}">
- <divid="message"class="success">${message}</div>
- </c:if>
- </div>
- <labelfor="file">File</label>
- <inputid="file"type="file"name="file"/>
- <p><buttontype="submit">Upload</button></p>
- </form>
- </div>
- </body>
- </html>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>fileupload | mvc-showcase</title>
<link href="<c:url value="/resources/form.css" />" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="<c:url value="/resources/jquery/1.6/jquery.js" />"></script>
<script type="text/javascript" src="<c:url value="/resources/jqueryform/2.8/jquery.form.js" />"></script>
</head>
<body>
<div id="fileuploadContent">
<h2>File Upload</h2>
<p>
See the <code>org.springframework.samples.mvc.fileupload</code> package for the @Controller code
</p>
<form id="fileuploadForm" action="fileupload" method="POST" enctype="multipart/form-data" class="cleanform">
<div class="header">
<h2>Form</h2>
<c:if test="${not empty message}">
<div id="message" class="success">${message}</div>
</c:if>
</div>
<label for="file">File</label>
<input id="file" type="file" name="file" />
<p><button type="submit">Upload</button></p>
</form>
</div>
</body>
</html>
上传页面正确显示后,我们需要一个方法处理上传请求,processUpload,现在FileUploadController看起来是这样的:
- package org.springframework.samples.mvc.fileupload;
- import java.io.IOException;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.springframework.web.multipart.MultipartFile;
- @Controller
- @RequestMapping("/fileupload")
- publicclass FileUploadController {
- @RequestMapping(method=RequestMethod.GET)
- publicvoid fileUploadForm() {
- }
- @RequestMapping(method=RequestMethod.POST)
- publicvoid processUpload(@RequestParam MultipartFile file, Model model) throws IOException {
- model.addAttribute("message", "File '" + file.getOriginalFilename() + "' uploaded successfully");
- }
- }
package org.springframework.samples.mvc.fileupload;
import java.io.IOException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
@Controller
@RequestMapping("/fileupload")
public class FileUploadController {
@RequestMapping(method=RequestMethod.GET)
public void fileUploadForm() {
}
@RequestMapping(method=RequestMethod.POST)
public void processUpload(@RequestParam MultipartFile file, Model model) throws IOException {
model.addAttribute("message", "File '" + file.getOriginalFilename() + "' uploaded successfully");
}
}
这里出现了一个新注解@RequestParam,先看一下JSP页面的代码片段:
- <inputid="file"type="file"name="file"/>
<input id="file" type="file" name="file" />
input框提交的参数名就是file,@RequestParam注解自动将POST提交的参数中名为file的封装到一个MultipartFile类中,一切都在开发人员眼皮底下做好了,大大减少了开发人员的代码量。现在我们在上传页面选择一个文件上传,后台报错,看报错日志:
- java.lang.IllegalArgumentException: Expected MultipartHttpServletRequest: is a MultipartResolver configured?
- at org.springframework.util.Assert.notNull(Assert.java:112)
java.lang.IllegalArgumentException: Expected MultipartHttpServletRequest: is a MultipartResolver configured?
at org.springframework.util.Assert.notNull(Assert.java:112)
说"是不是没配置MultipartResolver?",恩,还没有配,所有在servlet-context.xml中加上这么一段配置:
- <!-- Only needed because we require fileupload in the org.springframework.samples.mvc.fileupload package -->
- <beans:beanid="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
<!-- Only needed because we require fileupload in the org.springframework.samples.mvc.fileupload package --> <beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
这个Bean也是一个视图解析器(view resolver)。
完整的XML:
- <?xmlversion="1.0"encoding="UTF-8"?>
- <beans:beansxmlns="http://www.springframework.org/schema/mvc"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:beans="http://www.springframework.org/schema/beans"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
- xsi:schemaLocation="
- http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
- <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
- <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory -->
- <resourcesmapping="/resources/**"location="/resources/"/>
- <!-- Imports user-defined @Controller beans that process client requests -->
- <beans:importresource="controllers.xml"/>
- <!-- You have to add this because you had a <resources/> declare -->
- <mvc:annotation-driven/>
- <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
- <beans:beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <beans:propertyname="prefix"value="/WEB-INF/views/"/>
- <beans:propertyname="suffix"value=".jsp"/>
- </beans:bean>
- <!-- Only needed because we require fileupload in the org.springframework.samples.mvc.fileupload package -->
- <beans:beanid="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
- </beans:beans>
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure --> <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory --> <resources mapping="/resources/**" location="/resources/" /> <!-- Imports user-defined @Controller beans that process client requests --> <beans:import resource="controllers.xml" /> <!-- You have to add this because you had a <resources/> declare --> <mvc:annotation-driven/> <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <beans:property name="prefix" value="/WEB-INF/views/" /> <beans:property name="suffix" value=".jsp" /> </beans:bean> <!-- Only needed because we require fileupload in the org.springframework.samples.mvc.fileupload package --> <beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" /> </beans:beans>
然后再次上传文件,页面显示:
- File 'a.js' uploaded successfully
File 'a.js' uploaded successfully
上传成功,最后我们分析一下FileUploadController中的processUpload方法:
- @RequestMapping(method=RequestMethod.POST)
- publicvoid processUpload(@RequestParam MultipartFile file, Model model) throws IOException {
- model.addAttribute("message", "File '" + file.getOriginalFilename() + "' uploaded successfully");
- }
@RequestMapping(method=RequestMethod.POST)
public void processUpload(@RequestParam MultipartFile file, Model model) throws IOException {
model.addAttribute("message", "File '" + file.getOriginalFilename() + "' uploaded successfully");
}
Model就是MVC模式中的M,Model层封装了要传递给View层显示的值。
最后更新一下pom.xml文件,添加两个依赖:
- <!-- File Upload -->
- <dependency>
- <groupId>commons-fileupload</groupId>
- <artifactId>commons-fileupload</artifactId>
- <version>1.2.2</version>
- </dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- <version>2.0.1</version>
- </dependency>
<!-- File Upload --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.0.1</version> </dependency>
好像前面写的代码根本没有涉及到这两个jar包啊,为什么要引入?
之前我写道
底层真正由谁来做的呢?我们加入了multipartResolver这个Bean,它对应的类为
org.springframework.web.multipart.commons.CommonsMultipartResolver,
看看这个类的源代码,import中赫然写着:
- import org.apache.commons.fileupload.FileItem;
- import org.apache.commons.fileupload.FileItemFactory;
- import org.apache.commons.fileupload.FileUpload;
- import org.apache.commons.fileupload.FileUploadBase;
- import org.apache.commons.fileupload.FileUploadException;
- import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
因此SpringMVC封装了commons-fileupload上传组件,真正起上传作用的还是commons-fileupload-1.2.2.jar这个jar包里面的类。