SpringMVC文件上傳下載(單文件、多文件)

  • 2020 年 7 月 30 日
  • 筆記

前言

大家好,我是bigsai,今天我們學習Springmvc的文件上傳下載。

文件上傳和下載是互聯網web應用非常重要的組成部分,它是資訊交互傳輸的重要渠道之一。你可能經常在網頁上傳下載文件,你可能也曾沉浸於互聯網技術的神秘,而本篇就為你解開它神秘的面紗。

本文已收錄在公眾號:bigsai中(微信搜索bigsai),,同時也收錄在博學谷中(以關卡方式展開歡迎體驗),更多好玩精彩等待給您分享,歡迎關注!

案例分析

你肯定會問:通過本篇可能能夠學到什麼?

那我很負責任的告訴你,通過本篇文章,你能夠掌握Springmvc文件上傳(單文件、多文件)文件下載知識和內容的使用,並能夠根據這些實現一些基本的案例。

核心思路拆解

你可能會問:,這麼一個完整的項目是如何分工運行?

不急不急,我來告訴你,其實這麼一個文件上傳下載的項目,它是一個b-s結構的web項目,涉及到前端和服務端,從宏觀來看它是這樣的一個結構:

在這裡插入圖片描述

但是從文件上傳、下載兩個功能來看它們之間又是有所區別的,文件上傳的主要核心是用戶上傳的文件服務端接受存儲

在這裡插入圖片描述

而文件下載更重要的部分是用戶請求之後服務端給用戶返回二進位文件

在這裡插入圖片描述

所以文件上傳和文件下載的項目大體結構相似,只是各個部分在具體實現上有差別,我們需要更多關注下文件上傳和下載服務端的實現和區別。

案例所涉及知識點

在本案例中,用到了以下知識點:

html頁面form表單:

在前端無論是html還是jsp等模板引擎編寫上傳的頁面時候。<form> 標籤就意為一個(文件)上傳的表單。

  • 表單能夠包含若干 input 標籤,而input標籤又有不同類型比如文本欄位、複選框、單選框、文件等等。
  • 我們通常使用表單編寫若干標籤代表我們想要向服務端發送的數據,然後通過標籤的按鈕將數據請求提交至服務端。
  • 表單的method表示請求的類型(一般為post),action表示需要請求的url地址,enctype表示傳輸數據類型。

Springmvc:

案例的文件上傳和下載基於Springmvc,而我們在Springboot項目中整合Springmvc。

  • 本案例使用Springmvc作為項目mvc架構的框架,將模型(Model),視圖(View),控制器(Controller)分離降低項目的耦合性。
  • 本案例使用Springmvc的MultipartFile介面和ResponseEntity介面實現文件上傳和下載。

創建Springmvc項目

Springmvc為一個mvc架構的web框架,創建Springmvc項目的方式有很多,你可以選擇直接通過IDEA創建Springmvc項目,也可以通過Maven方式創建web項目然後添加Springmvc的依賴,但這兩種方式有太多的配置還需要配置tomcat,在效果一致的情況下咱們盡量簡化一些開發配置類的工作,所以不採用以上兩種方式創建項目。

而Springboot簡化了Spring項目的開發,開箱即用,且內嵌tomcat,所以咱們選擇創建基於Springboot且整合Springmvc的項目方便快捷,更能直奔主題進行操作。

項目創建

首先,打開IDEA,創建項目,選擇Spring Initializr類型初始化點擊next。
在這裡插入圖片描述
然後你會得到一個選擇項目名和一些配置的頁面,我們在Group中填寫com,而Artifact咱們填寫fileupload。點擊next。
在這裡插入圖片描述
接著在選擇對應模組依賴的時候,選擇Spring web 模組,此模組就是包含Springmvc的web模組
在這裡插入圖片描述
接著選擇需要創建項目的地址目錄,點擊next在這裡插入圖片描述

這樣你就可以得到一個完整的包含web模組(Springmvc)的Springboot項目,就可以在裡面編寫咱們項目的程式碼。
在這裡插入圖片描述

目錄介紹

上面創建完的基於Springboot的Springmvc項目,默認有若干文件和文件夾,不同文件和文件夾有著不同的職責:

  • java:用來編寫java服務端相關程式碼,例如Controller,Dao,Service等。
  • application.properties: 編寫一些項目和框架的配置內容以及和第三方框架整合配置等
  • static: 靜態資源目錄,用來存放html、JavaScript、圖片等資源。
  • teamplates:用來編寫Thymeleaf等模板引擎,這裡不使用
  • pom.xml:編寫maven項目jar包資源依賴。如果項目需要引入其他依賴或者修改打包方式可以進行修改。

對於web項目的文件上傳,需要進行一定配置以滿足我們的使用需求,我們在application.propertis進行以下配置:

# 允許項目中文件上傳
spring.servlet.multipart.enabled=true
# 上傳文件的臨時目錄 (一般情況下不用特意修改)
#spring.servlet.multipart.location=
# 上傳文件最大為 1M (默認值 1M 根據自身業務自行控制即可)
spring.servlet.multipart.max-file-size=104857600
# 上傳請求最大為 10M(默認值10M 根據自身業務自行控制即可)
spring.servlet.multipart.max-request-size=104857600
# 文件大小閾值,當大於這個閾值時將寫入到磁碟,否則存在記憶體中,(默認值0 一般情況下不用特意修改)
spring.servlet.multipart.file-size-threshold=0
# 判斷是否要延遲解析文件(相當於懶載入,一般情況下不用特意修改)
spring.servlet.multipart.resolve-lazily=false

當然,你對文件有大小等其他要求可以對配置進行自行更改。到這裡帶有Springmvc環境的項目已經創建完成啦,剩下的只需要編寫前端、服務端程式碼運行測試即可。

單文件上傳

下面請跟我實戰 Springmvc單文件上傳。一個完整的文件上傳項目有兩部分組成:前端介面和服務端程式。

前端設計

對於前端頁面,我們使用你一定熟悉的html而不選用其他模板引擎。而form表單是html文件上傳的核心組件,你在使用前需要了解它的一些屬性。

表單的enctype屬性
上面說了一個表單文件傳輸的大體流程,你也知道表單有個至關重要的屬性:enctype。而entype值通常有以下三種:

  • application/x-www-form-urlencoded:默認編碼方式,在發送前編碼所有字元(默認)使用url編碼方式,和get請求有些相似。但這種方式如果發送大量二進位數據效率會比較低。
  • multipart/form-data:不對字元編碼。在使用包含文件上傳控制項的表單時,必須使用該值。通常用來向服務端發送二進位數據,而我們的文件也主要以二進位的方式進行傳輸。
  • text/plain:空格轉換為 “+” 加號,但不對特殊字元編碼。

所以本單文件上傳案例中,需要注意以下事項:

  • 表單的enctype要為multipart/form-data類型,表示二進位傳輸。
  • 在一個form表單內定義一個input為file屬性的標籤,代表文件上傳。
  • form表單的method需要為post。
  • enctype要為multipart/form-data類型,表示二進位傳輸。

前端頁面的規則了解之後你在static下創建一個index1.html文件,裡面具體的程式碼內容為:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>單文件上傳</title>
</head>
<body>
<h2>單文件上傳</h2>
<form  action="onfile" method="post" enctype='multipart/form-data'>
    <input type="file" name="file" ><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

其中action="onfile"代表的為請求地址為onfile,這裡都在項目內所以用相對地址即可,如果上傳為其他介面也可填寫對應的絕對地址。這樣前端頁面就編寫完成,我們還需要編寫文件上傳對應服務端模組。

服務端設計

服務端主要負責文件接受,在前端看起來實現文件上傳的頁面很簡單,但實際上在服務端的文件接收並沒有那麼容易,因為傳過來的不光光是這一個(或多個)二進位文件,還附帶一些頭資訊、文件名等等數據。打包過來的數據如果是文本數據解析可能還好,但是二進位文件數據一旦出現一點錯誤可能得到的整個文件都是損壞的。並且在咱們java web技術棧中文件上傳也是有一定發展的歷史的:

servlet文件上傳(3.0以前)
在servlet3.0以前,文件上傳在服務端接收需要使用request.getInputStream()獲取表單的二進位數據,但是在解析時候非常麻煩和複雜,對於文件上傳這麼一個很基本的模組在接收的時候可能要耗費很大的成本和精力去解決它,並且很多初級攻城獅很可能由於對io模組陌生無法實現上傳文件在服務端的接收。
在這裡插入圖片描述

所以這個時候一些具有責任感的公司、組織就把它們的解析方法貢獻出來供大家使用,大家不需了解傳輸文件底層內容,這些開源的處理方式中,最流行的當屬apache旗下開源的commons-fileuploadcommons-io,把兩個jar包加入到項目中你直接了解下這個api如何使用即可。有了這兩個jar包,簡單學習它的api,你就可以在普通的web項目中很容易的實現上傳文件的功能!
在這裡插入圖片描述
servlet3.0以後
隨著servlet版本更新,設計者可能看到javaweb開發中原生api對文件上傳支援不太友好的問題,所以在api對文件上傳的支援得到優化,簡化了Java Web的開發。在servlet3.0中主要增加Part這個類用來讀取文件數據和資訊,在Part中直接將傳輸文件的名稱、頭資訊、二進位文件分割開,通過簡單的api就可以實現文件上傳的功能。不需要再添加外部jar包

Springmvc文件上傳
文件上傳和下載是web開發常用模組,而Springmvc作為一款優秀的web框架,對很多模組和內容進行更高度的封裝和集成,而這麼常用的文件上傳肯定是少不了的,所以Springmvc的文件上傳基於apache旗下開源的commons-fileuploadcommons-io包。將其進行二次集成和封裝至Springmvc,將方法和內容封裝至MultipartFile介面讓我們使用起來更加方便,能夠容易實現單文件、多文件上傳。

對於上述各種文件上傳服務端實現方式,大致可以通過下圖展示:
在這裡插入圖片描述
通過上圖你就可明白Springmvc文件上傳實現的原理,那麼下面你就可以進行大顯身手啦!Springmvc處理上傳文件很簡單,我們需要在java目錄下創建一個uploadController.java創建這麼一個控制器,在上面加上@Controller註解。在Controller中編寫以下程式碼:

 @PostMapping("onfile")
 @ResponseBody
 public String onfile(MultipartFile file) throws IOException {
     File file1 =new File("F:/fileupload/"+file.getOriginalFilename());//創建file對象
     if(!file1.exists())
            file1.createNewFile();//在磁碟創建該文件
     file.transferTo(file1);//將接受的文件存儲
     return "sucucess";
 }

其中:

  • @PostMapping(“onfile”) 的意思為該請求方式為post,且請求的url在項目中的相對地址為onfile
  • @ResponseBody指不返回web頁面,而是返回字元串或json字元串,在這裡我們直接用一個成功單詞代表跳轉後的介面。
  • public String onfile(MultipartFile file) 函數名不重複就行,而MultipartFile file就是Springmvc封裝的一個處理文件的介面,其中參數名(這裡是file)要和前端介面文件名相同(input type=”file”,name=”file”中的name),通過這個介面你可以更容易的對文件進行各種操作,而本案例就是將上傳的文件保存到本地F盤。

對於函數中的幾行核心程式碼各司其職,除了注釋的解釋外,大致的流程可以參考如下圖:
在這裡插入圖片描述

運行測試

這樣啟動項目,在瀏覽器輸入//localhost:8080/index1.html,選擇文件上傳,點擊上傳之後就可以在本地看到上傳的文件啦。
在這裡插入圖片描述
至此,單文件上傳就完成啦,單文件上傳前端需要注意的就是form表單的method類型以及 enctype參數,而服務端也只需要用MultipartFile 介面就可以很容易的對文件進行接受。

第四關 多文件上傳

上面講的是單文件上傳,很多時候你可能遇到的需求不光光是單文件上傳。就比如你一定熟悉這個頁面:
在這裡插入圖片描述

如上你可以看到,這麼一次文件上傳不止一個圖片,並且數量也不確定,但都屬於同一名稱和集合的內容。這就是多文件上傳。對於這種情況無論在前端還是服務端也是很容易處理的。

前端設計

我們這裡實現一個多張圖片的上傳,首先在static目錄下創建一個index2.html的頁面。裡面的具體內容為:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>多文件上傳</title>
</head>
<body>
<h2>同一類別多個文件上傳</h2>
<form name="onfile"  action="onfiles2" method="post" enctype="multipart/form-data">
    圖片:
    <input type="file" name="img"><br>
    <input type="file" name="img"><br>
    <input type="file" name="img"><br>
    <input type="file" name="img"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

這樣前端頁面就編寫完成,其中action要改為onfiles2,也就是待會要在服務端編寫的介面。還有注意的這些input 所有type為file代指類型為文件,而name均為img意思是上傳一組名稱為img圖片的集合。

服務端設計

而在我們服務端,其實用MultipartFile[]數組就可以對這樣的多文件進行接收,我們在controller中編寫以下程式碼:

@PostMapping("onfiles2")
@ResponseBody
public String onfiles2(MultipartFile img[]) throws IOException {
   for(int i=0;i<img.length;i++)
   {
       if(!img[i].isEmpty())//文件不空
       {
           File imgfile =new File("F:/fileupload/"+img[i].getOriginalFilename());
           imgfile.createNewFile();
           img[i].transferTo(imgfile);
           logger.info(img[i].getOriginalFilename());
       }
   }
    return "sucucess";
}

這個處理方式和前面的很相似,只不過是需要遍歷MultipartFile[]對每個文件進行接收處理,當然文件為空的時候不進行處理。

運行測試

這樣打開瀏覽器輸入://localhost:8080/index2.html,上傳文件測試效果:
在這裡插入圖片描述

這樣一組類似相冊上傳的功能就完成啦,當然實際開發中的文件上傳的要求肯定比這個要求嚴格很多,可能對文件的格式、大小都有一定的要求,這就要求你在前端和服務端都要對文件的後綴名、大小等資訊進行校驗,以達到自己場景化的需求。

文件下載

文件下載估計你在日常生活中會經常遇到,而你下載的其實就是服務端(伺服器)的資源,對於文件類型有多種多樣的,瀏覽器也能夠識別很多種資源,事實上你現在訪問的這個網頁也是服務端的html文件、圖片文件等資源,只不過這些資源瀏覽器能夠顯示而不會保存到本地。

直接訪問資源VS下載資源

如果直接訪問的資源是瀏覽器所不能識別解析的,例如doc、zip等類型文件,那訪問的時候會默認下載到本地。而當你在Springmvc中使用下載功能時,無論是什麼資源都以下載的形式返回給客戶端。這種區別可以參考下圖:
在這裡插入圖片描述

在文件下載方面的實現,servlet本身也是實現文件下載的,不過使用起來有點繁瑣。其原理就是往HttpServletResponse response 的輸出流寫位元組內容。而我們Springmvc對文件下載也做了封裝,將下載功能封裝至ResponseEntity類中,我們在使用的時候也很方便。下面就來實戰文件下載的功能。

首先,我們在F盤建立download文件夾,在裡面添加對應文件,這個文件夾我們作為服務端的資源。
在這裡插入圖片描述

前端設計

我們在創建一個文件下載的前端頁面,在static目錄下創建index3.html,頁面的具體內容為:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Springmvc文件下載</title>
</head>
<body>
<h2>Springmvc文件下載</h2>
個人照片<a href="/download/個人照片.png">個人照片.png</a><br>
個人簡歷<a href="/download/個人簡歷.pdf">個人簡歷.pdf</a>
</body>
</html>

其中href是下載的超鏈接,download是下載的介面名,而鏈接最後面部分則是下載資源名稱。

服務端設計

文件下載的原理就是服務端向客戶端返回二進位流和資訊,而Springmvc通過ResponseEntity完成。我們在controller中編寫以下介面實現下載的功能:

@GetMapping("download/{filename}")
public ResponseEntity<byte[]>download(@PathVariable String filename) throws IOException {
    //下載文件的路徑(這裡絕對路徑)
    String filepath= "F:/download/"+filename;
    File file =new File(filepath);
    //創建位元組輸入流,這裡不實用Buffer類
    InputStream in = new FileInputStream(file);
    //available:獲取輸入流所讀取的文件的最大位元組數
    byte[] body = new byte[in.available()];
    //把位元組讀取到數組中
    in.read(body);
    //設置請求頭
    MultiValueMap<String, String> headers = new HttpHeaders();
    headers.add("Content-Disposition", "attchement;filename=" + file.getName());
    //設置響應狀態
    HttpStatus statusCode = HttpStatus.OK;
    in.close();
    ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(body, headers, statusCode);
    return entity;//返回
}

這樣就是實現了文件下載功能,如果用傳統servlet的方式下載文件可能需要在HttpServletResponse response中設置各種資訊,而使用Springmvc的ResponseEntity只需要將文件二進位主體、頭資訊以及狀態碼設置好即可進行文件下載,在易用性和簡潔上更勝一籌。

運行測試

打開瀏覽器輸入://localhost:8080/index3.html;點擊需要下載的文件,就實現了文件下載的功能,運行情況圖如下:
在這裡插入圖片描述

此時你就遇到了一個文件下載非常常見的問題:中文文件名錯誤顯示。這個解決方案也很容易解決,只需將Content-Disposition內容後面的文件名進行url編碼即可,具體程式碼為(替換上面對於部分):

headers.add("Content-Disposition", "attchement;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));

這樣重啟程式,刷新頁面再次點擊下載的鏈接,你就會發現文件被成功的下載了:
在這裡插入圖片描述

總結與拓展

至此,Springmvc的單文件上傳、多文件上傳以及文件下載你已經全部掌握了,是不是滿滿的成就感想去實現一個自己的小網站並把相關內容放進去?不過Springmvc文件上傳下載雖然簡單,但你依然需要掌握其原理,學好java中的io文件傳輸,這樣在各種場景的文件傳輸任務中方能勝任。

總結

前面所講文件上傳,前端就是form表單用<input type="file">表示客戶端要上傳文件,而服務端主要使用MultipartFile或者MultipartFile[]分別接收單個文件和多個文件。而在存儲到本地也僅僅需要在本地磁碟創建對應文件然後MultipartFile調用transferTo()方法即可將上傳的文件儲存。

而文件下載的前端需要一個請求的url鏈接,服務端需要編寫這個鏈接對應的介面。通過一些名稱找到文件在本地真實的位置通過ResponseEntity即可將二進位文件返回給客戶達到文件下載的功能。而ResponseEntity使用也很簡單在創建時候只需要傳入二進位主體、頭和狀態碼即可成功返回,而這些Springmvc已進行了很好封裝你可以直接使用。

而無論是文件上傳、多文件上傳還是文件下載,一個完整的案例大致都需要這樣一個過程:

  • 構思需求和頁面大體樣式
  • 編寫前端html頁面
  • 編寫服務端響應的請求
  • 啟動程式運行測試

在其中過程如果有問題可以根據編譯器的錯誤提示、運行時的錯誤日誌找到根源進行修正,這樣完整的案例就可以成功完成啦!

案例拓展

你是否覺得自己掌握的可以了?那好,咱們拓展提升一下,我給你來一個需求:單文件和多文件混合上傳

假設小明需要實現一個文件上傳功能,小明需要上傳一份簡歷和若干份照片(小於3)。這個項目該如何設計呢?它的計劃頁面可能是這樣的:

在這裡插入圖片描述

我覺得聰明的你一定不會被難住,對於前端介面的html有什麼想法呢?

對於種類來說有簡歷和照片兩種文件,對於它們各自來說,簡歷只有一份,而照片可能有多份。

那麼咱們的html頁面可以這樣設計:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>個人資訊上傳</title>
</head>
<body>
<h2>個人資訊上傳</h2>
<form name="onfile"  action="infoupload" method="post" enctype="multipart/form-data">
    姓名:<input type="text" name="name" ><br>
    年齡:<input type="text" name="age"> <br>
    圖片:<input type="file" name="img">
         <input type="file" name="img">
    簡歷:<input type="file" name="resume"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

這裡面和前面的單文件上傳不同的是有多個input標籤,此外action也要改成infoupload意思是你需要寫這麼一個介面來處理這個文件上傳的內容。在controller中編寫以下程式碼:

 private  static Logger logger= LoggerFactory.getLogger(uploadController.class);
    @PostMapping("infoupload")
    @ResponseBody
    public String onfile(String name,String age, MultipartFile img[],MultipartFile resume) throws IOException {
        logger.info(name);//日誌中列印傳輸的name
        logger.info(age);
        //接收img[]
        for(int i=0;i<img.length;i++)
        {
            if(!img[i].isEmpty())//文件不空
            {
                File imgfile =new File("F:/fileupload/"+img[i].getOriginalFilename());
                imgfile.createNewFile();
                img[i].transferTo(imgfile);
            }
        }
         //接收resume
        File resumefile =new File("F:/fileupload/"+resume.getOriginalFilename());
        //在磁碟中創建文件,此時文件存在但沒有內容
        resumefile.createNewFile();
        //將接受的文件複製到創建的文件中
        resume.transferTo(resumefile);
        return "sucucess";
    }

這個理解起來其實也很容易,這個和上面主要的區別就是函數中的多參數,其實每一個參數都是要和前端頁面的form表單input標籤的內容對應(名稱一致)。form表單中的file類型在Springmvc的controller中就是對應MultipartFile類型,form表單中的text類型對應controller中的String類型。如果上傳單個文件,在服務端就用MultipartFile類型參數接收,如果多文件就用MultipartFile[]進行接收。上傳類型和個數根據你自己的需求設計定義。

我們啟動程式打開瀏覽器輸入//localhost:8080/index4.html選擇文件進行上傳,然後在本地你可以看到文件成功被保存。

在這裡插入圖片描述

至此,本篇的內容就結束了,本文主要簡單講解了Springmvc中文件上傳、多文件上傳、文件下載的實現,現在你已熟練掌握。青山不改,綠水長流,我們下期再見!下課!

部落客微信公眾號:bigsai 一個等你關注而朝思夜暮的程式猿。
在這裡插入圖片描述