target
了解使用 JavaWeb方式的文件上传
掌握文件上传的表单设置
掌握使用 SpringMVC 进行文件上传
理解使用SpringMVC进行文件下载
文件上传你是任何一个项目都必不缺少的,抖音里面头像的上传,QQ里面视频的上传,拼多多里面商品详情介绍等,文件上传随处可见。下面介绍JavaWeb方式的原始上传和SpringMVC方式的上传。
1. 文件上传-JavaWeb方式
对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以一般选择采用apache的开源工具common-fileupload
这个文件上传组件。common-fileupload
是依赖于common-io
这个包的,所以还需要下载这个包。
新建动态Web项目:FileUpload-1
在WebContent目录下新建上传表单:upload.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<form method="post" action="${pageContext.servletContext.contextPath }/upload"
enctype="multipart/form-data">
上传用户:<input type="text" name="username"><br>
上传文件:<input type="file" name="myfile" multiple="multiple"><br>
<input type="submit" value="上传">
</form>
</body>
</html>
新建 UploadServlet类处理文件上传:
package com.lee.servlet;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final int FILE_SIZE = 10;// 单个文件最大值
private static final String EXT_NAME = "gif,pdf,dmg,jpg,jpeg,png,bmp,swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb,doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2";// 定义允许上传的文件名
private static final String savePath = "WEB-INF/upload";
private static final String temPath = "WEB-INF/tem";
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 得到上传文件的保存目录,将上传文件存放在WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
String saveRealPath = this.getServletContext().getRealPath(savePath);
createDir(saveRealPath);// 创建WEB-INF/upload目录
// 上传时生成临时文件保存目录
String saveTemPath = this.getServletContext().getRealPath(temPath);
File temDir = createDir(saveTemPath);// 创建WEB-INF/tem文件夹
String message = "";// 消息提醒
String fileName = "";// 上传的文件名
// 使用Apache文件上传组件处理文件上传步骤
try {
/**
* 1.创建DiskFileItemFactory对象
*/
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置工厂的缓冲区大小,当上传的文件大小超过缓冲区大小时,就会生成一个临时文件存放在指定的临时目录之中
// 设置缓冲区大小为100kb,如果不设置,默认缓冲区大小是10kb
factory.setSizeThreshold(1024 * 100);
// 设置上传时生成的临时文件的保存目录
factory.setRepository(temDir);
/**
* 2.创建文件上传解析器
*/
ServletFileUpload upload = new ServletFileUpload(factory);
// 解决上传文件名的中文乱码问题
upload.setHeaderEncoding("UTF-8");
// 判断提交上来的数据是否是文件表单
if (!ServletFileUpload.isMultipartContent(req)) {
// 终止文件上传处理
return;
}
// 获取文件表单中的所有文件
List<FileItem> items = upload.parseRequest(req);
for (FileItem item : items) {
if (item.isFormField()) {// formfield中封装的是普通的输入项数据
String name = item.getFieldName();
// 解决输入项数据中文乱码问题
String value = item.getString("UTF-8");
// value = new String(value.getBytes("iso8859-1"),"UTF-8");
System.out.println(name + "=" + value);
} else {// formfield中封装的是上传文件
fileName = item.getName();// 得到文件上传名
System.out.println("文件名:" + fileName);
if (fileName == null || fileName.trim().length() == 0) {
continue;
}
/**
* 注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的 如: C:\Users\H__D\Desktop\1.txt
* 而有些则是: 1.txt
*/
// 处理获取到的上传文件的文件名的路径部分,只保留文件名部分
fileName = fileName.substring(fileName.lastIndexOf(File.separator) + 1);
// 得到上传文件的扩展名
String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
// 通过扩展名判断上传文件是否合法
System.out.println("上传文件的扩展名是:" + fileExt);
if (!EXT_NAME.contains(fileExt)) {
System.out.println("不允许上传" + fileExt + "格式的文件!");
message += "文件:" + fileName + ",上传文件扩展名不允许是" + fileExt + "<br>";
continue;
}
// 检查文件大小
if (item.getSize() == 0 || item.getSize() > FILE_SIZE * 1024 * 1024) {
message += "文件大小为:" + item.getSize() / 1024 / 1024 + "M,规定大小不超过" + FILE_SIZE + "M<br>";
continue;
}
// 得到存储的文件名
String saveFileName = makeFileName(fileName);
// 获取item中上传文件的输入流
InputStream is = item.getInputStream();
// 创建文件输出流
FileOutputStream out = new FileOutputStream(saveRealPath + File.separator + saveFileName);
// 创建一个缓冲区
byte[] b = new byte[1024];
int len = 0;
while ((len = is.read(b)) > 0) {
out.write(b, 0, len);
}
// 关闭流
out.close();
is.close();
// 删除临时文件
item.delete();
message += "文件:" + fileName + "上传成功!<br>";
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
message += fileName + "上传失败!";
}
req.setAttribute("message", message);
req.getRequestDispatcher("WEB-INF/views/message.jsp").forward(req, resp);
}
/**
* 为防止文件覆盖现象,为上传文件产生一个唯一的文件名
*
* @param fileName
* @return
*/
private String makeFileName(String fileName) {
return UUID.randomUUID().toString().replaceAll("-", "") + "_" + fileName;
}
/**
* 创建目录
*
* @param path
*/
private File createDir(String path) {
File pathDir = new File(path);
if (!pathDir.exists()) {
// 创建WEB-INF/upload目录
pathDir.mkdirs();
}
return pathDir;
}
}
在WEB-INF/views目录下新建消息页面:message.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>提示</title>
</head>
<body>
${message }
</body>
</html>
访问地址:http://localhost:8080/File/upload.jsp
此种方式可以上传一个或多个文件。
文件上传到服务器的位置"upload",该位置是指:workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps
几个细节:
①为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
②为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。
③要限制上传文件的最大值。
④要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。
⑤表单的enctype="multipart/form-data" method="post"
2. 文件上传-SpringMVC方式
Spring MVC 框架的文件上传是基于 commons-fileupload 组件的文件上传,只不过 Spring MVC 框架在原有文件上传组件上做了进一步封装,简化了文件上传的代码实现,取消了不同上传组件上的编程差异。
commons-fileupload 组件依赖于 Apache 的另外一个项目(commons-io),因此需要导入commons-io的jar包。
Commons 是 Apache 开放源代码组织中的一个 Java子项目,该项目包括文件上传、命令行处理、数据库连接池、XML 配置文件处理等模块。fileupload 就是其中用来处理基于表单的文件上传的子项目,commons-fileupload 组件性能优良,并支持任意大小文件的上传。
2.1 表单属性
由于多数文件上传都是通过表单形式提交给后台服务器的,因此,要实现文件上传功能,就需要提供一个文件上传的表单。同时该表单必须满足以下3个条件:
- form表单的method属性设置为post;
- form表单的enctype属性设置为
multipart/form-data
; - 提供
<input type="file" name="fileName" />
的文件上传输入框。
表单的 enctype 属性指定的是表单数据的编码方式,该属性有以下 3 个值:
- application/x-www-form-urlencoded:这是默认的编码方式,它只处理表单域里的 value 属性值。
- multipart/form-data:该编码方式以二进制流的方式来处理表单数据,并将文件域指定文件的内容封装到请求参数里。
- text/plain:该编码方式只有当表单的 action 属性为“mailto:”URL 的形式时才使用,主要适用于直接通过表单发送邮件的方式。
在HTML5之前,如果想要上传多个文件,必须使用多个<input>
元素。但是在HTML5中,在<input>
元素配置multiple属性即可进行多个文件的上传:
<input type="file" name="fileName" multiple />
2.3 MultipartFile接口
在 Spring MVC 框架中上传文件时将文件相关信息及操作封装到 MultipartFile 对象中,因此开发者只需要使用 MultipartFile 类型声明模型类的一个属性即可对被上传文件进行操作。该接口具有如下方法:
名称 | 作用 |
---|---|
byte[] getBytes() | 以字节数组的形式返回文件的内容 |
String getContentType() | 返回文件的内容类型 |
InputStream getInputStream() | 返回一个InputStream,从中读取文件的内容 |
String getName() | 返回请求参数的名称 |
String getOriginalFillename() | 返回客户端提交的原始文件名称 |
long getSize() | 返回文件的大小,单位为字节 |
boolean isEmpty() | 判断被上传文件是否为空 |
void transferTo(File destination) | 将上传文件保存到目标目录下 |
在上传文件时需要在配置文件中使用 Spring 的 org.springframework.web.multipart.commons.CommonsMultipartResolver 类配置 MultipartResolver
用于文件上传。
2.3 springmvc配置文件
当客户端form表单的enctype属性为multipart/form-data时,浏览器就会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP请求。Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。
在Sring MVC中使用MultipartResolver也非常简单,只需要在Spring MVC配置文件springmvc.xml中定义MultipartResolver接口实现类即可,示例如下:
<!-- 配置文件解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置最大允许上传的文件的大小,单位是B -->
<property name="maxUploadSize" value="50000000"></property>
<!-- 设置最大允许上传的单个文件的大小,单位是B -->
<property name="maxUploadSizePerFile" value="10000000"></property>
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
注意:
由于MultipartResolver实现类CommonsMultipartResolver
内部是引用multipartResolver字符串获取该实现类对象并完成文件上传操作的,所以在配置CommonsMultipartResolver
时必须指定该bean的id为multipartResolver。
由于MultipartResolver实现类CommonsMultipartResolver是Spring MVC内部通过Apache Commons FileUpload技术实现的。因此Spirng MVC的文件上传还需要依赖Apache Commons FileUpload的组件,即需要导入支持文件上传和下载的JAR包,具体如下:
- commons-fileupload.jar
- commons-io.jar
2.4 代码实现
新建动态Web项目:FileUpload-2
① 引入 jar
将一下jar 复制到lib文件夹下:
commons-fileupload-1.4.jar
commons-io-2.6.jar
commons-logging-1.2.jar
spring-aop-4.3.9.RELEASE.jar
spring-beans-4.3.9.RELEASE.jar
spring-context-4.3.9.RELEASE.jar
spring-core-4.3.9.RELEASE.jar
spring-expression-4.3.9.RELEASE.jar
spring-web-4.3.9.RELEASE.jar
spring-webmvc-4.3.9.RELEASE.jar
② 创建 web.xml 文件
在 WEB-INF 目录下创建 web.xml 文件。为防止中文乱码,需要在 web.xml 文件中添加字符编码过滤器。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>FileUpload-2</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- The front controller of this Spring Web application, responsible for
handling all application requests -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
③ 创建 springmvc配置文件
在 src 下配置 springmvc.xml ,配置包扫描、视图解析器以及文件上传相关设置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 使用扫描机制扫描控制器类 -->
<context:component-scan
base-package="com.lee.controller" />
<!-- 配置视图解析器 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 配置文件解析器 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置最大允许上传的文件的大小,单位是B -->
<property name="maxUploadSize" value="1000000000"></property>
<!-- 设置最大允许上传的单个文件的大小,单位是B -->
<property name="maxUploadSizePerFile" value="100000000"></property>
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
</beans>
开发经验:一定要加上id="multipartResolver"
,否则会报错400。
④ 文件上传页面
在 WebContent 目录下创建 JSP页面 upload.jsp,在该页面中使用表单上传文件(可以一个或多个文件呢),具体代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SpringMVC方式上传</title>
</head>
<body>
<form action="${pageContext.servletContext.contextPath }/upload" method="post" enctype="multipart/form-data">
上传文件:<input type="file" name="files" multiple />
<input type="submit" value="上传">
</form>
</body>
</html>
⑤ 创建控制器类
package com.lee.controller;
@Controller
public class UploadController {
private static final String EXT_NAME = "gif,pdf,dmg,jpg,jpeg,png,bmp,swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb,doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2";// 定义允许上传的文件名
private static final String SAVE_PATH = "WEB-INF/upload";
@RequestMapping("/upload")
public String fileUpload(@RequestParam("files") List<MultipartFile> files, HttpServletRequest req,
Map<String, Object> map) {
String message = "";
// 对上传的文件进行解析
if (files != null && files.size() > 0) {
for (MultipartFile file : files) {
// 获取文件的原始名字
String filename = file.getOriginalFilename();
// 设置文件保存目录
String saveRealPath = req.getServletContext().getRealPath(SAVE_PATH);
File saveDir = new File(saveRealPath);
if (!saveDir.exists()) {
saveDir.mkdirs();
}
// 判断文件格式是否符合
String extFileName = filename.substring(filename.lastIndexOf(".") + 1);
if (!EXT_NAME.contains(extFileName)) {
message += filename + "格式不符合!<br>";
map.put("message", message);
continue;
}
// 设置保存文件的名字:UUID或者时间进行重新命名
String newFileName = UUID.randomUUID().toString().replaceAll("-", "") + "_" + filename;
// 使用multipartFile接口上传
try {
File saveFile = new File(saveRealPath + File.separator + newFileName);
file.transferTo(saveFile);
message += filename + "上传成功!<br>";
} catch (Exception e) {
e.printStackTrace();
message += filename + "上传失败!<br>";
}
map.put("message", message);
}
}
return "success";
}
}
⑥ 创建成功显示页面
在 WEB-INF/views 目录下创建页面 success.jsp。具体代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>提示</title>
</head>
<body>
${message }
</body>
</html>
部署项目,进行测试,可以选择一个或多个文件测试。
3. 文件下载
3.1 实现方法
实现文件下载有以下两种方法:
- 通过超链接实现下载。
- 利用程序编码实现下载。
通过超链接实现下载固然简单,但暴露了下载文件的真实位置,并且只能下载存放在 Web 应用程序所在的目录下的文件。
利用程序编码实现下载可以增加安全访问控制,还可以从任意位置提供下载的数据,可以将文件存放到 Web 应用程序以外的目录中,也可以将文件保存到数据库中。
利用程序实现下载需要设置两个报头:
① Web 服务器需要告诉浏览器其所输出内容的类型不是普通文本文件或 HTML 文件,而是一个要保存到本地的下载文件,这需要设置 Content-Type 的值为 application/x-msdownload。
② Web 服务器希望浏览器不直接处理相应的实体内容,而是由用户选择将相应的实体内容保存到一个文件中,这需要设置 Content-Disposition 报头。
该报头指定了接收程序处理数据内容的方式,在 HTTP 应用中只有 attachment 是标准方式,attachment 表示要求用户干预。在 attachment 后面还可以指定 filename 参数,该参数是服务器建议浏览器将实体内容保存到文件中的文件名称。
设置报头的示例如下:
response.setHeader("Content-Type", "application/x-msdownload");
response.setHeader("Content-Disposition", "attachment;filename="+filename);
3.2 代码实现
下面继续通过 FileUpload-2 应用讲述利用程序实现下载的过程,要求从(workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\WEB-INF\uploadf)中下载文件,具体开发步骤如下:
① 编写控制器类
首先编写控制器类 FileDownController,在该类中有 3 个方法,即 show、down 和 toUTF8String。其中,show 方法获取被下载的文件名称;down 方法执行下载功能;toUTF8String 方法是下载保存时中文文件名的字符编码转换方法。
package com.lee.controller;
@Controller
public class FileDownController {
/**
* 显示要下载的文件
*/
@RequestMapping("showDownFiles")
public String show(HttpServletRequest request, Model model) {
// 从 workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\下载
String realpath = request.getServletContext()
.getRealPath("WEB-INF/upload");
File dir = new File(realpath);
File files[] = dir.listFiles();
// 获取该目录下的所有文件名
ArrayList<String> fileName = new ArrayList<String>();
for (int i = 0; i < files.length; i++) {
fileName.add(files[i].getName());
}
model.addAttribute("files", fileName);
return "showDownFiles";
}
/**
* 执行下载
*/
@RequestMapping("down")
public String down(@RequestParam String filename,
HttpServletRequest request, HttpServletResponse response) {
String aFilePath = null; // 要下载的文件路径
FileInputStream in = null; // 输入流
ServletOutputStream out = null; // 输出流
try {
// 从workspace\.metadata\.plugins\org.eclipse.wst.server.core\
// tmp0\wtpwebapps下载
aFilePath = request.getServletContext().getRealPath("WEB-INF/upload");
// 设置下载文件使用的报头
response.setHeader("Content-Type", "application/x-msdownload");
response.setHeader("Content-Disposition", "attachment; filename="
+ toUTF8String(filename));
// 读入文件
in = new FileInputStream(aFilePath + File.separator+ filename);
// 得到响应对象的输出流,用于向客户端输出二进制数据
out = response.getOutputStream();
out.flush();
int aRead = 0;
byte b[] = new byte[1024];
while ((aRead = in.read(b)) != -1 & in != null) {
out.write(b, 0, aRead);
}
out.flush();
in.close();
out.close();
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
/**
* 下载保存时中文文件名的字符编码转换方法
*/
public String toUTF8String(String str) {
StringBuffer sb = new StringBuffer();
int len = str.length();
for (int i = 0; i < len; i++) {
// 取出字符中的每个字符
char c = str.charAt(i);
// Unicode码值为0~255时,不做处理
if (c >= 0 && c <= 255) {
sb.append(c);
} else { // 转换 UTF-8 编码
byte b[];
try {
b = Character.toString(c).getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
b = null;
}
// 转换为%HH的字符串形式
for (int j = 0; j < b.length; j++) {
int k = b[j];
if (k < 0) {
k &= 255;
}
sb.append("%" + Integer.toHexString(k).toUpperCase());
}
}
}
return sb.toString();
}
}
② 创建文件列表页面
下载文件示例需要一个显示被下载文件的 JSP 页面 showDownFiles.jsp,在WEB-INF/views目录下编写,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<table>
<tr>
<td>被下载的文件名</td>
</tr>
<!--遍历 model中的 files-->
<c:forEach items="${files}" var="filename">
<tr>
<td>
<a href="${pageContext.request.contextPath }/down?filename=${filename}">${filename}</a>
</td>
</tr>
</c:forEach>
</table>
</body>
</html>
③ 测试下载功能
将应用部署到到 Tomcat 服务器并启动 Tomcat 服务器,然后通过地址"http://localhost:8080/FileUpload-2/showDownFiles"测试下载示例。