一、文件上传的基础
1.1 为何需要文件上传
案例:
注册表单/保存商品等相关模块!
-- 注册选择头像 / 商品图片
(数据库:存储图片路径 / 图片保存到服务器中指定的目录)
1.2 文件上传的要点
文件上传,要点:
前台:
1. 提交方式:post
2. 表单中有文件上传的表单项: <input type=”file” />
3. 指定表单类型:
默认类型:enctype="application/x-www-form-urlencoded"
文件上传类型:enctype="multipart/form-data"
<form action="./file?method=upload" method="post" enctype="multipart/form-data">
用户名:<input type="text"><br/>
文件:<input type="file" /> <br/>
<input type="submit" />
</form>
二、手动实现文件上传
2.1 代码实现
update.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>
<form action="./update" method="post" enctype="multipart/form-data">
姓名:<input type="text" name="username"><br/>
文件:<input type="file" name="file"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
UpdateServlet.java
import com.sun.net.httpserver.HttpServer;
public class UpdateServlet extends HttpServlet{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取get方式提交的参数
//req.getQueryString();
//2.获取post方式提交的参数
//req.getInputStream();
//3。通用
//req.getParameter("key");
ServletInputStream inputStream = req.getInputStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
String content="";
while((content=reader.readLine())!=null) {
System.out.println(content);
}
reader.close();
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>update</servlet-name>
<servlet-class>org.jsoft.demo.UpdateServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>update</servlet-name>
<url-pattern>/update</url-pattern>
</servlet-mapping>
</web-app>
测试:
2.2 结果分析
上传文件,控制台打印的结果
------WebKitFormBoundarykJM6xysF4ZPr4em2
Content-Disposition: form-data; name="username"
1212
------WebKitFormBoundarykJM6xysF4ZPr4em2
Content-Disposition: form-data; name="file"; filename="a.txt"
Content-Type: text/plain
aaaa
啊啊啊
bbbb
------WebKitFormBoundarykJM6xysF4ZPr4em2--
分析
手动上传文件,最终获取上面的数据,不过我们要对上面的结果进行解析!
文件上传,在开发中经常用,每次都写解析程序!(工具类)
也可以使用开源的文件上传组件-FileUploade组件!
三.使用FileUploade组件实现文件上传
使用FileUploade组件,需要导包
commons-fileupload-1.2.1.jar
commons-io-1.4.jar
3.1 FileUploade 组件 API
|-- DiskFileItemFactory 文件上传工厂类(DiskFileItemFactory 是创建 FileItem 对象的工厂)
fac.setRepository(repository)
设置临时目录。
如果不设置,则系统默认为tomcat/tempm目录。
临时目录的作用:
上传文件时,文件大小超过内存,则肯定内存溢出的。
所以设置了临时目录。在上传过程中,会先把文件传到临时目录,
然后在从临时目录中传出到开发者指定的目录。
eg:
DiskFileItemFactory fac=new DiskFileItemFactory();
fac.setRepository(repository);
|-- ServletFileUpload 文件上传核心类(可以所有的FieItem对象)
List parseRequest(req) 解析请求数据,将表单项封装为一个个FileItem对象,并将对象放到List中并返回,
Boolean isMultipartContent(req) 判断表单类型,如果是文件上传表单,返回true。
void setFileSizeMax(30*1024*1024); 设置上传单个文件的大小,最大为30M
void setSizeMax(30*1024*1024); 设置上传文件的总大小,最大为50M
void setHeaderEncoding("UTF-8");设置获取表单项值得编码,相当于req.setCharacterEncoding("UTF-8")
eg:
DiskFileItemFactory fac=new DiskFileItemFactory();
ServletFileUpload uploadUtil=new ServletFileUpload(fac);
|-- FileItem 封装了表单项的信息。
普通表单项
fileItem.getFieldName() 获取表单项的name值
fileItem.String() 获取表单项的value值
fileItem.String("UTF-8") 指定编码集,获取表单项的value值
文件表单项
fileItem.getFieldName() 获取表单项的name值
fileItem.getContentType() 获取文件类型
fileItem.getName(); 获取文件名
fileItem.delete() 删除临时文件。(文件上传时会缓存数据到临时目录,上传完成后,需要删除)
fileItem.write(file) 写文件到指定的目录
eg:
不能写到其他盘,只能写到该项目下。否则报错。
fileItem.write(new File(this.getServletContext().getRealPath("./update/a.txt")));
fileItem.getInputStream(); 获取文件输入流
eg:
System.out.println("文件内容:");
InputStream in = fileItem.getInputStream();//文件内容
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
String content="";
while((content=reader.readLine())!=null) {
System.out.println(content);
}
3.1.1 文件上传案例代码
+++ 实现步骤
1、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录
2、使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
3、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。
4、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件
为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值
为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。
FileUpdateServlet.java
/**
* 使用FileUpload实现文件上传
* @author BGS
*
*/
public class FileUpdateServlet extends HttpServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.文件上传工厂类
DiskFileItemFactory fac=new DiskFileItemFactory();
//fac.setRepository(repository); //设置临时目录
//2.文件上传核心类
ServletFileUpload uploadUtil=new ServletFileUpload(fac);
//设置上传单个文件的大小,最大为30M
uploadUtil.setFileSizeMax(30*1024*1024);
//设置上传文件的总大小,最大为50M
uploadUtil.setSizeMax(50*1024*1024);
//设置获取表单项值得编码,相当于req.setCharacterEncoding("UTF-8")
uploadUtil.setHeaderEncoding("UTF-8");
//判断请求的类型。
if(ServletFileUpload.isMultipartContent(req)) {
try {
//文件上传表单
List<FileItem> fileItems = uploadUtil.parseRequest(req);
for(FileItem fileItem:fileItems) {
if(fileItem.isFormField()) {
//普通表单项
String fieldName = fileItem.getFieldName(); //表单项的name值
String value = fileItem.getString();//表单项的value值
//String value1 = fileItem.getString("UTF-8");
System.out.println(fieldName+":"+value);
}else {
//文件表单项
//表单项的name值
String fieldName = fileItem.getFieldName(); //表单项的name值
//文件名称
String fileName = fileItem.getName();//文件名
//文件类型
String contentType = fileItem.getContentType();//文件类型
//读取文件内容
InputStream in = fileItem.getInputStream();//文件内容
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
String content="";
while((content=reader.readLine())!=null) {
System.out.println(content);
}
//将上传的文件传到update路径
fileItem.write(new File(this.getServletContext().getRealPath("./update/a.txt")));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}else {
//普通表单
System.out.println("普通表单提交");
}
}
}
update.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>
<form action="./update" method="post" enctype="multipart/form-data">
姓名:<input type="text" name="username"><br/>
文件:<input type="file" name="file"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>update</servlet-name>
<servlet-class>org.jsoft.demo.UpdateServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>update</servlet-name>
<url-pattern>/update</url-pattern>
</servlet-mapping>
</web-app>
3.1.1 文件上传处理细节
+++ 中文文件乱码问题
文件名中文乱码问题,可调用ServletUpLoader的setHeaderEncoding方法,
或者设置request的setCharacterEncoding属性
+++ 临时文件的删除问题
由于文件大小超出DiskFileItemFactory.setSizeThreshold方法设置的内存缓
冲区的大小时,Commons-fileupload组件将使用临时文件保存上传数据,因此
在程序结束时,务必调用FileItem.delete方法删除临时文件。
Delete方法的调用必须位于流关闭之后,否则会出现文件占用,而导致删除失
败的情况。
+++ 文件存储位置
1.为保证服务器安全,上传文件应保存在应用程序的WEB-INF目录下,或者不
受WEB服务器管理的目录。
2.为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上
传程序应保证上传文件具有唯一文件名。
3.为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根
据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储。
3.2 FileUploade 实现文件上传完整版
FileUpLoadeServlet .java
/**
* 使用FileUpload实现文件上传
* @author BGS
*
*/
public class FileUpLoadeServlet extends HttpServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/*********文件上传组件: 处理文件上传************/
try {
// 1. 文件上传工厂
FileItemFactory factory = new DiskFileItemFactory();
// 2. 创建文件上传核心工具类
ServletFileUpload upload = new ServletFileUpload(factory);
// 一、设置单个文件允许的最大的大小: 30M
upload.setFileSizeMax(30*1024*1024);
// 二、设置文件上传表单允许的总大小: 80M
upload.setSizeMax(80*1024*1024);
// 三、 设置上传表单文件名的编码
// 相当于:request.setCharacterEncoding("UTF-8");
upload.setHeaderEncoding("UTF-8");
// 3. 判断: 当前表单是否为文件上传表单
if (ServletFileUpload.isMultipartContent(req)){
// 4. 把请求数据转换为一个个FileItem对象,再用集合封装
List<FileItem> list = upload.parseRequest(req);
// 遍历: 得到每一个上传的数据
for (FileItem item: list){
// 判断:表单项类型
if (item.isFormField()){
// 普通文本数据
String fieldName = item.getFieldName(); // 表单元素名称
String content = item.getString(); // 表单元素名称, 对应的数据
//item.getString("UTF-8"); 指定编码
System.out.println(fieldName + " " + content);
}else {
// 文件数据
String fieldName = item.getFieldName(); // 表单元素名称
String name = item.getName(); // 文件名
String content = item.getString(); // 表单元素名称, 对应的数据
String type = item.getContentType(); // 文件类型
InputStream in = item.getInputStream(); // 上传文件流
/*
* 四、文件名重名
* 对于不同用户readme.txt文件,不希望覆盖!
* 后台处理: 给用户添加一个唯一标记!
*/
// a. 随机生成一个唯一标记
String id = UUID.randomUUID().toString();
// b. 与文件名拼接
name = id +"#"+ name;
// 获取上传基路径,并上传
File file = new File(getServletContext().getRealPath("/uploade"),name);
item.write(file);
//删除系统产生的临时文件
item.delete();
System.out.println();
}
}
}
else {
System.out.println("当前表单不是文件上传表单,处理失败!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
inedx.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>
<form action="./uploade" method="post" enctype="multipart/form-data">
姓名:<input type="text" name="username"><br/>
文件:<input type="file" name="file"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>uploade</servlet-name>
<servlet-class>org.jsoft.demo.FileUpLoadeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>uploade</servlet-name>
<url-pattern>/uploade</url-pattern>
</servlet-mapping>
</web-app>
测试:
3.3 FileUploade 组件的原理
使用servelt读取post请求发送的文件,获取的流文件,读取出来的是一段格式代码。
------WebKitFormBoundarykJM6xysF4ZPr4em2
Content-Disposition: form-data; name="username"
1212
------WebKitFormBoundarykJM6xysF4ZPr4em2
Content-Disposition: form-data; name="file"; filename="a.txt"
Content-Type: text/plain
aaaa
啊啊啊
bbbb
------WebKitFormBoundarykJM6xysF4ZPr4em2--
我们可以自定义解析器,解析输入的内容。
FileUpload组件就是利用这个原理。它把每一个form表单项封装为一个FileItem
对象。我们通过fileItem对象,来做文件的一系列操作。
四、文件上传与下载的完整版本
4.1 代码实现
FileServlet.java
/**
* 处理文件上传与下载
* @author Jie.Yuan
*
*/
public class FileServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取请求参数: 区分不同的操作类型
String method = request.getParameter("method");
if ("upload".equals(method)) {
// 上传
upload(request,response);
}
else if ("downList".equals(method)) {
// 进入下载列表
downList(request,response);
}
else if ("down".equals(method)) {
// 下载
down(request,response);
}
}
/**
* 1. 上传
*/
private void upload(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// 1. 创建工厂对象
FileItemFactory factory = new DiskFileItemFactory();
// 2. 文件上传核心工具类
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置大小限制参数
upload.setFileSizeMax(10*1024*1024); // 单个文件大小限制
upload.setSizeMax(50*1024*1024); // 总文件大小限制
upload.setHeaderEncoding("UTF-8"); // 对中文文件编码处理
// 判断
if (upload.isMultipartContent(request)) {
// 3. 把请求数据转换为list集合
List<FileItem> list = upload.parseRequest(request);
// 遍历
for (FileItem item : list){
// 判断:普通文本数据
if (item.isFormField()){
// 获取名称
String name = item.getFieldName();
// 获取值
String value = item.getString();
System.out.println(value);
}
// 文件表单项
else {
/******** 文件上传 ***********/
// a. 获取文件名称
String name = item.getName();
// ----处理上传文件名重名问题----
// a1. 先得到唯一标记
String id = UUID.randomUUID().toString();
// a2. 拼接文件名
name = id + "#" + name;
// b. 得到上传目录
String basePath = getServletContext().getRealPath("/upload");
// c. 创建要上传的文件对象
File file = new File(basePath,name);
// d. 上传
item.write(file);
item.delete(); // 删除组件运行时产生的临时文件
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 2. 进入下载列表
*/
private void downList(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 实现思路:先获取upload目录下所有文件的文件名,再保存;跳转到down.jsp列表展示
//1. 初始化map集合Map<包含唯一标记的文件名, 简短文件名> ;
Map<String,String> fileNames = new HashMap<String,String>();
//2. 获取上传目录,及其下所有的文件的文件名
String bathPath = getServletContext().getRealPath("/upload");
// 目录
File file = new File(bathPath);
// 目录下,所有文件名
String list[] = file.list();
// 遍历,封装
if (list != null && list.length > 0){
for (int i=0; i<list.length; i++){
// 全名
String fileName = list[i];
// 短名
String shortName = fileName.substring(fileName.lastIndexOf("#")+1);
// 封装
fileNames.put(fileName, shortName);
}
}
// 3. 保存到request域
request.setAttribute("fileNames", fileNames);
// 4. 转发
request.getRequestDispatcher("/downlist.jsp").forward(request, response);
}
/**
* 3. 处理下载
*/
private void down(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取用户下载的文件名称(url地址后追加数据,get)
String fileName = request.getParameter("fileName");
fileName = new String(fileName.getBytes("ISO8859-1"),"UTF-8");
// 先获取上传目录路径
String basePath = getServletContext().getRealPath("/upload");
// 获取一个文件流
InputStream in = new FileInputStream(new File(basePath,fileName));
// 如果文件名是中文,需要进行url编码
fileName = URLEncoder.encode(fileName, "UTF-8");
// 设置下载的响应头
response.setHeader("content-disposition", "attachment;fileName=" + fileName);
// 获取response字节流
OutputStream out = response.getOutputStream();
byte[] b = new byte[1024];
int len = -1;
while ((len = in.read(b)) != -1){
out.write(b, 0, len);
}
// 关闭
out.close();
in.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>fileServlet</servlet-name>
<servlet-class>org.jsoft.demo.FileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>fileServlet</servlet-name>
<url-pattern>/fileServlet</url-pattern>
</servlet-mapping>
</web-app>
index.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>
<a href="${pageContext.request.contextPath }/upload.jsp">文件上传</a>
<a href="${pageContext.request.contextPath }/fileServlet?method=downList">文件下载</a>
</body>
</html>
downlist.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<table border="1" align="center">
<tr>
<th>序号</th>
<th>文件名</th>
<th>操作</th>
</tr>
<c:forEach var="en" items="${requestScope.fileNames}" varStatus="vs">
<tr>
<td>${vs.count }</td>
<td>${en.value }</td>
<td>
<%--<a href="${pageContext.request.contextPath }/fileServlet?method=down&..">下载</a>--%>
<!-- 构建一个地址 -->
<c:url var="url" value="fileServlet">
<c:param name="method" value="down"></c:param>
<c:param name="fileName" value="${en.key}"></c:param>
</c:url>
<!-- 使用上面地址 -->
<a href="${url }">下载</a>
</td>
</tr>
</c:forEach>
</table>
</body>
</html>
upload.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>
<form name="frm_test" action="${pageContext.request.contextPath }/fileServlet?method=upload" method="post" enctype="multipart/form-data">
<%--<input type="hidden" name="method" value="upload">--%>
用户名:<input type="text" name="userName"> <br/>
文件: <input type="file" name="file_img"> <br/>
<input type="submit" value="提交">
</form>
</body>
</html>
4.2 展示结果
五、其他
5.1 设置客户端以下载的方式打开获取的资源
response.setHeader("content-disposition", "attachment;fileName=" + fileName);
5.1.1 案例一:读取本地文件,然后发送给客户端。客户端以下载的方式下载资源文件。
IndexServlet.java
public class IndexServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//读取本地文件
File f=new File(this.getServletContext().getRealPath("/index.jsp"));
//设置客户端以下载的方式接收资源文件
resp.setHeader("content-disposition", "attachment;fileName=a.txt" );
//发送本地文件
ServletOutputStream out = resp.getOutputStream();
FileInputStream in=new FileInputStream(f);
byte[]buf=new byte[1024];
int length=0;
while((length=in.read(buf))!=-1) {
out.write(buf,0,length);
}
out.close();
in.close();
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>index</servlet-name>
<servlet-class>org.jsoft.demo.IndexServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>index</servlet-name>
<url-pattern>/index</url-pattern>
</servlet-mapping>
</web-app>
测试:
客户端直接访问servlet。可以看到下载a.txt文件。
5.1.2 案例二:服务端写出一段文字,然后发送给客户端。客户端以下载的方式下载资源文件。
IndexServlet.java
public class IndexServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置客户端以下载的方式接收资源文件
resp.setHeader("content-disposition", "attachment;fileName=a.txt" );
resp.getWriter().write("Hello World");
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>index</servlet-name>
<servlet-class>org.jsoft.demo.IndexServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>index</servlet-name>
<url-pattern>/index</url-pattern>
</servlet-mapping>
</web-app>
测试:
客户端直接访问servlet。可以看到下载a.txt文件。