前言
在SpringMVC项目中需要用到文件上传的功能,故对比了网络上比较通用的上传方式以及效率。
期间用到了两个链接,先放上:
一:commonsmultipartresolver 的源码
二:该文章涉及的代码 GitHub库: https://github.com/SDUmzg/CodeTools
简单的前端显示
前端显示没有加未选择文件的异常处理,超级简洁。
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!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">
<title>Insert title here</title>
</head>
<body>
<form name="serForm" action="/file/fileUpload" method="post" enctype="multipart/form-data">
<h1>采用流的方式上传文件</h1>
<input type="file" name="file">
<input type="submit" value="upload"/>
</form>
<form name="Form2" action="/file/fileUploadTransto" method="post" enctype="multipart/form-data">
<h1>采用multipart提供的file.transfer方法上传文件</h1>
<input type="file" name="file">
<input type="submit" value="upload"/>
</form>
<form name="Form2" action="/file/springUpload" method="post" enctype="multipart/form-data">
<h1>使用spring mvc提供的类的方法上传文件</h1>
<input type="file" name="file">
<input type="submit" value="upload"/>
</form>
</body>
</html>
三种方式代码块:
package com.alearner.CodeTools.controller;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.Date;
import java.util.Iterator;
/**
* Created by mzg on 2017/9/18.
*/
@RequestMapping(value = "/file")
@Controller
public class FileController {
public static Logger logger=Logger.getLogger(System.class);
/*
* 通过流的方式上传文件
* @RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
*/
@RequestMapping("fileUpload")
public String fileUpload(@RequestParam("file") CommonsMultipartFile file) throws IOException {
//用来检测程序运行时间
long startTime=System.currentTimeMillis();
System.out.println("fileName:"+file.getOriginalFilename());
try {
//获取输出流
OutputStream os=new FileOutputStream("E:/FileUpload/"+new Date().getTime()+file.getOriginalFilename());
//获取输入流 CommonsMultipartFile 中可以直接得到文件的流
InputStream in=file.getInputStream();
int temp;
//一个一个字节的读取并写入
while((temp=in.read())!=(-1))
{
os.write(temp);
}
// //这样处理会快很多,减少更多的io操作
// byte[] readBytes=new byte[128];
// while(in.read(readBytes) != -1){
// os.write(readBytes);
// }
// //网上另一段神奇的代码,暂未测试,理论上这个比较快
//
// byte [] b=new byte[1024];
// int len;
// while((len=in.read(b,0,b.length))!=-1){
// os.write(b, 0, len);
// }
os.flush();
os.close();
in.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long endTime=System.currentTimeMillis();
System.out.println("方法一的运行时间:"+String.valueOf(endTime-startTime)+"ms");
return "/success";
}
/*
* 采用file.Transto 来保存上传的文件
*/
@RequestMapping("fileUploadTransto")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file) throws IOException {
long startTime=System.currentTimeMillis();
System.out.println("fileName:"+file.getOriginalFilename());
String path="E:/FileUpload/"+new Date().getTime()+file.getOriginalFilename();
File newFile=new File(path);
//通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(newFile);
long endTime=System.currentTimeMillis();
System.out.println("方法二的运行时间:"+String.valueOf(endTime-startTime)+"ms");
return "/success";
}
/*
*采用spring提供的上传文件的方法
*/
@RequestMapping("springUpload")
public String springUpload(HttpServletRequest request) throws IllegalStateException, IOException
{
long startTime=System.currentTimeMillis();
//将当前上下文初始化给 CommonsMutipartResolver (多部分解析器)
CommonsMultipartResolver multipartResolver=new CommonsMultipartResolver(
request.getSession().getServletContext());
//检查form中是否有enctype="multipart/form-data"
if(multipartResolver.isMultipart(request))
{
//将request变成多部分request
MultipartHttpServletRequest multiRequest=(MultipartHttpServletRequest)request;
//获取multiRequest 中所有的文件名
Iterator iter=multiRequest.getFileNames();
while(iter.hasNext())
{
//一次遍历所有文件
MultipartFile file=multiRequest.getFile(iter.next().toString());
if(file!=null)
{
String path="E:/FileUpload/"+file.getOriginalFilename();
//上传
file.transferTo(new File(path));
}
}
}
long endTime=System.currentTimeMillis();
System.out.println("方法三的运行时间:"+String.valueOf(endTime-startTime)+"ms");
return "/success";
}
}
三种测试结果
花费的时间都在文件IO操作上了,不断减少文件的IO操作就会不断提升速度,内存允许可以多用缓冲
#CodeTools FileController 关于文件上传的三种测试结果: 测试一:文件echarts.min.js 大小:628 KB (643,788 字节) 方法一的运行时间:2550ms 方法二的运行时间:7ms 方法三的运行时间:5ms 测试二:文件:BaiduNetdisk_5.6.1.exe 大小:21.7 MB (22,804,880 字节) 方法一的运行时间:89498ms 方法二的运行时间:21ms 方法三的运行时间:113ms 测试三:文件:TIM1.2.0Trial.exe 大小:58.1 MB (60,938,656 字节) (文件较大没用方法一测试) 方法二的运行时间:64ms 方法三的运行时间:46ms[title]支持原理和解析(来源于网络)[/title]
Springmvc对文件上传的支持,springmvc内部已经实现了客户端上传文件到服务端我们只需要按照自己的需求去配置即可,
通过在springmvc的核心配置文件中配置支持文件上传的实体类即可
<!-- 文件上传配置 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 默认编码 -->
<property name="defaultEncoding" value="UTF-8"/>
<!-- 上传文件大小限制为100M,100*1024*1024 -->
<property name="maxUploadSize" value="104857600"/>
<!-- 内存中的最大值 -->
<property name="maxInMemorySize" value="4096"/>
</bean>
跟踪源码可以发现commonsMulitpartResolver 类继承了 CommonsFileUploadSupport 实现了 MultipartReolver和ServletContextAware等接口
CommonsFileUploadSupport 是一个抽象类,commonsMulitpartResolver继承了这个抽象类就拥有了CommonsFileUploadSupport 的所有方法,然后在根据自己的需求去扩展,这样用法的java编程种堪称为经典,屡试不爽,这是一种吧oop运用到了极致的做法,可以体现springmvc的思想之与java的高度一致。
众所周知,客户端不可能上传无限大的文件到服务端的(其实不仅仅只是文件),这就是为什么CommonsFileUploadSupport是抽象类的原因,但是不管是是上传文件还是上传别的东西,文件的大小是上传文件的必须要做一个限制的,所有该属性在抽象类中,二编码风格则不同,更具具体的需求可以设置不同的编码,如果在抽象类中定义死了不便于扩展,所有这个属性是可以让子类去重写的。
关于实现MultipartReolver和ServletContextAware接口,这才是能做到文件上传的关键回归最原始的上传文件,servlet上传文件,因为springmvc无论是入口还是对web的操作的原理都是基于servlet的,并在此基础上做扩展,所有文件上传也是如此,servlet上传文件要求表单的enctype的属性必须是 multipart/form-data,,所有实现MultipartReolver表示该请求支持表单的MIME编码,
至于ServletContextAware,那肯定就是是通过该容器去获取其他的8个对象,jsp中的9大对象中只需要获取它就能通过它去获取其他的8个对象了,再次回到servlet上传文件,要求对request对象进行加强和重写,而不能直接在request对象中获取文件,struts2中的某一个拦截器拦截器就对requers进行了加强和重写,这就是为什么struts2中上传文件只需限制文件大小(其实也有默认)而不用做过多的配置的原因,springmvc也是通过ServletContext容器去获取request对象然后进行重写和加强,以致于在控制器中直接通过request获取文件相关属性和把表单元素对象直接转化为文件对象。这也是springmvc的可扩展性的体现,不需要上传文件的时候不配置,而struts2即使不做文件上传struts框架仍然会对requers进行拦截和加强。
文章评论