筆記34 Spring MVC的高級技術——處理multipart形式的數據


一、需求介紹:

Spittr應用在兩個地方需要文件上傳。當新用戶注冊應用的時候,我 們希望他們能夠上傳一張圖片,從而與他們的個人信息相關聯。當用 戶提交新的Spittle時,除了文本消息以外,他們可能還會上傳一 張照片。

二、multipart介紹

一般表單提交所形成的請求結果是很簡單的,就是以“&”符分割的多 個name-value對。但是當上傳二進制數據時,如上傳圖片,就出現問題。與之不同的是,multipart格式的數據會將一個表單拆分為多個 部分(part),每個部分對應一個輸入域。在一般的表單輸入域中, 它所對應的部分中會放置文本型數據,但是如果上傳文件的話,它所 對應的部分可以是二進制,下面展現了multipart的請求體:

在這個multipart的請求中,我們可以看到profilePicture部分與其 他部分明顯不同。除了其他內容以外,它還有自己的ContentType頭,表明它是一個JPEG圖片。盡管不一定那么明顯,但profilePicture部分的請求體是二進制數據,而不是簡單的文 本。

盡管multipart請求看起來很復雜,但在Spring MVC中處理它們卻很容 易。在編寫控制器方法處理文件上傳之前,必須要配置一個 multipart解析器,通過它來告訴DispatcherServlet該如何讀取 multipart請求。

三、 配置multipart解析器 

DispatcherServlet並沒有實現任何解析multipart請求數據的功 能。它將該任務委托給了Spring中MultipartResolver策略接口的 實現,通過這個實現類來解析multipart請求中的內容。從Spring 3.1開 始,Spring內置了兩個MultipartResolver的實現可以選擇: 

    • CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart請求;
    • StandardServletMultipartResolver:依賴於Servlet 3.0 對multipart請求的支持(始於Spring 3.1)。

一般來講,在這兩者之 間,StandardServletMultipartResolver可能會是優選的方 案。它使用Servlet所提供的功能支持,並不需要依賴任何其他的項 目。

1.使用Servlet 3.0解析multipart請求 

兼容Servlet 3.0的StandardServletMultipartResolver沒有構 造器參數,也沒有要設置的屬性。這樣,在Spring應用上下文中,將 其聲明為bean就會非常簡單,如下所示:

1     @Bean
2     public MultipartResolver multipartResolver() throws IOException {
3         return new StandardServletMultipartResolver();
4     }

如果 我們想要限制用戶上傳文件的大小,該怎么實現?如果我們想要指定 文件在上傳時,臨時寫入目錄在什么位置的話,該如何實現?因為沒有屬性和構造器參數,StandardServletMultipartResolver 的功能看起來似乎有些受限。

其實並不是這樣,我們是有辦法配 置StandardServletMultipartResolver的限制條件的。只不 過不是在Spring中配置StandardServletMultipartResolver, 而是要在Servlet中指定multipart的配置。至少,我們必須要指定在文 件上傳的過程中,所寫入的臨時文件路徑。如果不設定這個最基本配 置的話,StandardServlet-MultipartResolver就無法正常工 作。具體來講,我們必須要在web.xml或Servlet初始化類中,將 multipart的具體細節作為DispatcherServlet配置的一部分。 

因為我們一直使用的配置是DispatcherServlet的Servlet初始化類繼承了 Abstract AnnotationConfigDispatcherServletInitializer ,所以就不會直接創建DispatcherServlet實例並將其注冊到Servlet上下 文中。這樣的話,將不會有對Dynamic Servlet registration的引用供我 們使用了。但是,我們可以通過重載customizeRegistration() 方法(它會得到一個Dynamic作為參數)來配置multipart的具體細 節:

1     @Override
2     protected void customizeRegistration(Dynamic registration) {
3         // TODO Auto-generated method stub
4         // super.customizeRegistration(registration);
5         registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
6     }

代碼中使用的是只有一個參數的 MultipartConfigElement構造器,這個參數指定的是文件系統 中的一個絕對目錄,上傳文件將會臨時寫入該目錄中。但是,還可以通過其他的構造器來限制上傳文件的大小。除了臨時路徑的位 置,其他的構造器所能接受的參數如下:

    • 上傳文件的最大容量(以字節為單位)。默認是沒有限制的。
    • 整個multipart請求的最大容量(以字節為單位),不會關心有多 少個part以及每個part的大小。默認是沒有限制的。
    • 在上傳的過程中,如果文件大小達到了一個指定最大容量(以字 節為單位),將會寫入到臨時文件路徑中。默認值為0,也就是 所有上傳的文件都會寫入到磁盤上。

例如,假設我們想限制文件的大小不超過2MB,整個請求不超過 4MB,而且所有的文件都要寫到磁盤中。下面的代碼使 用MultipartConfigElement設置了這些臨界值:

1     @Override
2     protected void customizeRegistration(Dynamic registration) {
3         // TODO Auto-generated method stub
4         registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads", 2097152, 4194304, 0));
5     }

如果我們使用更為傳統的web.xml來配 置MultipartConfigElement的話,那么可以使用<servlet>中 的<multipart-config>元素,如下所示:

 

2.配置Jakarta Commons FileUpload multipart解析器 

通常來講,StandardServletMultipartResolver會是最佳的 選擇,但是如果我們需要將應用部署到非Servlet 3.0的容器中,那么 就得需要替代的方案。

Spring內置了 CommonsMultipartResolver,可以作 為StandardServletMultipartResolver的替代方案。 

將CommonsMultipartResolver聲明為Spring bean的最簡單方式如 下:

1 @Bean
2      public MultipartResolver multipartResolver() throws IOException {
3      return new CommonsMultipartResolver();
4     }

與StandardServletMultipartResolver有所不 同,CommonsMultipart-Resolver不會強制要求設置臨時文件路 徑。默認情況下,這個路徑就是Servlet容器的臨時目錄。不過,通過 設置uploadTempDir屬性,我們可以將其指定為一個不同的位置:

 

實際上,我們可以按照相同的方式指定其他的multipart上傳細節,也 就是設置CommonsMultipartResolver的屬性。例如,如下的配 置就等價於我們在前文通過MultipartConfigElement所配置的 StandardServletMultipartResolver:

在這里,我們將最大的文件容量設置為2MB,最大的內存大小設置為 0字節。這兩個屬性直接對應於MultipartConfigElement的第二 個和第四個構造器參數,表明不能上傳超過2MB的文件,並且不管文 件的大小如何,所有的文件都會寫到磁盤中。但是 與MultipartConfigElement有所不同,我們無法設定multipart請 求整體的最大容量。

四、處理multipart請求 

現在已經在Spring中(或Servlet容器中)配置好了對mutipart請求的處 理,那么接下來我們就可以編寫控制器方法來接收上傳的文件。要實 現這一點,最常見的方式就是在某個控制器方法參數上添 加@RequestPart注解。 

假設我們允許用戶在注冊Spittr應用的時候上傳一張圖片,那么我們 需要修改表單,以允許用戶選擇要上傳的圖片,同時還需要修 改SpitterController 中的processRegistration()方法來接 收上傳的圖片。

<form>標簽現在將enctype屬性設置為multipart/formdata,這會告訴瀏覽器以multipart數據的形式提交表單,而不是以表 單數據的形式進行提交。在multipart中,每個輸入域都會對應一個 part。 

除了注冊表單中已有的輸入域,我們還添加了一個新的<input> 域,其type為file。這能夠讓用戶選擇要上傳的圖片文件。accept 屬性用來將文件類型限制為JPEG、PNG以及GIF圖片。根據其name 屬性,圖片數據將會發送到multipart請求中的profilePicture part 之中。

我們需要修改processRegistration()方法,使其能夠接 受上傳的圖片。其中一種方式是添加byte數組參數,並為其添 加@RequestPart注解。如下示例:

當注冊表單提交的時候,profilePicture屬性將會給定一個byte 數組,這個數組中包含了請求中對應part的數據(通過 @RequestPart指定)。如果用戶提交表單的時候沒有選擇文件, 那么這個數組會是空(而不是null)。獲取到圖片數據后,processRegistration()方法剩下的任務就是將文件保存到 某個位置。

1.接受MultipartFile (如何將byte數組轉換為可存儲的文件。)

使用上傳文件的原始byte比較簡單但是功能有限。因此,Spring還提 供了MultipartFile接口,它為處理multipart數據提供了內容更為 豐富的對象。如下的程序清單展現了MultipartFile接口的概況。

MultipartFile提供了獲取上傳文件byte的方式, 但是它所提供的功能並不僅限於此,還能獲得原始的文件名、大小以 及內容類型。它還提供了一個InputStream,用來將文件數據以流 的方式進行讀取。

MultipartFile還提供了一個便利的transferTo()方 法,它能夠將上傳的文件寫入到文件系統中。

2.效果

                                          

                      

3.具體代碼實現

以增加留言為例子,每一條留言可以添加一張圖片。

在WebContent目錄下創建image文件夾用來存放上傳的圖片。

<1>修改SpittleControllers類中的addSpittle方法。 

 1     public String addSpittle(HttpServletRequest request, PubSpittle pubSpittle)
 2             throws IllegalStateException, IOException {
 3 
 4         MultipartFile profilePicture = pubSpittle.getSpittlePicture();
 5         String id = Long.toString(GenerateUnID.next());
 6         if (request.getParameter("spittlePicture") != null) {
 7             profilePicture.transferTo(new File(url, id + ".jpg"));
 8             pubSpittle.setSpittlePictureString(id);
 9         } else {
10             pubSpittle.setSpittlePictureString("spitter_logo_50");
11         }
12         spittleRepository.save(pubSpittle);
13         return "redirect:/spittles?username=" + username;
14     }

如果用戶不上傳圖片的話,會默認顯示應用logo,圖片名字是spitter_logo_50 

<2>PubSpittle.java要做相應的修改 

 1 package myspittr.pubspittle;
 2 
 3 import org.springframework.web.multipart.MultipartFile;
 4 
 5 public class PubSpittle {
 6     private String message;
 7     private String title;
 8     private String username;
 9     private MultipartFile spittlePicture;
10     private String spittlePictureString;
11 
12     public String getMessage() {
13         return message;
14     }
15 
16     public void setMessage(String message) {
17         this.message = message;
18     }
19 
20     public String getTitle() {
21         return title;
22     }
23 
24     public void setTitle(String title) {
25         this.title = title;
26     }
27 
28     public String getUsername() {
29         return username;
30     }
31 
32     public void setUsername(String username) {
33         this.username = username;
34     }
35 
36     public MultipartFile getSpittlePicture() {
37         return spittlePicture;
38     }
39 
40     public void setSpittlePicture(MultipartFile spittlePicture) {
41         this.spittlePicture = spittlePicture;
42     }
43 
44     public String getSpittlePictureString() {
45         return spittlePictureString;
46     }
47 
48     public void setSpittlePictureString(String spittlePictureString) {
49         this.spittlePictureString = spittlePictureString;
50     }
51 }

<3>圖片的名字也存儲到數據庫中所對應留言的條目中,所以Spittle.java也要做相應的修改。

 1 package myspittr.spittle;
 2 
 3 import org.apache.commons.lang3.builder.EqualsBuilder;
 4 import org.apache.commons.lang3.builder.HashCodeBuilder;
 5 
 6 public class Spittle {
 7     private Long id;
 8     private String message;
 9     private String title;
10     private String time;
11     private String username;
12     private String spittlePicture;
13 
14     public Spittle() {
15     }
16 
17     public Spittle(Long id, String message, String title, String time, String username, String spittlePicture) {
18         this.id = id;
19         this.message = message;
20         this.time = time;
21         this.title = title;
22         this.username = username;
23         this.spittlePicture = spittlePicture;
24     }
25 
26     public String getUsername() {
27         return username;
28     }
29 
30     public long getId() {
31         return id;
32     }
33 
34     public String getMessage() {
35         return message;
36     }
37 
38     public String getTime() {
39         return time;
40     }
41 
42     public String getTitle() {
43         return title;
44     }
45 
46     public String getSpittlePicture() {
47         return spittlePicture;
48     }
49 
50     @Override
51     public boolean equals(Object that) {
52         return EqualsBuilder.reflectionEquals(this, that, "id", "time");
53     }
54 
55     @Override
56     public int hashCode() {
57         return HashCodeBuilder.reflectionHashCode(this, "id", "time");
58     }
59 }

<4>顯示上傳的圖片

在spittles.jsp頁面合適的位置中添加如下代碼:

1 <img src="<s:url value="/image/${spittle.spittlePicture}.jpg" /> " width="200" height="299">

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM