commons-fileupload实现上传进度条的显示
本文将使用 apache fileupload ,spring MVC jquery 实现一个带进度条的多文件上传, 由于fileupload 的局限,暂不能实现每个上传文件都显示进度条,只能实现一个总的进度条
优点:不用引入第三方的组件(如js框架,flash等)
缺点:如果同时上传多个文件,由于apache fileupload API的限制,只能显示一个总的进度条
1.引入环境所需要的jar包
引入上传工具类jar包
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
引入mvc处理json数据jar包(@requestbody&@responsebody)
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.5.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.1</version> </dependency>
2.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery.js"></script> <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css"> <script type="text/javascript"> $(document).ready(function(){ $('#subbut').bind('click', function(){ $('#fForm').submit(); var eventFun = function(){ $.ajax({ type: 'GET', url: '${pageContext.request.contextPath}/test/process.action', data: {}, dataType: 'json', success : function(data){ $("#fff").html("上传进度:"+data.show); $( "#proBar" ).progressbar({ value: data.rate }); if(data.rate == 100){ window.clearInterval(intId); alert("上传完成"); } }});}; var intId = window.setInterval(eventFun,100); }); }); </script> <title>Insert title here</title> </head> <body> <form id='fForm' action="${pageContext.request.contextPath}/test/testbar.action" encType="multipart/form-data" method="post" target="uploadf"> <div> <label>上传文件:</label> <div> <input type="file" name="file" style="width:550"> </div> <label id="fff"></label> <div> <div style="width:50%"> <div id = 'proBar'></div> </div> </div> </div> <div > <div > <button type="button" id="subbut" >submit</button> </div> </div> </form> <iframe name="uploadf" style="display:none"></iframe> </body> </html>
这里注意需要引入js和css,如果需要更换进度条的样式,可以引入其他的css(如bootstarp等)
3.后台的实现
import java.io.File; import java.util.Iterator; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemFactory; import org.apache.commons.fileupload.ProgressListener; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/test") public class FileUploadController { private final Logger log = Logger.getLogger(FileUploadController.class); /** * upload 上传文件 */ @RequestMapping(value = "/testbar.action", method = RequestMethod.POST) public ModelAndView upload(HttpServletRequest request,HttpServletResponse response) throws Exception { final HttpSession hs = request.getSession(); ModelAndView mv = new ModelAndView(); boolean isMultipart = ServletFileUpload.isMultipartContent(request); if(!isMultipart){ return mv; } // Create a factory for disk-based file items FileItemFactory factory = new DiskFileItemFactory(); // Create a new file upload handler ServletFileUpload upload = new ServletFileUpload(factory); //解决上传文件名的中文乱码 upload.setHeaderEncoding("UTF-8"); upload.setProgressListener(new ProgressListener(){ public void update(long pBytesRead, long pContentLength, int pItems) { ProcessInfo pri = new ProcessInfo(); pri.itemNum = pItems; pri.readSize = pBytesRead; pri.totalSize = pContentLength; pri.show = pBytesRead+"/"+pContentLength+" byte"; pri.rate = Math.round(new Float(pBytesRead) / new Float(pContentLength)*100); hs.setAttribute("proInfo", pri); } }); // Parse the request List items = upload.parseRequest(request); // Process the uploaded items Iterator iter = items.iterator(); while (iter.hasNext()) { FileItem item = (FileItem) iter.next(); if (item.isFormField()) { String name = item.getFieldName(); String value = item.getString("utf-8"); System.out.println("this is common feild!"+name+"="+value); } else { System.out.println("this is file feild!"); String fieldName = item.getFieldName(); String fileName = item.getName().substring(item.getName().lastIndexOf("\\")+1);; String contentType = item.getContentType(); boolean isInMemory = item.isInMemory(); long sizeInBytes = item.getSize(); File uploadedFile = new File("c://"+fileName); item.write(uploadedFile); } } return mv; } /** * process 获取进度 */ @RequestMapping(value = "/process.action", method = RequestMethod.GET) public @ResponseBody Object process(HttpServletRequest request,HttpServletResponse response) throws Exception { return ( ProcessInfo)request.getSession().getAttribute("proInfo"); } //精度条pojo class ProcessInfo{ public long totalSize = 1; public long readSize = 0; public String show = ""; public int itemNum = 0; public int rate = 0; } }
4.SpringMVC中servletFileUpload.parseRequest(request)解析为空获取不到数据问题
问题产生的原因
先看一下springmvc中上传的配置
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8" /> <property name="maxUploadSize" value="2000000000" /> </bean>
再看看controller中使用
方式一
public void upload2(HttpServletRequest request) { // 转型为MultipartHttpRequest try { MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; List<MultipartFile> fileList = multipartRequest.getFiles("file"); for (MultipartFile mf : fileList) { if(!mf.isEmpty()){ } } } catch (Exception e) { e.printStackTrace(); } }
方式二
public String upload(HttpServletRequest request, @RequestParam(value = "file") MultipartFile[] files) { try { for (MultipartFile mf : files) { if(!mf.isEmpty()){ } } } catch (Exception e) { e.printStackTrace(); } return "upload"; }
这里springMVC 都为我们封装好成自己的文件对象了,转换的过程就在我们所配置的CommonsMultipartResolver这个转换器里面.他的转换器里面就是调用common-fileupload的方式解析,然后再使用parseFileItems()方法封装成自己的文件对象.大家应该发现了上面的这句代码,已经使用过fileUpload解析过request了,你在Controller里面接收到的request都已经是解析过的,你再次使用upload进行解析获取到的肯定是空,这个就是问题的所在
解决方式一(以上案例就是用此解决方法)
1.删除mvc里面上传的配置
<!-- <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> --> <!-- 指定所上传文件的总大小不能超过200KB。注意maxUploadSize属性的限制不是针对单个文件,而是所有文件的容量之和 --> <!-- <property name="maxUploadSize" value="200000"/> --> <!-- </bean> -->
2.在控制器里面自己完成request的解析(当然上面spring MVC提供的两种方法是不能用的,所有上传的地方都需要自己做处理)
public void upload3(HttpServletRequest request) { DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); try { List<FileItem> list = upload.parseRequest(request); for(FileItem item : list){ if(item.isFormField()){ }else{ //item.write(new File("")); } } } catch (FileUploadException e) { e.printStackTrace(); } }
解决方式二(重写listener)
1.创建状态pojo
public class Progress { public long totalSize = 1; public long readSize = 0; public String show = ""; public int itemNum = 0; public int rate = 0; public void setTotalSize(long totalSize) { this.totalSize = totalSize; } public void setReadSize(long readSize) { this.readSize = readSize; } public String getShow() { return readSize+"/"+totalSize+" byte"; } public int getItemNum() { return itemNum; } public void setItemNum(int itemNum) { this.itemNum = itemNum; } public int getRate() { return Math.round(new Float(readSize) / new Float(totalSize)*100); } }
2.写自己的listener
import javax.servlet.http.HttpSession; import org.apache.commons.fileupload.ProgressListener; public class FileUploadListener implements ProgressListener{ private HttpSession session; public void setSession(HttpSession session){ this.session=session; Progress status = new Progress(); session.setAttribute("status", status); } /* * pBytesRead 到目前为止读取文件的比特数 pContentLength 文件总大小 pItems 目前正在读取第几个文件 */ public void update(long pBytesRead, long pContentLength, int pItems) { Progress status = (Progress) session.getAttribute("status"); status.setReadSize(pBytesRead); status.setTotalSize(pContentLength); status.setItemNum(pItems); } }
3.写自己的resolver
public class CommonsMultipartResolverExt extends CommonsMultipartResolver{ @Override protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { FileUploadListener listener = new FileUploadListener(); listener.setSession(request.getSession()); String encoding = determineEncoding(request); FileUpload fileUpload = prepareFileUpload(encoding); fileUpload.setProgressListener(listener); try { List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); return parseFileItems(fileItems, encoding); } catch (FileUploadBase.SizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); } catch (FileUploadException ex) { throw new MultipartException("Could not parse multipart servlet request", ex); } } }
4.配置上传resolver
<bean id="multipartResolver" class="com.fyh.www.common.CommonsMultipartResolverExt"></bean>
5.测试
@Controller @RequestMapping("/test2") public class FileUploadController2 { private final Logger log = Logger.getLogger(FileUploadController2.class); /** * upload 上传文件 */ @RequestMapping(value = "/testbar.action", method = RequestMethod.POST) public ModelAndView upload(HttpServletRequest request,HttpServletResponse response) throws Exception { ModelAndView mv = new ModelAndView(); MultipartHttpServletRequest multipartRequest=(MultipartHttpServletRequest) request; MultipartFile file = multipartRequest.getFile("file"); InputStream inputStream = file.getInputStream(); FileUtils.copyInputStreamToFile(inputStream, new File("e://"+file.getOriginalFilename())); return mv; } /** * process 获取进度 */ @RequestMapping(value = "/process.action", method = RequestMethod.GET) public @ResponseBody Object process(HttpServletRequest request,HttpServletResponse response) throws Exception { return ( Progress)request.getSession().getAttribute("status"); } }
此方案在获取监控进度的同时并不因影响mvc原有上传方法的使用
解决方式三(需要用到进度条的上传时,写独立的servlet,servlet+apache uploadfile 与springmvc互不干预)不建议用此方法