1.如何实现文件上传
文件上传:将浏览器端用户选择文件,通过网络,保存到服务器端硬盘上。
开发步骤
1.浏览器端:(1)选择文件 (2)发送文件
<form action="...servlet" method="post" enctype="multipart/form-data">
<input type="file" name="image">
<input type="submit">
</form>
(1) <input type="file">
表示浏览器将提供一个选择按钮,作为表单元素,必须提供name属性,及name=“image”
名称自定义
(2) <form action>
设置请求servlet路径
(3) <form method="post">
文件上传必须设置成post请求(post请求的内容没有限制)
(4) <form enctype="">
enctype 表示表单提交编码方式(encoding type),如果文件上传固定值:multipart/form-data
(5) <input type="submit">
表单如果需要提交,必须提供提交按钮
2.服务器端
服务器端通过开源组件Commons-FileUpload将请求中的各种表单域解析出来,并实现一个或者多个的文件上传,同时也可以限制上传文件大小等内容。
2.案例展示
2.1.创建文件上传表单Form.html
<form action="UploadServlet" method="post"
enctype="multipart/form-data">
<table width="600px">
<tr>
<td>上传者</td>
<td><input type="text" name="name" /></td>
</tr>
<tr>
<td>上传文件</td>
<td><input type="file" name="myfile"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="上传" /></td>
</tr>
</table>
</form>
2.2 添加jar包
commons-fileupload-1.2.1
commons-io-1.4
2.3 创建UploadServlet
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
try {
// 创建工厂
//在使用ServletFileUpload对象解析请求时需要根据DiskFileItemFactory对象的属性sizeThreshold(临界值)
//和repository(临时目录)来决定将解析得到的数据保存在内存还是临时文件中,如果是临时文件,保存在哪个临时目录中?。
//所以,我们需要在进行解析工作前构造好DiskFileItemFactory对象,
//通过ServletFileUpload对象的构造方法或setFileItemFactory()方法设置ServletFileUpload对象的fileItemFactory属性
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置上传文件以临时文件的形式保存在磁盘的临界值,默认是10KB.
factory.setSizeThreshold(10);
File f=new File("e:\\Target");
if(!f.exists()){
f.mkdirs();
}
//设置文件的缓存路径
factory.setRepository(f);
// 创建 fileupload 组件
ServletFileUpload fileupload = new ServletFileUpload(factory);
fileupload.setHeaderEncoding("utf-8");
//设置单个 文件上传的最大尺寸
//fileupload.setFileSizeMax(10000);
// 解析 request
List<FileItem> fileitems = fileupload.parseRequest(request);
PrintWriter writer=response.getWriter();
// 遍历集合
for (FileItem fileitem : fileitems) {
// 判断是否为普通字段
if (fileitem.isFormField()) {
// 获得字段名和字段值
String name = fileitem.getFieldName();
//如果表单项是name,也就是上传者
if(name.equals("name")){
//如果上传者表单项不为空,得到上传者信息并显示
if(!fileitem.getString().equals("")){
String value=fileitem.getString("utf-8");
writer.print("上传者:"+value+"<br/>");
}
}
} else {
// 上传的文件路径。浏览器不同获取到的完整路径与名称是不一样的。
// 例如ie浏览器可能是”c:\Sunset.jpg”,而其他的可能是Sunset.jsp
//获取上传的文件名
String filename = fileitem.getName();
writer.print("文件来源:"+filename+"<br>");
// 截取出文件名
filename = filename
.substring(filename.lastIndexOf("\\") + 1);
// 文件名需要唯一
filename = UUID.randomUUID().toString() + "_" +
filename;
// 在服务器创建同名文件
String webPath = "/upload/";
//将服务器中文件夹路径和文件名路径组合成完整的服务器路径
// 这个请求路径不是你的源码路径,而是服务器加载后生成的
String filepath =getServletContext().getRealPath(webPath+filename);
// 创建文件
File file = new File(filepath);
//得到文件的父目录(upload),然后创建父目录
file.getParentFile().mkdirs();
//创建文件
file.createNewFile();
// 获得上传文件流
InputStream in = fileitem.getInputStream();
// 获得写入文件流
OutputStream out = new FileOutputStream(file);
// 流的对拷
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0)
out.write(buffer, 0, len);
// 关流
in.close();
out.close();
// 删除临时文件
//fileitem.delete();
writer.print("上传成功<br>");
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
3.相关知识
3.1 FileItem接口
文件上传时需要用到FileItem类,FileItem是一个接口,它的实现类是DiskFileItem。
1)getInputStream():
以流的形式返回上传文件的数据内容。
2)getContentType():
用于获得上传文件的类型,即表单字段元素描述头属性“Content-Type”的值。
如果是文件域,例如上传“LZG.zip”压缩包,返回application/x-zip-compressed类型;如果FileItem类对象对应的是普通表单字段,该方法将返回null。
3)getName():
用于获得文件上传字段中的文件名。
4)isInMemory():
用来判断FileItem对象封装的数据内容是存储在内存中,还是存储在临时文件中,如果存储在内存中则返回true,否则返回false。
5)getSize():
返回该上传文件的大小(以字节为单位)。Long型。
6)getString():
getString方法用于将FileItem对象中保存的数据流内容以一个字符串返回,它有两个重载的定义形式:
public java.lang.String getString()
public java.lang.String getString(java.lang.String encoding)
前者使用缺省的字符集编码将主体内容转换成字符串,
后者使用参数指定的字符集编码将主体内容转换成字符串。如果在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个getString方法,并为之传递正确的字符集编码名称。
7)write(File):
write方法用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。
8)delete():
delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。
9)getFieldName():
用于返回表单标签name属性的值。如上例中<input type="text" name="name" />
的value。
10)isFormField():
用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true,否则返回false。因此,可以使用该方法判断是否为普通表单域,还是文件上传表单域。
11)getOutputStream():
以流的形式存储上传文件的内容
3.2 DiskFileItemFactory类
将请求消息实体中的每一个项目封装成单独的DiskFileItem (FileItem接口的实现) 对象的任务
由 org.apache.commons.fileupload.FileItemFactory
接口的默认实现
org.apache.commons.fileupload.disk.DiskFileItemFactory
来完成。当上传的文件项目比较小时,直接保存在内存中(速度比较快),比较大时,以临时文件的形式,保存在磁盘临时文件夹(虽然速度慢些,但是内存资源是有限的)。
属性
-
public static final int DEFAULT_SIZE_THRESHOLD
:将文件保存在内存还是磁盘临时文件夹的默认临界值,值为10240,即10kb。 -
private File repository
:用于配置在创建文件项目时,当文件项目大于临界值时使用的临时文件夹,默认采用系统默认的临时文件路径,可以通过系统属性 java.io.tmpdir获取。如下代码:System.getProperty(“java.io.tmpdir”);
private int sizeThreshold
:用于保存将文件保存在内存还是磁盘临时文件夹的临界值
构造方法
-
public DiskFileItemFactory()
采用默认临界值和系统临时文件夹构造文件项工厂对象。 -
public DiskFileItemFactory(int sizeThreshold,File repository)
采用参数指定临界值和系统临时文件夹构造文件项工厂对象。 -
FileItem createItem()
根据DiskFileItemFactory相关配置将每一个请求消息实体项目创建成DiskFileItem 实例,并返回。该方法从来不需要我们亲自调用,FileUpload组件在解析请求时内部使用。 -
void setSizeThreshold(int sizeThreshold)
Apache文件上传组件在解析上传数据中的每个字段内容时,需要临时保存解析出的数据,以便在后面进行数据的进一步处理(保存在磁盘特定位置或插入数据库)。因为Java虚拟机默认可以使用的内存空间是有限的,超出限制时将会抛出“java.lang.OutOfMemoryError”错误。如果上传的文件很大,例如800M的文件,在内存中将无法临时保存该文件内容,Apache文件上传组件转而采用临时文件来保存这些数据;但如果上传的文件很小,例如600个字节的文件,显然将其直接保存在内存中性能会更加好些。
setSizeThreshold方法用于设置是否将上传文件已临时文件的形式保存在磁盘的临界值(以字节为单位的int值),如果从没有调用该方法设置此临界值,将会采用系统默认值10KB。对应的getSizeThreshold() 方法用来获取此临界值。 -
void setRepository(File repository)
setRepositoryPath方法用于设置当上传文件尺寸大于setSizeThreshold方法设置的临界值时,将文件以临时文件形式保存在磁盘上的存放目录。有一个对应的获得临时文件夹的File getRespository()
方法。
注意:当从没有调用此方法设置临时文件存储目录时,默认采用系统默认的临时文件路径,可以通过系统属性 java.io.tmpdir 获取。如下代码:
System.getProperty("java.io.tmpdir")
;
Tomcat系统默认临时目录为“<tomcat安装目录>/temp/”。
3.3 ServletFileUpload 类
org.apache.commons.fileupload.servlet.ServletFileUpload
类是Apache文件上传组件处理文件上传的核心高级类(所谓高级就是不需要管底层实现,暴露给用户的简单易用的接口)。
使用其parseRequest(HttpServletRequest)
方法可以将通过表单中每一个HTML标签提交的数据封装成一个FileItem对象,然后以List列表的形式返回。使用该方法处理上传文件简单易用。
构造方法:
-
public ServletFileUpload()
构造一个未初始化的实例,需要在解析请求之前先调用setFileItemFactory()方法设置 fileItemFactory属性。 -
public ServletFileUpload(FileItemFactory fileItemFactory)
构造一个实例,并根据参数指定的FileItemFactory 对象,设置 fileItemFactory属性。
ServletFileUpload类常用方法: -
public void setSizeMax(long sizeMax)
setSizeMax方法继承自FileUploadBase类,用于设置请求消息实体内容(即所有上传数据)的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。
在请求解析的过程中,如果请求消息体内容的大小超过了setSizeMax方法的设置值,将会抛出FileUploadBase内部定义的SizeLimitExceededException
异常(FileUploadException的子类)。该方法有一个对应的读方法:public long getSizeMax()方法。 -
public void setFileSizeMax(long fileSizeMax)
setFileSizeMax方法继承自FileUploadBase类
,用于设置单个上传文件的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。该方法有一个对应的读方法:public long geFileSizeMax()方法。
在请求解析的过程中,如果单个上传文件的大小超过了setFileSizeMax方法的设置值,将会抛出FileUploadBase内部定义的FileSizeLimitExceededException异常(FileUploadException的子类)。 -
public List parseRequest(javax.servlet.http.HttpServletRequest req)
parseRequest 方法是ServletFileUpload类的重要方法,它是对HTTP请求消息体内容进行解析的入口方法。它解析出FORM表单中的每个字段的数据,并将它们分别包装成独立的FileItem对象,然后将这些FileItem对象加入进一个List类型的集合对象中返回。 -
public FileItemIterator getItemIterator(HttpServletRequest request)
getItemIterator方法和parseRequest 方法基本相同。但是getItemIterator方法返回的是一个迭代器,该迭代器中保存的不是FileItem对象,而是FileItemStream 对象,如果你希望进一步提高新能,你可以采用getItemIterator方法,直接获得每一个文件项的数据输入流,做底层处理;如果性能不是问题,你希望代码简单,则采用parseRequest方法即可。 -
public stiatc boolean isMultipartContent(HttpServletRequest req)
isMultipartContent方法方法用于判断请求消息中的内容是否是“multipart/form-data”类型,是则返回true,否则返回false。isMultipartContent方法是一个静态方法,不用创建ServletFileUpload类的实例对象即可被调用。 -
getFileItemFactory()和setFileItemFactory(FileItemFactory)
方法继承自FileUpload类,用于设置和读取fileItemFactory属性。 -
public void setProgressListener(ProgressListener pListener)
设置文件上传进度监听器。该方法有一个对应的读取方法:ProgressListener getProgressListener()。 -
public void setHeaderEncoding()
在文件上传请求的消息体中,除了普通表单域的值是文本内容以外,文件上传字段中的文件路径名也是文本,在内存中保存的是它们的某种字符集编码的字节数组,Apache文件上传组件在读取这些内容时,必须知道它们所采用的字符集编码,才能将它们转换成正确的字符文本返回。
setHeaderEncoding方法继承自FileUploadBase类,用于设置上面提到的字符编码。如果没有设置,则对应的读方法getHeaderEncoding()方法返回null,将采用HttpServletRequest设置的字符编码,如果HttpServletRequest的字符编码也为null,则采用系统默认字符编码。可以通过一下语句获得系统默认字符编码:System.getProperty("file.encoding"))
;