16.7. Multipart文件上传支持

Spring Portlet MVC和Web MVC一样,也支持multipart来处理portlet中的文件上传。 插件式的PortletMultipartResolver提供了对multipart的支持, 它在org.springframework.web.portlet.multipart包里。 Spring提供了PortletMultipartResolver来和 Commons FileUpload 一起使用。余下的篇幅会介绍文件上传的支持。

缺省情况下,Spring Portlet是不会处理multipart的,如果开发人员需要处理multipart, 就必须在web应用的context里添加一个multipart解析器,然后, DispatcherPortlet会在每个请求里检查是否带有multipart。 如果没找到,请求会继续,如果找到了multipart,在context中声明的 PortletMultipartResolver会被调用。接着, 在请求里的multipart属性会和其它的属性一样被处理。

16.7.1. 使用PortletMultipartResolver

下面的例子介绍了 CommonsPortletMultipartResolver的使用:

<bean id="portletMultipartResolver"
    class="org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver">

    <!-- 一个属性;以byte为单位的最大文件长度 -->
    <property name="maxUploadSize" value="100000"/>
</bean>

当然为了使multipart解析器能够工作,必须把合适的jar放到类路径里。对于 CommonsMultipartResolver来说,需要 commons-fileupload.jar。注意,必须使用至少1.1 版本的Commons FileUpload,因为以前的版本不支持JSR-168应用。

现在你已经看到如何设置Portlet MVC来处理multipart请求,接下来我们 讨论它的使用。当DispatcherPortlet检测到 multipart时,它会激活在context里声明的解析器,并把请求交给它。然后解析器 把当前的ActionRequest放到支持文件上传的 MultipartActionRequest中。通过 MultipartActionRequest,可以得到 请求包含的multipart信息,并且在控制器里访问multipart文件。

注意,不能从RenderRequest接收到multipart 文件,而只能从ActionRequest里。

16.7.2. 处理表单里的文件上传

PortletMultipartResolver处理完后, 请求会继续被处理。你需要创建一个带有上传字段的表单来使用它(见下面),Spring会 把文件绑定在你的表单上(支持对象)。为了让用户上传文件,必须创建一个 (JSP/HTML)的表单:

<h1>Please upload a file</h1>
<form method="post" action="<portlet:actionURL/>" enctype="multipart/form-data">
    <input type="file" name="file"/>
    <input type="submit"/>
</form>

如你所见,我们在bean的属性后面创建名为“File”的字段 用来容纳byte[]。加上了编码属性(enctype="multipart/form-data"), 让浏览器知道怎样来编码multipart字段(不要忘记!)。

和其它那些不会自动转化为字符串或原始类型的属性一样,为了把二进制数据放到对象 里,必须注册一个使用PortletRequestDataBinder 的自定义的编辑器。现成有好几个编辑器可以用来处理文件并把结果放到对象上。 StringMultipartFileEditor能够把文件转换成字符串 (使用用户定义的字符集),ByteArrayMultipartFileEditor 能够把文件转换成字节数据。他们的功能和 CustomDateEditor一样。

所以,为了能够使用表单来上传文件,需要声明解析器,映射到处理这个bean的控制器的 映射以及控制器。

<bean id="portletMultipartResolver"
      class="org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver"/>

<bean id="portletModeHandlerMapping"
      class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
    <property name="portletModeMap">
        <map>
            <entry key="view" value-ref="fileUploadController"/>
        </map>
    </property>
</bean>

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

接着,创建控制器以及实际容纳这个文件属性的类。

public class FileUploadController extends SimpleFormController {

    public void onSubmitAction(
        ActionRequest request,
        ActionResponse response,
        Object command,
        BindException errors)
        throws Exception {

        // 类型转换bean
        FileUploadBean bean = (FileUploadBean) command;

        // 是否有内容
        byte[] file = bean.getFile();
        if (file == null) {
            // 奇怪,用户什么都没有上传
        }

        // do something with the file here
    }

    protected void initBinder(
            PortletRequest request, PortletRequestDataBinder binder)
        throws Exception {
        // to actually be able to convert Multipart instance to byte[]
        // we have to register a custom editor
        binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
        // 现在Spring知道如何来处理和转换multipart对象
    }
}

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[]属性进行任何操作,但实际上,你可以做任 何操作(把它存到数据库里,把它电邮出去,或其它)

下面是一个例子,文件直接绑定在的一个(表单支持)对象上的字符串类型属性上面:

public class FileUploadController extends SimpleFormController {

    public void onSubmitAction(
        ActionRequest request,
        ActionResponse response,
        Object command,
        BindException errors) throws Exception {

        // 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
        }

        // do something with the file here
    }

    protected void initBinder(
        PortletRequest request, PortletRequestDataBinder binder) throws Exception {

        // 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 objects and convert
    }
}

public class FileUploadBean {

    private String file;

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

    public String getFile() {
        return file;
    }
}

当然,最后的例子在上传文本文件时才有(逻辑上的)意义(在上传图像文件时, 它不会工作)。

第三个(也是最后一个)选项是,什么情况下需要直接绑定在(表单支持)对象的 MultipartFile属性上。在以下的情况, 不需要注册自定义的属性编辑器,因为不需要类型转换。

public class FileUploadController extends SimpleFormController {

    public void onSubmitAction(
        ActionRequest request,
        ActionResponse response,
        Object command,
        BindException errors) throws Exception {

        // 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
        }

        // do something with the file here
    }
}

public class FileUploadBean {

    private MultipartFile file;

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

    public MultipartFile getFile() {
        return file;
    }
}