上传和下载
上传
SpringMVC 中处理上传有两种方式:
- 使用Apache Commons FileUpload组件
- 利用Servlet3.0及其更高的版本的内置支持。Servlet3.0开始默认支持文件上传功能,Tomcat7.0版本就开始支持Servlet3.0.如果将应用程序部署到支持Servlet3.0及其更高版本的容器中,推荐使用这种方式
客户端准备
为了文件上传,必须将HTML表格的enctype属性值设置为multipart/form-data,例如:
表格中中必须包含类型为file的一个input元素,它将显示成一个按钮,点击时,它会打开一个对话框,用来选择文件(spring表单库中没有type=file,所以还是用HTML的input)
在HTML5之前,如果想要上传多个文件,必须使用多个文件input元素。但是在HTML5中,通过input元素引用多个multiple属性,使得多个文件上传变得更加简单。在HTML5中编写以下任意一行代码,便可生成一个按钮来选择多个文件:
<input type="file" name="fieldName" multiple/> <input type="tfile" name="fieldName" multple="multiple"/> <input type="file" name="fieldName" multiple=""/>i
MultipartFile 接口
在SpringMVC中处理已经上传的文件是很简单。上传到SpringMVC引用程序中的文件会被包在一个MultipartFile对象中。你唯一的任务是:用类型为MultipartFile的属性编写一个domain类。
org.springfarmwork.web.multipart.MultipartFile 接口具有以下方法:
接口 | 作用 |
byte[] getBytes() | 以字节数组的形式返回文件的内容 |
String getContextType() | 返回文件的内容类型 |
InputStram getInputStram() |
返回一个inputStream,从中读取文件的内容 |
String getName() | 一多部分的形式返回参数的名称 |
String getOriginalFilename() | 返回客户端本地驱动器中的初始文件名 |
long getSize() | 以字节为单位,返回文件的大小 |
boolean isEmpty() | 表示被上传文件是否为空 |
void transferTo(File destination) | 将文件保存在目标目录下 |
用Commons FileUpload上传文件
对于版本低于Servlet3.0的容器,需要使用Apache Commons FileUpload组件,为了让Commons FileUpload 正常工作,还需要另一个Apache Commons组件:Apache Commons IO。
导入所需jar包之后,还需要在SpringMVC配置文件中定义multipartResolver bean,这里使用的CommonsMultipartResolver对应使用Commons组件
<!-- 配置使用commons-fileupload进行文件上传 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="2000000"/> </bean>
当然我们可以设置一个上传的最大值(maxUploadSize 字节为单位),如果没有这个属性,则没有最大文件容量限制。没有最大文件容量限制不意味着可以上传任意大小的文件,因为上传过大的文件要花费很长的时间,这样会导致服务器超时。为了处理超大文件的问题,可以利用HTML5 File API将文件切片,然后再分别上传这些文件。
Domain类
其实就是将对象的属性类型设置为MultipartFile类型,为了适配多个文件上传,我们可以用一个List:
控制器
我们使用Multipart提供的API操作文件即可:获得Multipart类型的属性对象->获得文件名->设置保存路径->使用transferTo方法保存
@RequestMapping("/save-product") public String saveProduct(HttpServletRequest request, @ModelAttribute Product product,BindingResult result,Model model) { List<MultipartFile> files = product.getImages(); if(files!=null&&files.size()>0) { for(MultipartFile file:files) { String fileName=file.getOriginalFilename(); fileNames.add(fileName); File imageFile=new File(request.getServletContext().getRealPath("/image"),fileName); try { file.transferTo(imageFile); } catch (Exception e) { e.printStackTrace(); } } } //保存product进model model.addAttribute("product", product); return "productDetails"; }
这是一段将上传文件保存在部署路径下的image中的Action
页面
上传页面最重要的就是type=file的input:
我们在domain中使用的是名为images的List<MultipartFile>,所以在页面中可以使用数组的方式给input的name赋名,有多个上传input,下标就累加。
显示页面中,我们可以使用JSTL(foreach标签)+EL遍历出来即可:
<c:forEach items="${product.images }" var="image"> <li>${image.originalFilename }</li> <img width="100" src='<c:url value="/image/${image.originalFilename }"></c:url>'/> </c:forEach>
文件名使用originalFilename获得即可(可以看到MultipartFile的API有一个getOriginalFileName方法)。将img标签的src指向对应的图片路径即可。
用Servlet3.0及其更高版本上传文件
有了Servlet3.0,我们就不再需要Commons FileUpload组件了,不用再添加这个jar了。Servlet3.0以及其更高版本的容器中进行服务器文件上传编程,是围绕着注解类型MultipartConfig和javax.servlet.http.Part接口进行的。处理上传文件的Servlet必须以@MultipartConfig进行注解。
@MultipartConfig注解类型中出现的属性,如下:
- maxFileSize:上传问文件的最大容量,默认没有限制,超过指定大小的文件会被拒绝。
- maxRequestSize:表示多部分HTTP请求允许的最大容量,默认没有限制
- location:文件保存的位置
- fileSizeThreshold:上传文件超过这个容量界限,就会被写入磁盘
我们说了处理上传的Servlet需要用@MultipartConfig修饰,但是SpringMVC的DispatchServlet如果不修改源码就无法进行注解。但值得庆幸的是,Servlet3中有一种比较容易的方法,能使一个Servlet变成MultipartConfig Servlet,即给部署描述符(web.xml)中的Servlet声明赋值:
<servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> <!-- 配置servlet3.0文件上传--> <multipart-config> <max-file-size>20848820</max-file-size> <max-request-size>418018841</max-request-size> <file-size-threshold>1048576</file-size-threshold> </multipart-config> </servlet> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
此外,会需要在SpringMVC配置文件中使用一个不同于Commons方式的解析器:
<!-- 配置使用servlet3.0文件上传 --> <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"></bean>
其他的domain,Controller,页面我们都不需要修改,即可完成基于Servlet3.0及其以上版本的SpringMVC上传
总结一下这种方式:只是web.xml中包含一个multipart-config元素,springMVC配置文件使用的是StandardServletMultipartResolver这个解析器,其他的没什么与基于Commons FileUpload组件的方式不同,还是使用MultipartFile的API操作。
使用Part形式接受上传的文件
如果是将应用部署到Servlet3.0的容器中,那么MultiPartFile还有一个替代方案:SpringMVC接受javax.servlet.http.Part作为控制器方法的参数,可以使用Part替换MultipartFile。
@ResponseBody @RequestMapping(value="/upload_lunwen",method=RequestMethod.POST) public boolean saveProduct(@RequestParam(value="lunwen",required=true)Part lunwen, @RequestParam("userId")String userId,HttpServletRequest request) {
Part接口与MultipartFile接口没有太大的不同,只是方法名有一些差异:比如getSubmittedFileName()对应于getOriginalFilenmae(); write()对应于transferTo()。
值得一提的是,如果在编写控制器方法的时候,通过Part参数的形式接受文件上传,那么就不需要MutilpartResolver了,只有使用MutilpartFile的时候,我们才需要MutilpartResolver。
下载
像图片和HTML文件这样的静态资源,在浏览器中打开正确的URL即可下载。只要是该资源放在应用程序的目录下,或者放在应用程序目录的子目录下,而不是放在WEB-INF下。Servlet/JSP容器就会将该资源发送到浏览器。
然而,有时静态资源是放在WEB-INF或应用程序之外,或者是保存在一个数据库中,或者有时需要控制它的访问权限,防止其他网站交叉引用它。如果出现以上任意一种情况,都必须通过编程来发送资源。
为了将文件这样的静态资源发送到浏览器,需要在控制器中完成以下工作:
- 在方法中添加HttpServletResponse参数
- 为相应的内容设置文件内容类型(Content-Type)。如果不清楚内容类型,并且希望浏览器始终显示Save As(另存为)对话框(Chrome浏览器并不会显示,它默认都下载),则将它设置为application/octet-stream。这个值是不区分大小写的。
- 添加一个名为Content-Disposition的HTTP响应标题,并赋值为attachment;filename=fileName,这里的fileName是默认的文件名,应该出现在File Download(文件下载)对话框中,它通常与文件名同名,但并非一定如此。当然我们也可以自定义文件名。
之后我们就可以通过操作流来下载文件了:
FileInputStream fis=new FileInputStream(file); BufferedInputStram bis=new BufferedInputStream(fis); byte[] bytes=new byte[bis.available()]; response.setContentType(contentType); OutputStream os=response.getOutputStream(); bis.read(bytes); os.write(bytes);
首先要读取文件作为FileInputStream,并将内容加载到一个字节数组。随后,获取HttpServletResponse的OutputStream,并调用其write方法传入字节数组。
但是将文件发送到HTTP客户端的更好方法是使用Java NIO的Files.copy()方法:
Path path=Paths.get(...);
Files.copy(path,response.getOutputStream);
代码更少,运行速度更快。
例子
我们将文本文件1.txt放在/WEB-INF/file下,如果没有登录进到登录页进行登录,只有登录用户在可以下载这个文件,为了不让程序太复杂,我们使用写死用户名lz和密码123。开始使用SpringMVC下载这个静态资源吧。
Controller
登录请求方法:
@RequestMapping("/login") public String login(@ModelAttribute Login login,HttpSession session,Model model) { if("lz".equals(login.getUserName())&&"123".equals(login.getPassword())) { session.setAttribute("loggedId", true); return "main"; }else { return "loginForm"; } }
登录成功就到/WEB-INF/main.jsp页面下载,没有登录就去/WEB-INF/loginForm.jsp登录.loginForm.jsp就是个登录页;main.jsp有一个超链接,指向 downloadResource 这个请求。
重头戏:下载的请求方法:
@RequestMapping("/downloadResource") public String downloadResource(HttpSession session,HttpServletResponse response, HttpServletRequest request,Model model) { if(session==null||session.getAttribute("loggedId")==null) { model.addAttribute("login", new Login()); return "loginForm"; } String dataDir=request.getServletContext().getRealPath("/WEB-INF/file"); Path path=Paths.get(dataDir, "1.txt"); if(Files.exists(path)) { response.setContentType("application/octet-stream"); response.addHeader("Content-Disposition", "attachment;filename=1.text"); try { Files.copy(path, response.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } return null; }
先通过session判断是否登录成功,如果没有就去登录,登录用户就通过NIO下载。设置ContentType->设置Content-Desposition这个响应头->调用Files.copy()方法下载
防止交叉引用
有人可以你网站中静态资源的链接放在他的网站上,好像这些资源原本就属于他的一样。如果通过编程控制,使得只有referer的值与你的域名相同时才放出资源,就可以防止这种行为发生。当然,有些聪明的窃贼仍有办法下载你的东西,但是绝不会像从前那样不费吹灰之力就能办到。
只要是页面中请求服务器就会带上Referer这个请求头,我们直接拿到就可以: