zoukankan      html  css  js  c++  java
  • 笔记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">

     

     

  • 相关阅读:
    SAS学习笔记27 卡方检验
    SAS学习笔记26 方差分析
    SAS学习笔记25 t检验(单个样本t检验、配对样本t检验、两个独立样本t检验及方差不齐时的t'检验)
    SAS学习笔记23 线性回归、多元回归
    HTML canvas画布
    HTML 新全局特性
    MYSQL数据库学习(五)如何自定义函数
    什么是单机结构?什么是集群?什么是分布式?
    MYSQL数据库学习(四)如何备份还原数据库
    MYSQL数据库学习(三)关于DML操作
  • 原文地址:https://www.cnblogs.com/lyj-gyq/p/9010579.html
Copyright © 2011-2022 走看看