14.8.Spring对分段文件上传(multipart file upload)的支持

优质
小牛编辑
132浏览
2023-12-01

14.8. Spring对分段文件上传(multipart file upload)的支持

14.8.1. 介绍

Spring支持web应用中的分段文件上传。这种支持是由即插即用的MultipartResolver来实现。这些解析器都定义在org.springframework.web.multipart包里。Spring提供了现成的MultipartResolver可以支持Commons FileUpload(http://jakarta.apache.org/commons/fileupload)和 COS FileUpload(http://www.servlets.com/cos)。本章后面的部分描述了Spring是如何支持文件上传的。

通常情况下,Spring是不处理文件上传的,因为一些开发者想要自己处理它们。如果想使用Spring的这个功能,需要在web应用的上下文中添加分段文件解析器。这样,每个请求就会被检查是否包含文件上传。如果没有,这个请求就被正常的处理,否则,应用上下文中已经定义的MultipartResolver就会被调用。然后,你请求中的文件属性就会像其它属性一样被处理。

14.8.2. 使用MultipartResolver

下面的例子说明了如何使用CommonsMultipartResolver:

<bean id="multipartResolver"
   >

    <!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize" value="100000"/>
</bean>

下面这个例子使用CosMultipartResolver:

<bean id="multipartResolver">

    <!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize" value="100000"/>
</bean>

当然你需要在classpath中为分段文件解析器提供正确的jar文件。如果是CommonsMultipartResolver,你需要使用commons-fileupload.jar,如果是CosMultipartResolver,则使用cos.jar

你已经看到如何设置Spring处理文件上传请求,接下来我们看看如何使用它。当Spring的DispatcherServlet发现文件上传请求时,它会激活定义在上下文中的解析器来处理请求。这个解析器随后是将当前的HttpServletRequest封装成MultipartHttpServletRequest,后者支持分段文件上传。使用MultipartHttpServletRequest,你可以获取请求所包含的上传信息,甚至可以在控制器中获取分段文件的内容。

14.8.3. 在表单中处理分段文件上传

MultipartResolver完成分段文件解析后,这个请求就会和其它请求一样被处理。为了使用文件上传,你需要创建一个带文件上传域(upload field)的(HTML)表单,让Spring将文件绑定到你的表单上(如下所示):

<html>
    <head>
  <title>Upload a file please</title>
    </head>
    <body>
  <h1>Please upload a file</h1>
  <form method="post" action="upload.form" enctype="multipart/form-data">
<input type="file" name="file"/>
<input type="submit"/>
  </form>
    </body>
</html>

在上面这个表单里有一个input元素,这个元素的名字(“file”)和服务器端处理这个表单的bean(在下面将会提到)中类型为byte[]的属性名相同。 在这个表单里我们也声明了编码参数(enctype="multipart/form-data")以便让浏览器知道如何对这个文件上传表单进行编码(千万不要忘记这么做!)。

和其它不能自动转为字符串类型或者基本类型(primitive type)的属性一样,为了将上传的二进制数据存成bean的属性,你必须通过ServletRequestDatabinder注册一个属性编辑器。Spring中内置了几个这样的编辑器,它们可以处理文件,然后将结果存成bean的属性。比如,StringMultipartEditor可以将文件转换成一个字符串(使用用户声明的字符集)。ByteArrayMultipartEditor可以以将文件转换为byte数组。他们的功能和CustomDateEditor相似。

总而言之,为了使用表单上传文件,你需要声明一个解析器,一个控制器,再将文件上传的URL映射到控制器来处理这个请求。下面是这几个bean的声明。

<beans>
<!-- lets use the Commons-based implementation of the MultipartResolver interface -->
    <bean id="multipartResolver"
 />

    <bean id="urlMapping">
  <property name="mappings">
<value>
    /upload.form=fileUploadController
</value>
  </property>
    </bean>

    <bean id="fileUploadController">
  <property name="commandClass" value="examples.FileUploadBean"/>
  <property name="formView" value="fileuploadform"/>
  <property name="successView" value="confirmation"/>
    </bean>

</beans>

下面的代码定义了控制器和用来存放文件的那个bean。

public class FileUploadController extends SimpleFormController {

    protected ModelAndView onSubmit(
  HttpServletRequest request,
  HttpServletResponse response,
  Object command,
  BindException errors) throws ServletException, IOException {

   // cast the bean
  FileUploadBean bean = (FileUploadBean) command;

   let's see if there's content there
  byte[] file = bean.getFile();
  if (file == null) {
 // hmm, that's strange, the user did not upload anything
  }

   // well, let's do nothing with the bean for now and return
  return super.onSubmit(request, response, command, errors);
    }

    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)
  throws ServletException {
  // to actually be able to convert Multipart instance to byte[]
  // we have to register a custom editor
  binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
  // now Spring knows how to handle multipart object and convert them
    }

}

public class FileUploadBean {

    private byte[] file;

    public void setFile(byte[] file) {
  this.file = file;
    }

    public byte[] getFile() {
  return file;
    }
}

FileUploadBean用一个byte[]类型的属性来存放文件。前面已经提到过,通常控制器注册一个自定义的编辑器以便让Spring知道如何将解析器找到的multipart对象转换成bean指定的属性,但在上面的例子中,我们除了将byte数组记录下来以外,没有对这个文件进行任何操作,在实际的应用程序中你可以做任何你想做的事情(比如将文件存储在数据库中,通过电子邮件发送给某人等等)。

在下面这个例子里,上传的文件被绑定为(表单支持的)对象(form backing object)的String属性:

public class FileUploadController extends SimpleFormController {

    protected ModelAndView onSubmit(
  HttpServletRequest request,
  HttpServletResponse response,
  Object command,
  BindException errors) throws ServletException, IOException {

   // cast the bean
  FileUploadBean bean = (FileUploadBean) command;

   let's see if there's content there
  String file = bean.getFile();
  if (file == null) {
 // hmm, that's strange, the user did not upload anything
  }

   // well, let's do nothing with the bean for now and return
  return super.onSubmit(request, response, command, errors);
    }

    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)
  throws ServletException {
  // to actually be able to convert Multipart instance to a String
  // we have to register a custom editor
  binder.registerCustomEditor(String.class, new StringMultipartFileEditor());
  // now Spring knows how to handle multipart object and convert them
    }

}

public class FileUploadBean {

    private String file;

    public void setFile(String file) {
  this.file = file;
    }

    public String getFile() {
  return file;
    }
}

如果仅仅是处理一个文本文件的上传,上面这个例子的做法还是合理的。但如果上传的是一张图片, 那段代码就会出问题。

最后的解决方法就是将表单支持对象(form backing object) 的相关属性设成MultipartFile类型。 这样的话,没有类型转换的需要,我们也就不需要声明任何属性编辑器(PropertyEditor)。

public class FileUploadController extends SimpleFormController {

    protected ModelAndView onSubmit(
  HttpServletRequest request,
  HttpServletResponse response,
  Object command,
  BindException errors) throws ServletException, IOException {

   // cast the bean
  FileUploadBean bean = (FileUploadBean) command;

   let's see if there's content there
  MultipartFile file = bean.getFile();
  if (file == null) {
 // hmm, that's strange, the user did not upload anything
  }

   // well, let's do nothing with the bean for now and return
  return super.onSubmit(request, response, command, errors);
    }
}

public class FileUploadBean {

    private MultipartFile file;

    public void setFile(MultipartFile file) {
  this.file = file;
    }

    public MultipartFile getFile() {
  return file;
    }
}