Spring MVC 文件上传
一如既往记录下常用而又容易忘记的东西,本篇博文主要针对Spring MVC是如何上传文件的。以下记录两种上传方法并针对案例进行记录。(有关spring mvc其他配置省略)
1、使用Spring MVC 上传文件必须配置文件解析器,如下:
<!-- 上传文件拦截,设置最大上传文件大小 10M=10*1024*1024(B)=10485760 bytes --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760" /> <property name="maxInMemorySize" value="10240000" /> <property name="defaultEncoding" value="UTF-8" /> </bean>
2、建立上传文件表单代码,其中要注意form表单中的enctype 属性,必须存在且必须为multipart/form-data。还有当form中存在button标签时,用ajax异步提交表单后,也面会被刷新。原因:button 存在时会再次提交一下表单,所以页面被刷新了。
1 <!-- 2 enctype 属性值: 3 1、application/x-www-form-urlencoded 在发送前编码所有字符(默认) 4 2、multipart/form-data 不对字符编码。 在使用包含文件上传控件的表单时,必须使用该值。 5 3、text/plain 空格转换为 "+" 加号,但不对特殊字符编码。 6 --> 7 <div class="row"> 8 <form method="post" enctype="multipart/form-data" id="form1"> 9 <div><label>1、采用流的方式</label></div> 10 <div class="col-sm-7" style="padding-left:0px"> 11 <div class="input-group"> 12 <input type="text" class="form-control" id="showFileInput1"> 13 <input type="file" style="display:none" name="txtFile" id="uploadFileInput1" accept="text/plain"> 14 <span class="input-group-addon" id="uploadFileButton1"> 15 <span class="glyphicon glyphicon-folder-open"></span> 16 <label>浏览</label> 17 </span> 18 </div> 19 </div> 20 <div class="col-sm-5"> 21 <!-- 22 当form中存在button标签时,用ajax异步提交表单后,也面会被刷新。(感觉很诡异) 23 原因:button 存在时会再次提交一下表单,所以页面被刷新了。(之前认为button type='submit' 时)button才有提交表单的功能。 24 --> 25 <a class="btn btn-default" id="submit1">上传</a> 26 </div> 27 </form> 28 </div>
3.1、使用CommonsMultipartFile接收上传文件,其中要注意的是:方法中CommonsMultipartFile对应的变量名如果不是对应表单中文件输入框<input type="file" style="display:none" name="txtFile" id="uploadFileInput1" accept="text/plain">的名称就必须加上@RequestParam("txtFile") 强制注入。
/** * @Description: 通过文件流的形式上传 * @param file @RequestParam("txtFile") 将name=txtFile控件得到的文件封装成CommonsMultipartFile对象, * 如果不这样做会报CommonsMultipartFile没有初始化的错误 * java.lang.NoSuchMethodException: org.springframework.web.multipart.commons.CommonsMultipartFile.<init>() * @return * @author yuanfy * @date 2017年9月15日 下午4:36:11 * @version 6.5 */ @RequestMapping(value="test/upload1") @ResponseBody public String testUpload1(@RequestParam("txtFile")CommonsMultipartFile file){ Long times = System.currentTimeMillis(); if (file == null) { return null; } StringBuilder fileContent = new StringBuilder(); //1、获取文件信息 FileUtils.getFileInfo(file, fileContent); //2、上传文件并获取文件内容 try { file.transferTo(new File("F:\\text.log"));//另存文件 fileContent.append(FileUtils.getFileContentByLine(file.getInputStream())); } catch (IOException e) { return "获取文件内容失败"; } //3、返回文件信息和内容 String content = fileContent.toString(); content = content.replace("times", (System.currentTimeMillis()-times) + "ms"); return content; }
界面效果图如下:
3.2、使用CommonsMultipartResolver获取存放文件对象,拿到文件对象后遍历每个文件上传及获取相关的内容。
1 @RequestMapping(value="test/upload2") 2 @ResponseBody 3 public String testUpload2(HttpServletRequest request){ 4 Long times = System.currentTimeMillis(); 5 StringBuilder fileContent = new StringBuilder(); 6 //1.根据servletContext获取多文件上传解析组件 7 CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext()); 8 if (!multipartResolver.isMultipart(request)) { 9 return "不是上传文件表单,请检查表单属性"; 10 } 11 //2.将请求对象转换为多文件请求对象。 12 MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request; 13 //3、根据多文件请求对象获取文件存放Map 14 Map<String, MultipartFile> fileMap = multipartHttpServletRequest.getFileMap(); 15 Iterator<Entry<String, MultipartFile>> iterator = fileMap.entrySet().iterator(); 16 //4、迭代文件Map,获取具体的MultipartFile 17 while (iterator.hasNext()) { 18 Entry<String, MultipartFile> entry = iterator.next(); 19 MultipartFile multipartFile = entry.getValue(); 20 //获取文件头信息 21 FileUtils.getFileInfo(multipartFile, fileContent); 22 try { 23 //上传文件 24 multipartFile.transferTo(new File("F:\\text.log")); 25 //获取文件内容 26 fileContent.append(FileUtils.getFileContentByLine(multipartFile.getInputStream())); 27 }catch (Exception e) { 28 // TODO Auto-generated catch block 29 e.printStackTrace(); 30 } 31 } 32 //5、返回文件信息和内容 33 String content = fileContent.toString(); 34 content = content.replace("times", (System.currentTimeMillis()-times) + "ms"); 35 return content; 36 }
其中第一步获取文件解析器对象应该都清楚只要在容器中配置了对应的对象我们就可以获取到它,而它有根据上下文获取的构造函数就方便多了。
1 /** 2 * Constructor for standalone usage. Determines the servlet container's 3 * temporary directory via the given ServletContext. 4 * @param servletContext the ServletContext to use 5 */ 6 public CommonsMultipartResolver(ServletContext servletContext) { 7 this(); 8 setServletContext(servletContext); 9 }
然后根据request判断是否还有上传文件的表单,如果不是肯定直接返回,我们看看源码中是怎么判断的。
1 //CommonsMultipartResolver.class 主要判断request是否为空 2 @Override 3 public boolean isMultipart(HttpServletRequest request) { 4 return (request != null && ServletFileUpload.isMultipartContent(request)); 5 } 6 //ServletFileUpload 主要判断是否是post方法,因为上传文件必须是post提交,其实我们可以在我们自定义controller中的方法指定访问 7 public static final boolean isMultipartContent(HttpServletRequest request) { 8 if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) { 9 return false; 10 } 11 return FileUploadBase.isMultipartContent(new ServletRequestContext(request)); 12 } 13 //FileUploadBase.class 如果请求是MULTIPART 则返回true 14 public static final boolean isMultipartContent(RequestContext ctx) { 15 String contentType = ctx.getContentType();//类似:multipart/form-data; boundary=----WebKitFormBoundaryLF3eM94lDB0ocQxT 16 if (contentType == null) { 17 return false; 18 } 19 if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) { 20 return true; 21 } 22 return false; 23 }
所以如果request是上传文件的请求对象,则进行第二步。将request转换成多文件请求对象,然后获取存放文件的map。
可想而知这种方法效率是比第一种要低的,因为他要遍历文件map,但是在Spring MVC常用的却是这种方法。
效果图如下:
其中FileUtils.java代码如下:
1 package com.yuanfy.monitorsite.common.util; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.IOException; 7 import java.io.InputStream; 8 9 import org.springframework.web.multipart.MultipartFile; 10 11 /** 12 * @Description: 文件工具类方法 13 * @author yuanfy 14 * @date 2017年9月15日 下午2:45:40 15 * @version 1.0 16 */ 17 public class FileUtils { 18 /** 19 * @Description: 获取文件信息 20 * @param file CommonsMultipartFile类型的文件 21 * @param fileContent StringBuilder,封装文件信息 22 * @author yuanfy 23 * @date 2017年9月15日 下午2:51:34 24 * @version 1.0 25 */ 26 public static void getFileInfo(MultipartFile file, StringBuilder fileContent) { 27 fileContent.append("文件名称:\t\t").append(file.getName()).append("\n") 28 .append("文件原始名称:\t").append(file.getOriginalFilename()).append("\n") 29 .append("文件大小:\t\t").append(file.getSize()).append("\n") 30 .append("文件类型:\t\t").append(file.getContentType()).append("\n") 31 .append("读取文件时长:\t times").append("\n"); 32 } 33 34 /** 35 * @Description: 根据文件对象获取文件内容 36 * @param file 37 * @author yuanfy 38 * @date 2017年9月15日 下午5:01:57 39 * @version 1.0 40 * @throws IOException 41 * @throws FileNotFoundException 42 */ 43 public static String getFileContentByLine(File file) throws FileNotFoundException, IOException { 44 return getFileContentByLine(new FileInputStream(file)); 45 } 46 /** 47 * @Description: 根据文件输入流对象获取文件内容 48 * @param in 文件输入流对象 49 * @author yuanfy 50 * @date 2017年9月15日 下午5:01:57 51 * @version 1.0 52 * @throws IOException 53 */ 54 public static String getFileContentByLine(InputStream in) throws IOException { 55 StringBuilder fileContent = new StringBuilder(); 56 57 byte[] bytes = new byte[1024]; 58 int len = 0; 59 while ((len = in.read(bytes)) != -1) { 60 String content = new String(bytes, 0, len, "UTF-8"); 61 fileContent.append(content); 62 } 63 StreamUtils.close(in); 64 return fileContent.toString(); 65 } 66 }
当然要想查看整个代码,可以访问我项目的整个代码:https://github.com/YuanFY/blog_demo。