一、簡單介紹
使用 Servlet上傳附件 原理上還是蠻簡單的,首先獲取上傳的附件對象,然後做一些簡單處理 後寫入到指定路徑的磁盤中。
二、準備條件
common-io.jar ,下載地址:http://commons.apache.org/io/download_io.cgi
common-upload.jar ,下載地址:http://jakarta.apache.org/commons/fileupload/
三、實現流程
首先在新建好的項目中 加入上述的兩個必須jar包,然後根據需求創建好upload.jsp頁面選擇附件,代碼如下:
<%@page import="java.io.File"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Use servlet upload file</title>
</head>
<body>
<form name="uploadForm" method="post" action="servlet/uploadFileServlet" enctype="multipart/form-data">
附件名稱:<input type="text" name="uploadName" value=""/><br/>
選擇附件:<input type="file" name="uploadFile"/><br/>
<input type="submit" value="上傳"/>
</form>
</body>
</html>
其中,enctype=“multipart/form-data” 是必須加入的,因爲原先沒有加入的普通表單是以字符提交到後臺的,但是加了之後所有參數全部會以二進制流格式傳入後臺,所以後臺處理代碼 request.getParameter(“paramName”) 將返回空字符串。
基本顯示如下圖:
頁面定義好了,接下來創建上面提交的servlet ,代碼如下:
package com.chenghui.servlet;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletConfig;
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.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
/**
* Servlet implementation class UploadFileServlet
*/
public class UploadFileServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String UPLOAD_PATH = "d:\\attach\\";
/**
* @see HttpServlet#HttpServlet()
*/
public UploadFileServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see Servlet#init(ServletConfig)
*/
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
}
/**
* @see Servlet#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
/**
*
* 偶然發現一個現象,當我定義的servlet 中重寫了 service方法,那麼每次調用的是service方法,除非註釋掉該方法纔會根據請求挑戰到對象的doPost/doGet中
* @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response)
*//*
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("service");
}*/
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//創建一個磁盤文件的工廠,然後將它 傳遞到servletFileUplaod的實例中
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
try {
//根據request對象獲取所有的文件集合,這裏包括input標籤輸入的值也屬於FileInput
List<FileItem> fileItemList = servletFileUpload.parseRequest(request);
Iterator iterator = fileItemList.iterator();
String showFileName = "";
while(iterator.hasNext()){
FileItem fileItem = (FileItem)iterator.next();
if(fileItem.isFormField()){ //是否是表單提交域,可以分區是否上傳的附件
String name = fileItem.getFieldName(); //input標籤的name
String value = fileItem.getString(); //input表單的value
if("uploadName".equals(name)){
showFileName = value;
}
}else{
String fieldName = fileItem.getFieldName(); //表單提交過來的file input標籤中name的屬性值
String fileName = fileItem.getName(); //file input上傳的文件名
String contentType = fileItem.getContentType(); //獲得上傳文件的類型
long size = fileItem.getSize(); //上傳文件的大小
String filePath = UPLOAD_PATH + showFileName + fileName.substring(fileName.lastIndexOf("."));
File saveFile = new File(filePath);
fileItem.write(saveFile); //將文件寫入磁盤中
//成功之後 簡單輸出一下
PrintWriter out = response.getWriter();
out.print("上傳成功!上傳文件爲:"+fileName+"<br/>保存的地址爲"+filePath+ "!!");
out.close();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
在web.xml中對應的配置:
<servlet>
<servlet-name>uploadFileServlet</servlet-name>
<servlet-class>com.chenghui.servlet.UploadFileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>uploadFileServlet</servlet-name>
<url-pattern>/servlet/uploadFileServlet</url-pattern>
</servlet-mapping>
好了基本上可以走一下流程了,在upload.jsp頁面選擇附件後點擊“上傳”按鈕,效果顯示如下:
然後去d:attach 目錄下找上傳的附件,找到了,ok上傳成功!但是顯示的文字有亂碼,這是因爲response對象在輸出的時候沒有指定具體的編碼,所以按照默認的iso-8859-1輸出 就亂碼了。
解決方案:在 PrintWriter out = response.getWriter(); 的上面加上
response.setContentType("text/html;charset=UTF-8");
就好了。
正確顯示效果如下:
另外,將上傳的附件是使用org.apache.commons.fileupload.FileItem 提供write方法寫入磁盤中的,這也可以用字節流完成該操作。
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//創建一個磁盤文件的工廠,然後將它 傳遞到servletFileUplaod的實例中
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
try {
//根據request對象獲取所有的文件集合,這裏包括input標籤輸入的值也屬於FileInput
List<FileItem> fileItemList = servletFileUpload.parseRequest(request);
Iterator iterator = fileItemList.iterator();
String showFileName = "";
//如果附件地址不存在 就創建一下
File uploadPath = new File(UPLOAD_PATH);
if(!uploadPath.exists()){
uploadPath.mkdir();
}
while(iterator.hasNext()){
FileItem fileItem = (FileItem)iterator.next();
if(fileItem.isFormField()){ //是否是表單提交域,可以分區是否上傳的附件
String name = fileItem.getFieldName(); //input標籤的name
String value = fileItem.getString(); //input表單的value
if("uploadName".equals(name)){
showFileName = value;
}
}else{
String fieldName = fileItem.getFieldName(); //表單提交過來的file input標籤中name的屬性值
String fileName = fileItem.getName(); //file input上傳的文件名
String contentType = fileItem.getContentType(); //獲得上傳文件的類型
long size = fileItem.getSize(); //上傳文件的笑答
String filePath = UPLOAD_PATH + showFileName + fileName.substring(fileName.lastIndexOf("."));
//org.apache.commons.fileupload.FileItem 提供write方法寫入磁盤中
//fileItem.write(new File(filePath));
//使用字節流讀取二進制格式的附件傳給文件流 然後 寫入磁盤
OutputStream outputStream = new FileOutputStream(new File(UPLOAD_PATH,showFileName + fileName.substring(fileName.lastIndexOf("."))));//這裏傳遞父親的文件夾路徑和當前文件的名稱
InputStream inputStream = fileItem.getInputStream();
int length = 0;
byte[] buf = new byte[1024];
while((length = inputStream.read(buf)) != -1){ //首先根據傳遞的字節數組將讀取的字節的數量返回,在判斷是否讀取的空
System.out.println(buf);
outputStream.write(buf, 0, length);
}
inputStream.close();
outputStream.close();
//成功之後 簡單輸出一下
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("上傳成功!上傳文件爲:"+fileName+"<br/>保存的地址爲"+filePath+ "!!");
out.close();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
上傳的效果是一樣的。輸出每次讀取的byte數組。
基本上,使用servlet 上傳附件的功能就已經完成了。
四、多附件上傳
如果有需求需要上傳多個附件,那麼現在這個demo同樣有效果,只需要稍作修改就好了。
upload.jsp需要增加一個選擇附件的控件,代碼 如下:
<%@page import="java.io.File"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Use servlet upload file</title>
</head>
<body>
<form name="uploadForm" method="post" action="servlet/uploadFileServlet" enctype="multipart/form-data">
附件名稱:<input type="text" name="uploadName" value=""/><br/>
選擇附件:<input type="file" name="uploadFile"/><br/>
附件名稱:<input type="text" name="uploadName1" value=""/><br/>
選擇附件:<input type="file" name="uploadFile2"/><br/>
<input type="submit" value="上傳"/>
</form>
</body>
</html>
servlet.java 邏輯其實不用改,因爲我們是迭代所有FileItem對象的集合,所以所有的附件和附件信息全都保存在這個集合中,簡單提煉了下代碼,具體如下:
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//創建一個磁盤文件的工廠,然後將它 傳遞到servletFileUplaod的實例中
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
try {
//根據request對象獲取所有的文件集合,這裏包括input標籤輸入的值也屬於FileInput
List<FileItem> fileItemList = servletFileUpload.parseRequest(request);
Iterator iterator = fileItemList.iterator();
String showFileName = "";
String showFileName1 = "";
//如果附件地址不存在 就創建一下
File uploadPath = new File(UPLOAD_PATH);
if(!uploadPath.exists()){
uploadPath.mkdir();
}
while(iterator.hasNext()){
FileItem fileItem = (FileItem)iterator.next();
if(fileItem.isFormField()){ //是否是表單提交域,可以分區是否上傳的附件
String name = fileItem.getFieldName(); //input標籤的name
String value = fileItem.getString(); //input表單的value
showFileName = value; //這裏注意好出場順序,不然就亂套了
}else{
String fieldName = fileItem.getFieldName(); //表單提交過來的file input標籤中name的屬性值
String fileName = fileItem.getName(); //file input上傳的文件名
String contentType = fileItem.getContentType(); //獲得上傳文件的類型
long size = fileItem.getSize(); //上傳文件的笑答
String filePath = UPLOAD_PATH + showFileName + fileName.substring(fileName.lastIndexOf("."));
//org.apache.commons.fileupload.FileItem 提供write方法寫入磁盤中
//fileItem.write(new File(filePath));
//使用字節流讀取二進制格式的附件傳給文件流 然後 寫入磁盤
OutputStream outputStream = new FileOutputStream(new File(UPLOAD_PATH,showFileName + fileName.substring(fileName.lastIndexOf("."))));//這裏傳遞父親的文件夾路徑和當前文件的名稱
InputStream inputStream = fileItem.getInputStream();
int length = 0;
byte[] buf = new byte[1024];
while((length = inputStream.read(buf)) != -1){ //首先根據傳遞的字節數組將讀取的字節的數量返回,在判斷是否讀取的空
System.out.println(buf);
outputStream.write(buf, 0, length);
}
inputStream.close();
outputStream.close();
//成功之後 簡單輸出一下
out.print("上傳成功!上傳文件爲:"+fileName+"<br/>保存的地址爲"+filePath+ "!!");
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}finally{
out.close();
}
}
五、實現下載功能
上傳功能實現完了 之後再來說說下載功能的具體實現。
如果我們把附件放在當前項目下,可以直接通過超鏈接 進行下載,但是假如附件放在我們上面例子的d:\attach 下那怎麼辦,tomcat 是不可能訪問項目以外的資源的。
所以接下來有兩種解決方案:
1、創建一個虛擬目錄
我們發佈項目的時候,也是通過在server.xml配置 實現將一個虛擬目錄發佈到tomcat服務器上,不一定這個虛擬目錄上非得有什麼WEB-INF,web.ml 等等之類的文件,這個目錄專門存放一些附件之類的同樣可以訪問。
那麼同理 在server.xml 中加上如下例子:
<Context docBase="D:\attach" path="/img" reloabable="true" />
訪問的話可以使用 localhost:8080/img/imgName 就好了。
2、採用輸入流讀取附件,然後輸出流寫入 即可完成下載功能
具體代碼如下:
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//獲取需要下載的附件的地址,然後採用輸入流InputStream讀取附件信息
String filePath = request.getParameter("filePath");
if(filePath!=null){
try{
File file = new File(filePath);
//創建輸入流
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[inputStream.available()];
//傳遞available() 有效的字節數,可以一次性讀取完畢
inputStream.read(buffer);
inputStream.close();
//清空response,設置response的Header
response.reset();
response.addHeader("Content-Disposition", "attachment;filename=" + new String(file.getName().getBytes("utf-8"),"ISO-8859-1"));
response.addHeader("Content-Length", "" + file.length());
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
outputStream.write(buffer);
outputStream.flush();
outputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
然後把上傳的輸出字符串改一下,加一個超鏈接,代碼如下:
//成功之後 簡單輸出一下
out.println("上傳成功!上傳文件爲:"+fileName+"<br/>保存的地址爲"+filePath+ "!!<a href='downloadFileServlet?filePath="+filePath+"'>下載</a><br/>");
接下來 重新部署下項目,看看結果。效果如下圖所示:
ok,下載附件功能也完成了!
六、上傳附件中遇到問題的解決方案
1、中文亂碼
在上面的例子中 附件顯示名稱和 附件的名稱都是使用的英文名稱,如果改爲中文呢? 所以果斷嘗試了一下,結果都亂碼了,當時第一時間想到的是 使用request.setCharacterEncoding(“UTF-8”); 但是沒有效果呀,這種設置字符編碼 方式只適合普通表單的post請求。
後來發現 可以這樣,在創建ServletFileUpload對象之後設置一下頭部編碼,代碼如下:
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
servletFileUpload.setHeaderEncoding("UTF-8");
再試一下,發現 附件的實際名稱沒變亂碼,但是 附件的顯示名稱還是亂碼,這需要單獨設置encoding,原先取參數都是
String value = fileItem.getString(); //input表單的value
需要傳遞encoding ,讓FileItem知道你想得到什麼格式的value,代碼如下:
String value = fileItem.getString("UTF-8"); //input表單的value
再重新部署一下,具體效果如下:
2、String類型的變量索引越界
String 類型的變量索引越界了,具體如下:
java.lang.StringIndexOutOfBoundsException: String index out of range: -1
仔細排查 發現如果上傳的附件框如果不選擇圖片,那麼Servlet還是會去按照常規獲取附件寫入磁盤,所以這裏
String filePath = UPLOAD_PATH + showFileName + fileName.substring(fileName.lastIndexOf("."));
在它上面過濾掉就好了,具體如下:
if(fileName.length()==0){
continue;
}