IT兄弟连 JavaWeb教程 文件上传技术

时间:2023-03-08 19:47:25

在Web应用系统开发中,文件上传和下载功能是非常常用的功能。

对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以一般选择采用apache的开源工具common-fileupload这个文件上传组件。这个common-fileupload上传组件的jar包可以去apache官网上面下载。common-fileupload是依赖于common-io这个包的,所以还需要下载这个包。

开发环境搭建

创建一个FileUploadAndDownload项目,加入Apache的commons-fileupload文件上传组件的相关Jar包,如图20所示。

IT兄弟连 JavaWeb教程 文件上传技术

图20  导入Jar包

实现文件上传

●  编写文件上传页面,upload.jsp页面代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>兄弟连IT教育</title>

</head>

<body>

<form action="${pageContext.request.contextPath}/upload"

enctype="multipart/form-data" method="post">

上传用户:<input type="text" name="username"><br />

上传文件1:<input type="file" name="file1"><br />

上传文件2:<input type="file" name="file2"><br />

<input type="submit" value="提交">

</form>

</body>

</html>

●  编写消息提示页面,message.jsp页面代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>兄弟连IT教育</title>

</head>

<body>

${message}

</body>

</html>

●  编写处理文件上传的Servlet

package com.xdl.servlet;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.util.List;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;

import org.apache.commons.fileupload.disk.DiskFileItemFactory;

import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

public void service(HttpServletRequest request,

HttpServletResponse response) throws ServletException, IOException {

// 得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,

// 不允许外界直接访问,保证上传文件的安全

String savePath = this.getServletContext().

getRealPath("/WEB-INF/ upload");

File file = new File(savePath);

// 判断上传文件的保存目录是否存在

if (!file.exists() && !file.isDirectory()) {

System.out.println(savePath + "目录不存在,需要创建");

// 创建目录

file.mkdir();

}

// 消息提示

String message = "";

try {

// 使用Apache文件上传组件处理文件上传步骤:

// 1、创建一个DiskFileItemFactory工厂

DiskFileItemFactory factory = new DiskFileItemFactory();

// 2、创建一个文件上传解析器

ServletFileUpload upload = new ServletFileUpload(factory);

// 解决上传文件名的中文乱码

upload.setHeaderEncoding("UTF-8");

// 3、判断提交上来的数据是否是上传表单的数据

if (!ServletFileUpload.isMultipartContent(request)) {

// 按照传统方式获取数据

return;

}

// 4、使用ServletFileUpload解析器解析上传数据,

//解析结果返回的是一个List<FileItem>集合,

//每一个FileItem对应一个Form表单的输入项

List<FileItem> list = upload.parseRequest(request);

for (FileItem item : list) {

// 如果fileitem中封装的是普通输入项的数据

if (item.isFormField()) {

String name = item.getFieldName();

// 解决普通输入项的数据的中文乱码问题

String value = item.getString("UTF-8");

System.out.println(name + "=" + value);

} else {// 如果fileitem中封装的是上传文件

// 得到上传的文件名称,

String filename = item.getName();

System.out.println(filename);

if (filename == null || filename.trim().equals("")) {

continue;

}

// 处理获取到的上传文件的文件名的路径部分,只保留文件名部分

filename = filename.substring

(filename.lastIndexOf ("\\") + 1);

// 获取item中的上传文件的输入流

InputStream in = item.getInputStream();

// 创建一个文件输出流

FileOutputStream out =

new FileOutputStream(savePath + "\\" + filename);

// 创建一个缓冲区

byte buffer[] = new byte[1024];

// 判断输入流中的数据是否已经读完的标识

int len = 0;

// 循环将输入流读入到缓冲区当中,

(len=in.read(buffer))>0就表示in里面还有数据

while ((len = in.read(buffer)) > 0) {

out.write(buffer, 0, len);

}

// 关闭输入流

in.close();

// 关闭输出流

out.close();

// 删除处理文件上传时生成的临时文件

item.delete();

message = "文件上传成功!";

}

}

} catch (Exception e) {

message = "文件上传失败!";

e.printStackTrace();

}

request.setAttribute("message", message);

request.getRequestDispatcher("/message.jsp").forward(request, response);

}

}

●  在web.xml文件中注册UploadServlet。

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns="http://xmlns.jcp.org/xml/ns/javaee"

xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"

version="3.1">

<servlet>

<servlet-name>UploadServlet</servlet-name>

<servlet-class>com.xdl.servlet.UploadServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>UploadServlet</servlet-name>

<url-pattern>/upload</url-pattern>

</servlet-mapping>

</web-app>

启动Tomcat服务器,运行效果如图21、图22、图23和图24所示。

IT兄弟连 JavaWeb教程 文件上传技术

图21  上传文件

IT兄弟连 JavaWeb教程 文件上传技术

图22  上传文件成功

IT兄弟连 JavaWeb教程 文件上传技术

图23  控制台中打印了上传文件的文件名

IT兄弟连 JavaWeb教程 文件上传技术

图24  服务器端接收到了客户端上传的文件

文件上传的细节

上述的代码虽然可以成功将文件上传到服务器上面的指定目录当中,但是文件上传功能有许多需要注意的小细节问题,以下列出的几点需要特别注意的

●  为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。

●  为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。

●  为防止一个目录下面出现太多文件,要使用hash算法打散存储。

●  要限制上传文件的最大值。

●  要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。

●  针对上述提出的5点细节问题,我们来改进一下UploadServlet,改进后的代码如下:

package com.xdl.servlet;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.util.List;

import java.util.UUID;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;

import org.apache.commons.fileupload.FileUploadBase;

import org.apache.commons.fileupload.ProgressListener;

import org.apache.commons.fileupload.disk.DiskFileItemFactory;

import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

public void service(HttpServletRequest request,

HttpServletResponse response) throws ServletException, IOException {

// 得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,

// 不允许外界直接访问,保证上传文件的安全

String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");

// 上传时生成的临时文件保存目录

String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");

File tmpFile = new File(tempPath);

if (!tmpFile.exists()) {

// 创建临时目录

tmpFile.mkdir();

}

// 消息提示

String message = "";

try {

// 使用Apache文件上传组件处理文件上传步骤:

// 1、创建一个DiskFileItemFactory工厂

DiskFileItemFactory factory = new DiskFileItemFactory();

// 设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,

//就会生成一个临时文件存放到指定的临时目录当中。

factory.setSizeThreshold(1024 * 100);

// 设置缓冲区的大小为100KB,如果不指定,那么缓冲区的大小默认是10KB

// 设置上传时生成的临时文件的保存目录

factory.setRepository(tmpFile);

// 2、创建一个文件上传解析器

ServletFileUpload upload = new ServletFileUpload(factory);

// 监听文件上传进度

upload.setProgressListener(new ProgressListener() {

public void update(long pBytesRead, long pContentLength,

int arg2) {

System.out.println("文件大小为:" + pContentLength

+ ",当前已处理:" + pBytesRead);

}

});

// 解决上传文件名的中文乱码

upload.setHeaderEncoding("UTF-8");

// 3、判断提交上来的数据是否是上传表单的数据

if (!ServletFileUpload.isMultipartContent(request)) {

// 按照传统方式获取数据

return;

}

// 设置上传单个文件的大小的最大值,目前是设置为1024*1024字节,也就是1MB

upload.setFileSizeMax(1024 * 1024);

// 设置上传文件总量的最大值

upload.setSizeMax(1024 * 1024 * 10);

// 4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个

// List<FileItem>集合,每一个FileItem对应一个Form表单的输入项

List<FileItem> list = upload.parseRequest(request);

for (FileItem item : list) {

// 如果fileitem中封装的是普通输入项的数据

if (item.isFormField()) {

String name = item.getFieldName();

// 解决普通输入项的数据的中文乱码问题

String value = item.getString("UTF-8");

System.out.println(name + "=" + value);

} else {// 如果fileitem中封装的是上传文件

// 得到上传的文件名称

String filename = item.getName();

System.out.println(filename);

if (filename == null || filename.trim().equals("")) {

continue;

}

// 处理获取到的上传文件的文件名的路径部分,只保留文件名部分

filename = filename.substring(

filename.lastIndexOf("\\") + 1);

// 得到上传文件的扩展名

String fileExtName =

filename.substring(filename.

lastIndexOf(".") + 1);

// 如果需要限制上传的文件类型,那么可以通过文件的扩展名来判断上

// 传的文件类型是否合法

System.out.println("上传的文件的扩展名是:" + fileExtName);

// 获取item中的上传文件的输入流

InputStream in = item.getInputStream();

// 得到文件保存的名称

String saveFilename = makeFileName(filename);

// 得到文件的保存目录

String realSavePath = makePath(saveFilename, savePath);

// 创建一个文件输出流

FileOutputStream out = new FileOutputStream

(realSavePath + "\\" + saveFilename);

// 创建一个缓冲区

byte buffer[] = new byte[1024];

// 判断输入流中的数据是否已经读完的标识

int len = 0;

// 循环将输入流读入到缓冲区当中

while ((len = in.read(buffer)) > 0) {

out.write(buffer, 0, len);

}

// 关闭输入流

in.close();

// 关闭输出流

out.close();

// 删除处理文件上传时生成的临时文件

// item.delete();

message = "文件上传成功!";

}

}

} catch (FileUploadBase.FileSizeLimitExceededException e) {

e.printStackTrace();

request.setAttribute("message", "单个文件超出最大值!!!");

request.getRequestDispatcher("/message.jsp").

forward(request, response);

return;

} catch (FileUploadBase.SizeLimitExceededException e) {

e.printStackTrace();

request.setAttribute("message",

"上传文件的总的大小超出限制的最大值!!!");

request.getRequestDispatcher("/message.jsp").

forward(request, response);

return;

} catch (Exception e) {

message = "文件上传失败!";

e.printStackTrace();

}

request.setAttribute("message", message);

request.getRequestDispatcher("/message.jsp").

forward(request, response);

}

private String makeFileName(String filename) {

// 为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名

return UUID.randomUUID().toString() + "_" + filename;

}

/**

* 为防止一个目录下面出现太多文件,要使用hash算法打散存储

*/

private String makePath(String filename, String savePath) {

// 得到文件名的hashCode的值,得到的就是filename这个字符串对象在内存中的地址

int hashcode = filename.hashCode();

int dir1 = hashcode & 0xf;

int dir2 = (hashcode & 0xf0) >> 4;

// 构造新的保存目录

String dir = savePath + "\\" + dir1 + "\\" + dir2;

// File既可以代表文件也可以代表目录

File file = new File(dir);

// 如果目录不存在

if (!file.exists()) {

// 创建目录

file.mkdirs();

}

return dir;

}

}

针对上述提出的5点小细节问题进行改进之后,我们的文件上传功能就算是做得比较完善了。