1. 准备工作
文件上传下载是项目里面最常见的功能之一,一个优秀的项目不可能没有文件上传下载操作。所以今天就来搞一搞文件上传和下载。
前端页面准备
- 重要:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器;
- 对表单中的 enctype 属性做个详细的说明:
- application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
- multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
- text/plain:除了把空格转换为 “+” 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
<form action="${pageContext.request.contextPath}/upload2" method="post" enctype="multipart/form-data" >
上传用户:<input type="text" name="username">
<p><input type="file" name="uploadFile"></p>
<p><input type="submit" value="上传">|<input type="reset" value="重置"></p>
</form>
就像上面一样,当然这只是最基本的样式,如果你有能力换样式也可以还样式。
推荐几款不错的前端框架
LayUI 一个非常不错的前端框架。
ElementUI 饿了么开发的前端框架,主要基于Vue实现。
这两个前端框架非常美观大方。
好的,言归正传。
一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。
文件上传
- 工具:IDEA,Tomcat8.5
使用IDEA创建Maven项目。
导入依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.2</version>
</dependency>
- mmons-fileupload是我们文件上传使用的,而commons-fileupload要依赖于commons-io进行开发。
前端页面代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%----%>
<form action="${pageContext.request.contextPath}/upload2" method="post" enctype="multipart/form-data" >
上传用户:<input type="text" name="username">
<p><input type="file" name="uploadFile"></p>
<p><input type="submit" value="上传">|<input type="reset" value="重置"></p>
</form>
</body>
</html>
创建Servlet并继承HttpServlet ,重写doPost方法
public class FileServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
}
}
配置Servlet
在web.xml文件中添加映射
<servlet>
<servlet-name>upload</servlet-name>
<servlet-class>com.file.servlet.UpFileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>upload</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>
到这里准备工作就做好了
2. 编写代码
- 判断上传文件是否是普通文件。
- 创建上传文件的报存路径,建议在WEB-INF路径下。 安全,用户我无法直接访问上传的文件。
- 临时文件路径,假如文件超出预期大小,我们把他放到一个临时文件中,过几天就自动删除,或者提示用户转存为永久文件。
- 判断上传文件一般都需要通过流来获取,我们可以使用request.getinputstream(),原生态的文件上传流获取,十分麻烦,但是我们都建议使用Apache的文件上传组件来进行上传,commons-fileupload,他需要依赖于commons-io组件。
文件前必须先了解几个类
核心API—DiskFileItemFactory
- DiskFileItemFactory 是创建FileItem 对象的工厂,这个工厂类常用方法:
- public void setSizeThreshold(int sizeThreshold) :设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时,fileupload组件将使用临时文件缓存上传文件。
- public void setRepository(Java.io.File repository) :指定临时文件目录,默认值为System.getProperty(“java.io.tmpdir”).
- public DiskFileItemFactory(int sizeThreshold,java.io.File repository) :构造函数
核心API—-ServletFileUpload
ServletFileUpload 负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem 对象中。常用方法有:
-
boolean isMultipartContent(HttpServletRequest request) :判断上传表单是否为multipart/form-data类型
-
List parseRequest(HttpServletRequest request):解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合。
-
setFileSizeMax(long fileSizeMax) :设置上传文件的最大值(单个文件),用于设置单个上传文件的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。
-
setSizeMax(long sizeMax) :设置上传文件总量的最大值(所有上传文件),用于设置请求消息实体内容(即所有上传数据)的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。
-
setHeaderEncoding(java.lang.String encoding) :设置编码格式。在文件上传请求的消息体中,除了普通表单域的值是文本内容以外,文件上传字段中的文件路径名也是文本,在内存中保存的是它们的某种字符集编码的字节数组,Apache文件上传组件在读取这些内容时,必须知道它们所采用的字符集编码,才能将它们转换成正确的字符文本返回。
核心API—FileItem
FileItem类的常用方法:
-
boolean isFormField(): isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true,否则返回false。
-
String getName():用于获得文件上传字段中的文件名。注意IE或FireFox中获取的文件名是不一样的,IE中是绝对路径,FireFox中只是文件名。
-
**String getFieldName()**用于返回表单标签name属性的值。
-
void write(File file):用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。
-
**String getString():**用于将FileItem对象中保存的数据流内容以一个字符串返回,它有两个重载的定义形式:
public Java.lang.String getString();
public java.lang.String getString(java.lang.String encoding) throws java.io.UnsupportedEncodingException
前者使用缺省的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。如果在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个getString方法,并为之传递正确的字符集编码名称。
- void delete():delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。尽管当FileItem对象被垃圾收集器收集时会自动清除临时文件,但及时调用delete方法可以更早的清除临时文件,释放系统存储资源。
代码
实现步骤:
1、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录。
2、使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
3、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。
4、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件:
4.1、 为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值。
4.2、为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。
doPost方法
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//判断上传文件是否是普通文件。
if (!ServletFileUpload.isMultipartContent(req)) {
return; //终止方法执行,说明这是
}
//创建上传文件的报存路径,建议在WEB-INF路径下。
// 安全,用户我无法直接访问上传的文件;
String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
File file = new File(uploadPath);
//判断文件是否存在
if (!file.exists()) {
file.mkdirs();//创建这个目录
}
try {
//缓存,临时文件
//临时文件路径,假如文件超出预期大小,我们把他放到一个临时文件中,过几天就自动删除,或者提示用户转存为永久文件。
String temPath = this.getServletContext().getRealPath("/WEB-INF/tem");
File temFile = new File(temPath);
//判断文件是否存在
if (!temFile.exists()) {
temFile.mkdirs();//创建这个 目录
}
//1.创建DiskFileItemFactory对象,处理文件上传路径或者大小限制;
DiskFileItemFactory factory = getDiskFileItemFactory(temFile);
//2.获取ServletFileUpload
ServletFileUpload upload = getServletFileUpload(factory);
//3.处理上传文件
String message = null;
message = uploadParseRequest(req, uploadPath, upload);
req.setAttribute("message", message);
req.getRequestDispatcher("message.jsp").forward(req, resp);
} catch (FileUploadException e) {
e.printStackTrace();
}
}
getDiskFileItemFactory方法
public static DiskFileItemFactory getDiskFileItemFactory(File temFile) {
DiskFileItemFactory factory = new DiskFileItemFactory();
//通过这个工厂设置一个缓冲区,当上传文件大于这个缓冲区的时候,将他放入临时文件中。
factory.setSizeThreshold(1024 * 1024);//缓冲区大小为1M
factory.setRepository(temFile);//临时文件目录,需要一个File
return factory;
}
getServletFileUpload方法
public static ServletFileUpload getServletFileUpload(DiskFileItemFactory factory) {
ServletFileUpload upload = new ServletFileUpload(factory);
//监听文件上传进度
upload.setProgressListener(new ProgressListener() {
@Override
// l:已读取到文件大小
// l1:文件大小
public void update(long l, long l1, int i) {
System.out.println("总大小:" + l1 + "已上传:" + l);
}
});
//处理乱码问题
upload.setHeaderEncoding("UTF-8");
//设置单个文件最大值
upload.setFileSizeMax(1024 * 1024 * 10);
//设置总共能够上传文件的大小
upload.setSizeMax(1024 * 1024 * 10);
return upload;
}
uploadParseRequest方法
public static String uploadParseRequest(HttpServletRequest req, String uploadPath, ServletFileUpload upload) throws IOException, FileUploadException {
//把前端请求解析,封装成一个FileItem对象,需要从ServletFileUpload对象中获取
List<FileItem> fileItems = upload.parseRequest(req);
//fileItem ==>每个表单对象
for (FileItem fileItem : fileItems) {
//判断上传的文件是普通的表单还是带文件的表单
if (fileItem.isFormField()) {
String name = fileItem.getFieldName();
String value = fileItem.getString("UTF-8");
System.out.println(name + ":" + value);
} else {//文件
//*******************处理文件*******************//
// 获取文件名
String uploadFileName = fileItem.getName();
//可能存在文件不合法的
if (uploadFileName.trim().equals("") || uploadFileName == null) {
continue; //返回
}
//获取上传文件名,
String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
//获取文件的后缀名
String fileExtName = uploadFileName.substring(uploadFileName.indexOf(".") + 1);
//可以使用UUID(唯一识别的通用码),保证文件名唯一。
//UUID.randomUUID(),随机生成一个唯一识别的通用码。
//private static final long serialVersionUID = -4856846361193249489L;
//
// implements Serializable :标记接口,JVM--->java栈 本地方法栈, native-->(调用)C++
String uuidPath = UUID.randomUUID().toString();
//*******************存放地址*******************//
//存到哪?uploadPath
//文件真是存在的路径 realpath
String realPath = uploadPath + "/" + uuidPath;
//给每个文件创建一个对应的文件夹。
File realPathFile = new File(realPath);
if (!realPathFile.exists()) {
realPathFile.mkdirs();
}
//*******************文件传输*******************//
//获得文件上传的流
InputStream inputStream = fileItem.getInputStream();
FileOutputStream fos = new FileOutputStream(realPath + "/" + fileName);
byte[] buffer = new byte[1024 * 1024];
int len = 0;
while ((len = inputStream.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
inputStream.close();
}
}
return "上传成功!";
}
思路:上面代码把各个部分封装成一个可供使用的方法。
当然也可以写在一起。我认为这种不易理解。看着也费劲。哈哈,这只是我个人的观点。
package com.file.servlet;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.UUID;
public class FileServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//判断上传文件是否是普通文件。
if (!ServletFileUpload.isMultipartContent(req)) {
return; //终止方法执行,说明这是
}
try {
//创建上传文件的报存路径,建议在WEB-INF路径下。
// 安全,用户我无法直接访问上传的文件;
String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
File file = new File(uploadPath);
//判断文件是否存在
if (!file.exists()) {
file.mkdirs();//创建这个目录
}
//缓存,临时文件
//临时文件路径,假如文件超出预期大小,我们把他放到一个临时文件中,过几天就自动删除,或者提示用户转存为永久文件。
String temPath = this.getServletContext().getRealPath("/WEB-INF/tem");
File temFile = new File(temPath);
//判断文件是否存在
if (!temFile.exists()) {
temFile.mkdirs();//创建这个 目录
}
//判断上传文件一般都需要通过流来获取,我们可以使用request.getinputstream(),原生态的文件上传流获取,
//十分麻烦,但是我们都建议使用Apache的文件上传组件来进行上传,commons-fileupload,他需要依赖于commons-io组件。
//1.创建DiskFileItemFactory对象,处理文件上传路径或者大小限制;
DiskFileItemFactory factory = new DiskFileItemFactory();
//通过这个工厂设置一个缓冲区,当上传文件大于这个缓冲区的时候,将他放入临时文件中。
factory.setSizeThreshold(1024 * 1024);//缓冲区大小为1M
factory.setRepository(temFile);//临时文件目录,需要一个File
//2.获取ServletFileUpload
ServletFileUpload upload = new ServletFileUpload(factory);
//监听文件上传进度
upload.setProgressListener(new ProgressListener() {
@Override
// l:已读取到文件大小
// l1:文件大小
public void update(long l, long l1, int i) {
System.out.println("总大小:" + l1 + "已上传:" + l);
}
});
//处理乱码问题
upload.setHeaderEncoding("UTF-8");
//设置单个文件最大值
upload.setFileSizeMax(1024 * 1024 * 10);
//设置总共能够上传文件的大小
upload.setSizeMax(1024 * 1024 * 10);
//3.处理上传文件
//把前端请求解析,封装成一个FileItem对象,需要从ServletFileUpload对象中获取
List<FileItem> fileItems = upload.parseRequest(req);
//fileItem ==>每个表单对象
for (FileItem fileItem : fileItems) {
//判断上传的文件是普通的表单还是带文件的表单
if (fileItem.isFormField()) {
String name = fileItem.getFieldName();
String value = fileItem.getString("UTF-8");
System.out.println(name + ":" + value);
} else {//文件
//*******************处理文件*******************//
// 获取文件名
String uploadFileName = fileItem.getName();
//可能存在文件不合法的
if (uploadFileName.trim().equals("") || uploadFileName == null) {
continue; //返回
}
//获取上传文件名,
String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
//获取文件的后缀名
String fileExtName = uploadFileName.substring(uploadFileName.indexOf(".") + 1);
//可以使用UUID(唯一识别的通用码),保证文件名唯一。
//UUID.randomUUID(),随机生成一个唯一识别的通用码。
//private static final long serialVersionUID = -4856846361193249489L;
//
// implements Serializable :标记接口,JVM--->java栈 本地方法栈, native-->(调用)C++
String uuidPath = UUID.randomUUID().toString();
//*******************存放地址*******************//
//存到哪?uploadPath
//文件真是存在的路径 realpath
String realPath = uploadPath + "/" + uuidPath;
//给每个文件创建一个对应的文件夹。
File realPathFile = new File(realPath);
if (!realPathFile.exists()) {
realPathFile.mkdirs();
}
//*******************文件传输*******************//
//获得文件上传的流
InputStream inputStream = fileItem.getInputStream();
FileOutputStream fos = new FileOutputStream(realPath + "/" + fileName);
byte[] buffer = new byte[1024 * 1024];
int len = 0;
while ((len = inputStream.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
inputStream.close();
}
}
String message = "成功";
req.setAttribute("message", message);
req.getRequestDispatcher("message.jsp").forward(req, resp);
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}
总结: 了解了commons-fileupload中的几个类,当然项目中不是完全这样写,还有很多改进的地方,主要是理解这种原理。
结束:欢迎各位留言交流,有帮助请点个赞👍再走吧