天天看點

Servlet筆記十(檔案上傳和下載下傳)

本欄部落格目錄

​​Serlvet筆記一(Servlet基礎)​​​​Servlet筆記二(請求和響應)​​​​Servlet筆記三(會話及其會話技術)​​​​Servlet筆記四(JSP技術)​​​​Servlet筆記五(EL表達式和JSTL)​​​​Servlet筆記六(Servlet 進階)​​​​Servlet筆記七(JDBC)​​​​Servlet筆記八(資料庫連接配接池與DBUtils工具)​​​​Servlet筆記九(JSP 開發模型)​​ Servlet筆記十(檔案上傳和下載下傳)

文章目錄

  • ​​如何實作檔案上傳​​
  • ​​檔案上傳的相關 API​​
  • ​​FileItem 接口​​
  • ​​DiskFileItemFactory 類​​
  • ​​ServletFileUpload 類​​
  • ​​實作檔案上傳​​
  • ​​檔案下載下傳​​
  • ​​實作檔案下載下傳​​

很多Web應用都為使用者提供了檔案上傳和下載下傳的功能,例如,圖檔的上傳與下載下傳、郵件附件的上傳與下載下傳等。

如何實作檔案上傳

要實作 Web 開發中的檔案上傳功能,通常需完成兩步操作:

一 是在 Web 頁面中添加上傳輸入項;

二 是在 Servlet 中讀取上傳檔案的資料,并儲存到本地硬碟中。

由于大多數檔案的上傳都是通過表單的形式送出給伺服器的,是以,要想在程式中實作檔案上傳的功能,首先要建立一個用于送出上傳檔案的表單頁面。在頁面中,需要使用 ​

​<input type="file">​

​ 标簽在 Web 頁面中添加檔案上傳輸入項。

<input type=“file”>标簽的使用需要注意以下兩點。

● 必須要設定 input 輸入項的 name 屬性,否則浏覽器将不會發送上傳檔案的資料。

● 必須将表單頁面的 method 屬性設定為 post 方式,enctype 屬性設定為“multipart/form-data” 類型。

示例代碼如下。

<%--指定表單資料的enctype屬性以及送出方式--%>
<form enctype="multipart/ form-data" method="post">
  <%--指定标記的類型和檔案域的名稱--%>
  選擇上傳檔案: <input type="file" name= "myfile" /><br />      

       當浏覽器通過表單送出上傳檔案時,由于檔案資料都附帶在 HTTP 請求消息體中,并且采用 MIME 類型(多用途網際網路郵件擴充類型)進行描述,在背景使用 request 對象提供的 getInputStream() 方法可以讀取到用戶端送出過來的資料。但由于使用者可能會同時上傳多個檔案,而在 Servlet 端直接讀取上傳資料,并分别解析出相應的檔案資料是一項非常麻煩的工作。為了友善處理使用者上傳資料,Apache 組織提供了一個開源元件 Commons-FileUpload。該元件可以友善地将 “multipart/form-data” 類型請求中的各種表單域解析出來,并實作一個或多個檔案的上傳,同時也可以限制上傳檔案的大小等内容。其性能十分優異,使用極其簡單。

       需要注意的是,在使用 FileUpload 元件時,要導入 commons-fileupload.jar 和 commons-io.jar 兩個 JAR 包,這兩個 JAR 包可以去 Apache 官網 "http://commons.apache.org/” 下載下傳(進入該網址頁面後,在 Apache Commons Proper 下方表格的 Components 列中找到 FileUpload 和 IO,單擊進入後即可找到下載下傳連結)。

       FileUpload 元件是通過 Servlet 來實作檔案上傳功能的。其工作流程如圖所示。

Servlet筆記十(檔案上傳和下載下傳)

       從圖中可以看出,實作檔案的上傳會涉及到幾個陌生類,這些類都是 Apache 元件上傳檔案的核心類。

檔案上傳的相關 API

FileItem 接口

Fileltem 接口在 Commons-FileUpload 元件中被實作,其主要用于封裝單個表單字段元素的資料,一個表單字段元素對應一個 Fileltem 對象。Commons-FileUpload 元件在處理檔案上傳的過程中,将每一個表單域(包括普通的文本表單域和檔案域)封裝在一個 Fileltem 對象中。

在此将 Fileltem 接口的實作類稱為 Fileltem 類, Fileltem 類實作了 Serializable 接口,是以,支援序列化操作。在 Fileltem 類中定義了許多擷取表單字段元素的方法,具體如下。

  • ( 1 ) boolean isFormField()方法

    isFormField() 方法用于判斷 Fileltem 類對象封裝的資料是一個普通文本表單字段,還是一個檔案表單字段,如果是普通文本表單字段,則傳回 true,否則傳回 false。

  • ( 2 ) String getName()方法

    getName() 方法用于獲得檔案上傳字段中的檔案名。如果 Fileltem 類對象對應的是普通文本表單字段,getName() 方法将傳回null; 否則,隻要浏覽器将檔案的字段資訊傳遞給伺服器,getName() 方法就會傳回一個字元串類型的結果,如:“/home/xxx/Sunset.jpg”。

    需要注意的是,通過不同浏覽器上傳的檔案,擷取到的完整路徑和名稱都是不一樣的。例如,使用者使用 IE 浏覽器上傳檔案,擷取到的就是完整的路徑“home/xxx/Sunset.jpg”;如果使用其他浏覽器,比如火狐,擷取到的僅僅是檔案名,沒有路徑,如 “Sunset.jpg"。

  • ( 3 ) String getFieldName()方法

    getFieldName() 方法用于獲得表單字段元素描述頭的 name 屬性值,也是表單标簽name 屬性的值。例如“name=file1” 中的“file1"。

  • ( 4 ) void write(File file)方法

    write() 方法用于将 Fileltem 對象中儲存的主體内容儲存到某個指定的檔案中。如果Fileltem對象中的主體内容是儲存在某個臨時檔案中,那麼該方法順利完成後,臨時檔案有可能會被清除。另外,該方法也可将普通表單字段内容寫入到一個檔案中,但它主要用于将上傳的檔案内容儲存到本地檔案系統中。

  • ( 5 ) String getString()方法

    getString() 方法用于将 Fileltem 對象中儲存的資料流内容以一個字元串傳回,它有兩個重載的定義形式。

    ● public String getString()

    ● public String getString(java.lang.String encoding)

    在上面重載的兩個方法中,前者使用預設的字元集編碼将主體内容轉換成字元串,後者使用參數指定的字元集編碼将主體内容轉換成字元串。需要注意的是,如果在讀取普通表單字段元素内容時出現中文亂碼現象,請調用第 2 個 getString() 方法,并為之傳遞正确的字元集編碼名稱。

  • ( 6 ) String getContentType()方法

    getContentType() 方法用于獲得上傳檔案的類型,即表單字段元素描述頭屬性

    “Content-Type”的值,如“image/jpeg"。 如果 Fileltem 類對象對應的是普通表單字段,該方法将傳回 null。

  • ( 7 ) boolean isInMemory()方法

    isInMemory() 方法用來判斷 Fileltem 對象封裝的資料内容是存儲在記憶體中,還是存儲在臨時檔案中,如果存儲在記憶體中則傳回 true,否則傳回 false。

  • ( 8 ) void delete()方法

    delete() 方法用來清空 Fileltem 類對象中存放的主體内容,如果主體内容被儲存在臨時檔案中,delete() 方法将删除該臨時檔案。需要注意的是,盡管 Fileltem 對象被垃圾收集器收集時會自動清除臨時檔案,但應該及時調用 delete() 方法清除臨時檔案,進而釋放系統存儲資源,以防系統出現異常,導緻臨時檔案被永久地儲存在硬碟中。

  • ( 9 ) InputStream getInputStream()方法

    getInputStream() 方法以流的形式傳回上傳檔案的資料内容。

  • ( 10 ) long getSize()方法

    getSize() 方法傳回該上傳檔案的大小(以位元組為機關)。

DiskFileItemFactory 類

DiskFileltemFactory 類用于将請求消息實體中的每一個檔案封裝成單獨的 Fileltem 對象。如果上傳的檔案比較小,将直接儲存在記憶體中,如果上傳的檔案比較大,則會以臨時檔案的形式,儲存在磁盤的臨時檔案夾中。預設情況下,檔案儲存在記憶體還是硬碟臨時檔案夾的臨界值是10240,即 10KB。DiskFileltemFactory 類中包含兩個構造方法,如表所示。

Servlet筆記十(檔案上傳和下載下傳)

表列舉了 DiskFileltemFactory 類的兩個構造方法。其中,第 2 個構造方法需要傳遞兩個參數,參數 sizeThreshold 代表檔案儲存在記憶體還是磁盤臨時檔案夾的臨界值,參數 repository 表示臨時檔案的存儲路徑。

接下來,針對 DiskFileltemFactory 類的常用方法進行詳細講解,具體如下所示。

  • ( 1 ) Fileltem createltem(String fieldName, String contentType, boolean isFormField, String fileName)方法

    該方法用于将請求消息實體建立成 Fileltem 類型的執行個體對象。需要注意的是,該方法是 FileUpload 元件在解析請求時内部自動調用,無需我們管理。

  • ( 2 ) setSize Threshold(int size Threshold) 和 getSizeThreshold() 方法

    setSizeThreshold() 方法用于設定是否将上傳檔案以臨時檔案的形式儲存在磁盤的臨界值。當 Apache 檔案上傳元件解析上傳的資料時,需要将解析後的資料臨時儲存,以便後續對資料進一步處理。由于 Java 虛拟機可使用的記憶體空間是有限的,是以,需要根據上傳檔案的大小決定檔案的儲存位置。例如,一個 800MB 的檔案是無法在記憶體中臨時儲存的,這時,Apache 檔案上傳元件可以采用臨時檔案的方式來儲存這些資料。但是,如果上傳的檔案很小,隻有

    600KB,顯然将其儲存在記憶體中是比較好的選擇。另外,對應的 getSizeThreshold() 方法用來擷取此臨界值。

  • ( 3 ) setRepository(File repository) 和 getRepository() 方法

    如果上傳檔案的大小大于 setSizeThreshold() 方法設定的臨界值,這時,可以采用 setRepository() 方法,将上傳的檔案以臨時檔案的形式儲存在指定的目錄下。在預設情況下,用的是系統預設的臨時檔案路徑,可以通過以下方式擷取。

    ​​

    ​System.getProperty ("java.io.tmpdir")​

    ​ 另外,對應的 getRepository() 方法用于擷取臨時檔案。

ServletFileUpload 類

ServletFileUpload 類是 Apache 元件處理檔案上傳的核心進階類,通過使用

parseRequest(HttpServletRequest) 方法可以将 HTML 中每個表單送出的資料封裝成一個 Fileltem 對象,然後以 List 清單的形式傳回。ServletFileUpload 類中包含兩個構造方法,如表所示。.

Servlet筆記十(檔案上傳和下載下傳)

表列舉了 ServletFileUpload 類的兩個構造方法。由于在檔案上傳過程中,FileltemFactory 類必須設定,是以,在使用第一個構造方法建立 ServletFileUpload 對象時,首先需要在解析請求之前調用 setFileltemFactory() 方法設定 fileltemFactory 屬性。

ServletFileUpload類的常用方法如下所示。

  • ( 1 ) setSizeMax(long sizeMax) 和 getSizeMax()方法

    setSizeMax()方法繼承自 FileUploadBase 類,用于設定請求消息實體内容(即所有上傳資料)的最大尺寸限制,以防止用戶端惡意上傳超大檔案來浪費伺服器端的存儲空間。其中,參數 sizeMax 是以位元組為機關。另外,對應的 getSizeMax() 方法用于讀取請求消息實體内容所允許的最大值。

  • ( 2 ) setFileSizeMax(long fileSizeMax) 和 getFileSizeMax()方法

    setFileSizeMax() 方法繼承自 FileUploadBase 類,用于設定單個上傳檔案的最大尺寸限制,以防止用戶端惡意上傳超大檔案來浪費伺服器端的存儲空間。其中,參數 fileSizeMax 是以位元組為機關。另外,對應的 getFileSizeMax() 方法用于擷取單個上傳檔案所允許的最大值。

  • ( 3 ) parseRequest(javax.servlet.http.HttpServletRequest req)

    parseRequest() 方法是 ServletFileUpload 類的重要方法,它是對 HTTP 請求消息體内容進行解析的入口。它解析出 Form 表單中的每個字段的資料,并将它們分别包裝成獨立的 Fileltem 對象,然後将這些 Fileltem 對象加入進一個 List 類型的集合對象中傳回。

  • ( 4 ) getltemlterator(HttpServletRequest request)

    gettemlterator() 方法和 parseRequest() 方法的作用基本相同,但 getltemlterator() 方法傳回的是一個疊代器,該疊代器中儲存的不是 Fileltem 對象,而是 FileltemStream 對象,如果希望進一步提高性能,可以采用 getltemlterator() 方法,直接獲得每一個檔案項的資料輸入流,作底層處理;如果性能不是問題,希望代碼簡單,則采用 parseRequest() 方法即可。

  • ( 5 ) isMultipartContent(HttpServletRequest req)

    isMultipartContent() 方法用于判斷請求消息中的内容是否是 “multipart/form-data" 類型,如果是,則傳回 true,否則傳回 false。需要注意的是,isMultipartContent() 方法是一個靜态方法,不用建立 ServletFileUpload 類的執行個體對象即可被調用。

  • ( 6 ) getFileltemFactory() 和 setFileltemFactory(FileltemFactory factory)

    這兩個方法繼承自 FileUpload 類,分别用于讀取和設定 fileltemFactory 屬性。

  • ( 7 ) setHeaderEncoding(String encoding) 方法和 getHeaderEncoding() 方法

    這兩個方法繼承自 FileUploadBase 類,用于設定和讀取字元編碼。需要注意的是,如果沒有使用 setHeaderEncoding() 設定字元編碼,則 getHeaderEncoding() 方法傳回null,上傳元件會采用 HttpServletRequest 設定的字元編碼。但是,如果 HttpServletRequest 的字元編碼也為 null,這時,上傳元件将采用系統預設的字元編碼。擷取系統預設字元編碼的方式如下所示。

    ​​

    ​System.getProperty ("file. encoding"));​

實作檔案上傳

項目結構

Servlet筆記十(檔案上傳和下載下傳)

1. 建立上傳頁面

​form.jsp​

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <title>upfile</title>
</head>
<body>
    <form action="/UploadServlet" method="post" enctype="multipart/form-data">
        <table>
            <tr>
                <td>上 傳 者:</td>
                <td>
                    <input type="text" name="name"/>
                </td>
            </tr>
            <tr>
                <td>上傳檔案</td>
                <td><input type="file" name="myfile"/></td>
            </tr>
            <tr>
                <td colspan="2"><input type="submit" value="上傳"></td>
            </tr>
        </table>
    </form>
</body>
</html>      

2. 建立 Servlet

​UploadServlet.java​

package com.xxx.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.UUID;

@WebServlet("/UploadServlet")
public class UploadServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.setContentType("text/html;charset=utf-8");

        try {

            // 建立 DiskFileItemFactory 工廠對象
            DiskFileItemFactory factory = new DiskFileItemFactory();

            // 設定檔案緩存目錄,如果該目錄不存在則新建立一個
            File f = new File("/home/sweetheart/Desktop/myjava/temp/");
            if(!f.exists()){
                f.mkdirs();
            }

            // 設定檔案的緩存目錄
            factory.setRepository(f);
            // 建立 ServletFileUpload 對象
            ServletFileUpload fileUpload = new ServletFileUpload(factory);
            // 設定字元編碼
            fileUpload.setHeaderEncoding("utf-8");
            // 解析 request, 得到上傳檔案的 FileItem 對象
            List<FileItem> fileitems = fileUpload.parseRequest(req);
            // 擷取字元流
            PrintWriter out = resp.getWriter();
            // 周遊集合
            for (FileItem fileItem: fileitems){
                // 判斷是否為普通字段
                if(fileItem.isFormField()){
                    // 擷取字段名及值
                    String name = fileItem.getFieldName();
                    if("name".equals(name)){
                        if(!fileItem.getString().equals("")){
                            String value = fileItem.getString("utf-8");
                            out.println("上傳者:" + value + "<br />");
                        }else{
                            System.out.println("上傳者的資訊是空的");
                        }
                    }

                }else{ // 上傳的是檔案

                    // 擷取檔案名
                    String filename = fileItem.getName();

                    if(filename != null && !"".equals(filename)){

                        out.println("上傳的檔案名稱為:" + filename + "<br/>");
                        // 截取檔案名,将帶有用戶端檔案路徑的字首部分進行删除
                        filename = filename.substring(filename.lastIndexOf("/") + 1);
                        // 設定檔案名唯一
                        filename = UUID.randomUUID().toString() + "_" + filename;
                        // 在服務建立同名的檔案
                        String webPath = "/upload/";
                        // 将伺服器中檔案夾路徑與檔案名組合成完整的伺服器路徑(絕對路徑)
                        String filepath = getServletContext().getRealPath(webPath + filename);
                        // 建立檔案
                        File file = new File(filepath);
                        file.getParentFile().mkdirs();
                        file.createNewFile();

                        // 擷取上傳檔案的輸入流
                        InputStream ins = fileItem.getInputStream();
                        // 使用 FileOutputStream 打開伺服器端上傳檔案
                        FileOutputStream fos = new FileOutputStream(file);

                        // 使用流寫入檔案中
                        byte[] buffer = new byte[1024]; // 1k 位元組
                        int len;
                        // 循環寫入
                        while ((len = ins.read(buffer)) > 0){
                            fos.write(buffer, 0, len);
                        }
                        // 關閉流
                        fos.close();
                        ins.close();
                        // 删除臨時檔案
                        fileItem.delete();

                        out.println("上傳檔案成功!<br />");

                    }else{
                        System.out.println("沒有上傳");
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}      

3. 運作結果

Servlet筆記十(檔案上傳和下載下傳)
Servlet筆記十(檔案上傳和下載下傳)

存放的臨時檔案被删除

Servlet筆記十(檔案上傳和下載下傳)
Servlet筆記十(檔案上傳和下載下傳)

檔案下載下傳

實作檔案下載下傳功能比較簡單,通常情況下,不需要使用第三方元件實作,而是直接使用 Servlet 類和輸入/輸出流實作。與通路伺服器檔案不同的是,要實作檔案的下載下傳,不僅需要指定檔案的路徑,還需要在 HTTP 協定中設定兩個響應消息頭:

// 設定接收程式處理資料的方式
Content-Disposition: attachment;filename=
// 設定實體内容的 MIME 類型
Content-Type: application/x-msdownload      

浏覽器通常會直接處理響應的實體内容,需要在 HTTP 響應消息中設定兩個響應消息頭字段,用來指定接收程式處理資料内容的方式為下載下傳方式。當單擊 [下載下傳] 超連結時,系統将請求送出到對應的 Servlet。在該 Servlet 中,首先擷取下載下傳檔案的位址,并根據該位址建立檔案位元組輸入流,然後通過該流讀取下載下傳的檔案内容,最後将讀取的内容通過輸出流寫到目标檔案中。

實作檔案下載下傳

項目結構

Servlet筆記十(檔案上傳和下載下傳)

1. 建立下載下傳頁面

​download.jsp​

<%@ page import="java.net.URLEncoder" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
</head>
<body>
    <!--解決中文編碼-->
    <a href="./DownloadServlet?filename=<%=URLEncoder.encode("圖檔.jpg", "utf-8")%>">檔案下載下傳</a>
</body>
</html>      

2. 建立 Servlet

​DownloadServlet.java​

package com.xxx.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

@WebServlet("/DownloadServlet")
public class DownloadServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 設定編碼
        resp.setContentType("text/html;charset=utf-8");
        // 擷取下載下傳檔案名
        String filename = req.getParameter("filename");

        // 解決中文編碼問題
        filename = new String(filename.getBytes(StandardCharsets.UTF_8));

        // 下載下傳檔案所在檔案夾
        String folder = "/download/";
        
        // 通知浏覽器以下載下傳方式打開; encode編碼器 解決中文編碼
        resp.addHeader("Content-Type", "application/octet-stream");
        resp.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "utf-8"));

        // 擷取輸入流
        InputStream ins = getServletContext().getResourceAsStream(folder+filename);
        // 擷取 response 對象的輸出流
        OutputStream out = resp.getOutputStream();
        // 1k位元組
        byte[] buffer = new byte[1024];
        int len;
        // 循環輸出
        while ((len=ins.read(buffer)) != -1){
            out.write(buffer, 0, len);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}      

​EncodingFilter.java​

package com.xxx.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/DownloadServlet")
public class EncodingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        System.out.println("destroy...");
    }
}      

3. 建立下載下傳目錄及檔案

Servlet筆記十(檔案上傳和下載下傳)

4. 運作結果

Servlet筆記十(檔案上傳和下載下傳)