在Web應用系統開發中,文件上傳和下載功能是很是經常使用的功能。html
對於文件上傳,瀏覽器在上傳的過程當中是將文件以流的形式提交到服務器端的,若是直接使用Servlet獲取上傳文件的輸入流而後再解析裏面的請求參數是比較麻煩,因此通常選擇採用apache的開源工具common-fileupload這個文件上傳組件。這個common-fileupload上傳組件的jar包能夠去apache官網上面下載。common-fileupload是依賴於common-io這個包的,因此還須要下載這個包。java
開發環境搭建web
建立一個FileUploadAndDownload項目,加入Apache的commons-fileupload文件上傳組件的相關Jar包,如圖20所示。算法
圖20 導入Jar包apache
實現文件上傳瀏覽器
● 編寫文件上傳頁面,upload.jsp頁面代碼以下:安全
<%@ page language="java" contentType="text/html; charset=UTF-8"服務器
pageEncoding="UTF-8"%>app
<!DOCTYPE html>dom
<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文件上傳組件處理文件上傳步驟:
// 一、建立一個DiskFileItemFactory工廠
DiskFileItemFactory factory = new DiskFileItemFactory();
// 二、建立一個文件上傳解析器
ServletFileUpload upload = new ServletFileUpload(factory);
// 解決上傳文件名的中文亂碼
upload.setHeaderEncoding("UTF-8");
// 三、判斷提交上來的數據是不是上傳表單的數據
if (!ServletFileUpload.isMultipartContent(request)) {
// 按照傳統方式獲取數據
return;
}
// 四、使用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服務器,運行效果如圖2一、圖2二、圖23和圖24所示。
圖21 上傳文件
圖22 上傳文件成功
圖23 控制檯中打印了上傳文件的文件名
圖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文件上傳組件處理文件上傳步驟:
// 一、建立一個DiskFileItemFactory工廠
DiskFileItemFactory factory = new DiskFileItemFactory();
// 設置工廠的緩衝區的大小,當上傳的文件大小超過緩衝區的大小時,
//就會生成一個臨時文件存放到指定的臨時目錄當中。
factory.setSizeThreshold(1024 * 100);
// 設置緩衝區的大小爲100KB,若是不指定,那麼緩衝區的大小默認是10KB
// 設置上傳時生成的臨時文件的保存目錄
factory.setRepository(tmpFile);
// 二、建立一個文件上傳解析器
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");
// 三、判斷提交上來的數據是不是上傳表單的數據
if (!ServletFileUpload.isMultipartContent(request)) {
// 按照傳統方式獲取數據
return;
}
// 設置上傳單個文件的大小的最大值,目前是設置爲1024*1024字節,也就是1MB
upload.setFileSizeMax(1024 * 1024);
// 設置上傳文件總量的最大值
upload.setSizeMax(1024 * 1024 * 10);
// 四、使用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點小細節問題進行改進以後,咱們的文件上傳功能就算是作得比較完善了。