准备工作
首先需要将上传文件过程中需要用到的两个 jar 包,复制粘贴到 WebContent → WEB-INF → lib 文件夹下。
没有这两个文件的点击下面下载
百度网盘链接:https://pan.baidu.com/s/1ISCbG63mBthJtNtYwdsedw
提取码:4e55
网页代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/Upload/UploadServlet" method="post" enctype="multipart/form-data">
<!-- enctype属性用于文件域上传文件,表示以多媒体方式传输文件 -->
<input type="file" name="uploadFile1" /><br><br>
<input type="file" name="uploadFile2" /><br><br>
<input type="text" name="name" value="柚咖"/><br><br>
<input type="submit" value="上传文件"/>
</form>
</body>
</html>
注意:在上传的时候,表单的提交方法一定是 post。
网页效果:
文件上传需要了解的知识
在 Servlet 类文件中需要使用到导入的 jar 包提供的API
1. 核心API —— DiskFileItemFactory类
DiskFileItemFactory类作用:DiskFileItemFactory类是创建 FileItem 对象的工厂类,可以设置缓存大小以及临时文件保存位置。
disk——硬盘,file——文件,item——项,factory——工厂
常用的方法:
- 默认的构造方法:
public DiskFileItemFactory();
- 带参数构造方法:
public DiskFileItemFactory(int sizeThreshold,java.io.File repository);
,sizeThreshold 是内存缓冲区大小,repository 是临时文件的位置 - 设置内存缓冲区大小:
public void setSizeThreshold(int sizeThreshold);
- 设置临时文件位置:
public void setRepository(Java.io.File repository);
2. 核心API —— ServletFileUpload类
ServletFileUpload类作用:ServletFileUpload类可以理解为是一个上传工具,负责处理上传的文件数据,并将表单中每个输入项封装成一个 FileItem 对象。
1.List<FileItem> parseRequest(HttpServletRequest request)
, 解析请求,(即 request 对象),并把表单中的每一个输入项包装成一个 fileItem 对象,并返回一个保存了所有 FileItem 的 List 集合
2. boolean isMultipartContent(HttpServletRequest request)
,判断上传表单是否为multipart/form-data类型
3. setFileSizeMax(long fileSizeMax)
,设置单个文件上传的大小的限制
4. setSizeMax(long sizeMax)
,设置所有文件上传的总大小的限制
5. setHeaderEncoding("utf-8");
,解决中文乱码问题
3. 核心API —— FileItem类
boolean isFormField()
,判断 FileItem 对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,返回 true —— 普通文本表单字段,返回 false —— 文件表单字段
上传功能实现
上传功能实现的步骤:
- 导入 commons-io 和 commons-fileupload 的 jar 包;
- 创建一个 DiskFileItemFactory 类的对象 factory,可以设置缓存大小、临时文件位置;
- 通过 DiskFileItemFactory 对象 factory 创建一个 ServletFileUpload 类的对象 upload,可以设置上传文件的大小限制;
- 通过 ServletFileUpload 对象 upload 的
parseRequest(HttpServletRequest request)
方法解析请求,并返回一个保存了所有上传内容的 List 对象; - 遍历获得的 List 对象,调用 FileItem 的 isFormField() 方法判断是普通文本表单字段还是上传文件的表单字段;
5.1 如果是上传文件表单字段,调用getName()获取文件名,调用getInputStream方法得到数据输入流,从而读取上传数据
5.2 如果是普通表单字段,则调用getFieldName、getString方法得到字段名和字段值。
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
upload(request);
} catch (FileUploadException e) {
e.printStackTrace();
}
}
private void upload(HttpServletRequest request) throws FileUploadException, IOException {
// 创建 DiskFileItemFactory对象
DiskFileItemFactory factory = new DiskFileItemFactory();
// 创建ServletFileUpload对象
ServletFileUpload upload = new ServletFileUpload(factory);
// 所有获取 FileItem的List数组
List<FileItem> items = upload.parseRequest(request);
for (FileItem fi : items) {
if (fi.isFormField()) {
// 普通文本表单元素
String name = fi.getFieldName();
// ①
String value = fi.getString();
System.out.println("name=" + name + " value=" + value);
} else {
// 文件上传表单元素
// 获取上传的文件名
String fileName = fi.getName();
System.out.println("fileName=" + fileName );
// 获取数据输入流,用于获取从web端传输的数据
InputStream is = fi.getInputStream();
// 创建文件输出流
FileOutputStream fos = new FileOutputStream("F:\\Process\\UploadFile\\" + fileName);
int len = -1;
byte[] b = new byte[1024 * 1024];
while (-1 != (len = is.read(b))) {
fos.write(b, 0, len);
}
fos.flush();
fos.close();
is.close();
fos = null;
is = null;
}
}
}
完成上面的代码之后,我们的文件上传功能的就实现了,现在对程序进行一下小的调试。
①. 获取普通文本表单字段的值时,如果该字段是中文,会显示乱码的原因是网页端传过来的这个字符串的编码格式是 ISO-8859-1,所以需要将其转化成 UTF-8。代码:value = new String(value.getBytes("ISO-8859-1"),"UTF-8");
②. 如果上传的文件是重名的,那么会进行覆盖,这个是不可以的,否则的话,用户上传两个同名的文件,后一个文件就会覆盖前一个文件,这是不可以被接受的!
首先要明白,服务器端存储文件的文件名和用户上传的文件名不是必须一致的,而服务器端存储的文件的文件名要保证必须是独一无二的。
/**
* 生成唯一的文件名
* @param fi
* @return 文件名
*/
private String getFileName(FileItem fi) {
String fileName = fi.getName();
// 获取文件后缀名
String temp = fileName.substring(fileName.lastIndexOf("."));
// 获取唯一的标识
String uuid = UUID.randomUUID().toString();
return uuid + temp;
}
使用我们自己写的 getFileName() 方法就可以为每个用户上传的文件获取一个唯一的标识了,这样就不会出现同名文件的覆盖了。
小结
文件上传功能的难点在于:文件传输的过程的理解。
正确的理解: 访问网页这个过程是在客户端发生的,客户端是一台电脑 A;而我们点击上传,是将文件传输到服务端,这个服务端又是另一台电脑 B,这个过程是:从电脑A中上传文件到 Servlet,Servlet 将这个文件输出到服务器中保存。也就是说,我们在使用文件输出流的时候的路径是服务器端的路径。
错误的理解: 因为我们调试程序的时候是用一台电脑既当客户端,又当服务端来用的,会很容易误解为,从本地电脑中传输文件数据到 Java 端,然后再从 Java 端传输到本地电脑的另一个路径中存储文件。这个理解是 JavaSE 的 I/O 操作的理解,要明白,文件的来源和目的地是不同的电脑。
将上传的文件回显
仅仅将文件从客户端上传到服务端,并不是一个完整的文件上传过程,还需要将上传到服务器端的文件在 web 端进行回显。
例如,我们登录 csdn,想要修改 csdn 的头像,这个过程是:从我们的电脑中选择图像,点击上传按钮,然后可以在 csdn 中看到我们新上传的头像,这是完整的上传过程。
所以接下来我们需要将上传的文件在 Web 端显示出来。
现在需要考虑的是这个回显数据的页面用请求转发还是请求重定向?
选择请求转发还是请求重定向,看是否需要本次请求的数据。因为需要在新页面中显示表单提交的普通文本表单字段数据和上传之后的文件,所以是需要用到本次请求数据的,选择请求转发。
文件回显的步骤
将上传文件进行回显的步骤:
- 将需要回显的数据存储到 request 域中;
- 进行请求转发,在新的页面中获取并展示需要回显的数据
思考,为什么要将数据存储在 request 域中,而不是其他的域?
为什么可以用 request 域?
因为这里使用的是请求转发,而请求转发使用的是同一个请求,而 request 域的范围就是一次请求,所以是可以使用的。
为什么不用其他的域?
- pageContext 域:范围太小,范围是一个页面,这里需要页面跳转,是两个页面,在 pageContext 域中存储的数据无法在新的页面中获取;
- session 域:可以通过 session 域来传递这些数据,但是!session 中存放的数据要尽可能的少,如果 session 存放的数据很多,每次请求的报文都会很大,网络会非常慢,所以一般来讲,session 中存储的就是用户信息和验证码。
- application:用 application 也可以获取数据,但是 application 是所有用户共用的,不能说用户 A 提交的数据,所有用户都共用,这是不合理的。
代码实现:
网页代码不变
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/Upload/UploadServlet" method="post" enctype="multipart/form-data">
<!-- enctype属性用于文件域上传文件,表示以多媒体方式传输文件 -->
<input type="file" name="uploadFile1" /><br><br>
<input type="file" name="uploadFile2" /><br><br>
<input type="text" name="name" value="柚咖"/><br><br>
<input type="submit" value="上传文件"/>
</form>
</body>
</html>
Servlet 代码,新增向 request 域中存数据的代码,以及请求转发代码
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
upload(request);
} catch (FileUploadException e) {
e.printStackTrace();
}
// 回显文件 —— 请求转发
request.getRequestDispatcher("/web/index2.jsp").forward(request, response);
}
private void upload(HttpServletRequest request) throws FileUploadException, IOException {
// disk:硬盘,file:文件,item:项,factory:工厂
// 创建 DiskFileItemFactory对象
DiskFileItemFactory factory = new DiskFileItemFactory();
// 创建ServletFileUpload对象
ServletFileUpload upload = new ServletFileUpload(factory);
// 所有获取 FileItem的List数组
List<FileItem> items = upload.parseRequest(request);
int i = 0;
for (FileItem fi : items) {
if (fi.isFormField()) {
// 普通文本表单元素
String name = fi.getFieldName();
// 可以识别中文的值
String value = fi.getString();
// 获取中文
value = new String(value.getBytes("ISO-8859-1"),"UTF-8");
System.out.println("name=" + name + " value=" + value);
//将数据存入 request 域
request.setAttribute(name, value);
} else {
// 文件上传表单元素
// 获取上传的文件名
String fileName = getFileName(fi);
System.out.println("fileName=" + fileName );
// 获取数据输入流,用于获取从web端传输的数据
InputStream is = fi.getInputStream();
// 创建文件输出流
FileOutputStream fos = new FileOutputStream("F:\\Process\\UploadFile\\" + fileName);
int len = -1;
byte[] b = new byte[1024 * 1024];
while (-1 != (len = is.read(b))) {
fos.write(b, 0, len);
}
fos.flush();
fos.close();
is.close();
fos = null;
is = null;
String filePath = "F:\\Process\\UploadFile\\" + fileName;
// 将数据存入 request 域
request.setAttribute("filePath"+i++, filePath);
}
}
}
/**
* 生成唯一的文件名
* @param fi
* @return 文件名
*/
private String getFileName(FileItem fi) {
String fileName = fi.getName();
// 获取文件后缀名
String temp = fileName.substring(fileName.lastIndexOf("."));
// 获取唯一的标识
String uuid = UUID.randomUUID().toString();
return uuid + temp;
}
}
请求转发的新的 jsp 文件代码
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
第一个文件名:
<% out.println(request.getAttribute("fileName0").toString()); %>
<br>
第一个图片:
<!-- 虚拟路径-->
<img alt="GG" src="/fileUpload/<%=request.getAttribute("fileName0").toString() %>" width="555px">
<br>
第二个文件名:
<% out.println(request.getAttribute("fileName1").toString()); %>
<br>
第二个图片:
<!-- 虚拟路径 -->
<img alt="GG" src="/fileUpload/<%=request.getAttribute("fileName1").toString() %>" width="555px">
<br>
文本输入框输入的数据:
<%=request.getAttribute("name").toString() %>
</body>
</html>
在 jsp 文件中的 img 标签的 src 属性填写的是虚拟路径,虚拟路径的配置可以看这篇博客。
总结:
在文件上传过程中用到了
- 两个 commons jar 包提供的 DiskFileItemFactory 类、ServletFileUpload 类、FileItem 类
- 字节流 I/O 操作
在文件回显过程中用到了
- 请求转发
- 使用 request 域保存数据
- 虚拟路径