在Spring MVC中使用FileUpload功能

先让我们来看一段摘自《Spring 2.5 Reference 中文版》(http://www.redsaga.com/spring_ref/2.5/spring-reference.pdf)的一段关于FileUpload的开场描述: 

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

 

是的,Spring通过配置一个分段上传解析器来完成对文件上传的解析和封装工作,那么Spring是如何完成这一工作的呢: 

 

首先,DispatcherServlet必须找到一个文件上传解析器的实例,使用这个实例来检查本次请求的HttpServletRequest是否是一个分段文件上传的Request,通过下面的Spring 源码可以看到,首先必须保证有一个MultipartResolver的实例,并且由该类的Resolver的isMultipart方法来验证,本次Request是否为文件上传的Request.如果以上条件都满足,那么Spring将其转换为一个继承自HttpServletRequest的MultipartHttpServletRequest返回,这样在你的Controller中就可以使用这个经过转换的request,从中取到MultipartFile信息。 

Java代码  

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {  

    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {  

        if (request instanceof MultipartHttpServletRequest) {  

            logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +  

                    "this typically results from an additional MultipartFilter in web.xml");  

        }  

        else {  

            return this.multipartResolver.resolveMultipart(request);  

        }  

    }  

    // If not returned before: return original request.  

    return request;  

}  

 

由以上分析可以看出,我们必须配置一个MultipartResolver,在这里我们使用支持Commons FileUpload的CommonsMultipartResolver: 

Java代码  

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:defaultEncoding="utf-8"/>  

 

而且我们可以在该Resolver中定义文件上传的最大长度: 

Java代码  

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:defaultEncoding="utf-8" p:maxUploadSize="100000"/>  

 

当用户选择的上传文件大于maxUploadSize值的时候,commons fileupload会抛出一个异常MaxUploadSizeExceededException表示用户上传的文件超出了最大限制。 

 

当然,我们可以通过Spring MVC中的ExceptionResolver来针对该异常定义一个显示错误的View,但针对有可能存在的多个文件上传Controller中都会发生文件大小超长这个异常的情况,除了我们自定义一个粒度更细的ExceptionResolver,我们还可以把上传文件合法性判断挪到用户自己的Controller中来做。而且我个人更偏向于后一种做法。 

 

除了Spring Configuration之外,我们还需要准备一个页面上传的jsp文件供View视图使用: 

Java代码  

<%@ page language="java" contentType="text/html; charset=UTF-8"  

    pageEncoding="UTF-8"%>  

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">  

  

<html>  

    <head>  

        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  

        <title>Insert title here</title>  

    </head>  

      

    <body style="text-align:left">  

        <% if(request.getAttribute("success") != null) {%>  

            Upload Successfully!!!<br/>  

        <% }%>  

        <form id="loginform" name="loginform" method="POST" enctype="multipart/form-data">  

            <table width="100%" border="0" cellspacing="0" cellpadding="0">  

                <tr>  

                    <td height="30" align="right">Choose File</td>  

                    <td align="left">  

                        <input name="imageFile" type="file"/>  

                    </td>  

                </tr>  

                <tr>  

                    <td align="center" colspan="2">  

                        <input type="submit" value="submit" name="submit" />  

                    </td>  

                </tr>  

            </table>  

        </form>  

    </body>  

</html>  

 

注意:在文件上传Form表单中,一定要将enctype设置为"multipart/form-data"因为只有这样才能使Spring知道这是一个文件上传的请求。 

 

细心的读者也许能发现Form表单中action为默认值也就是说post到和上传页面同样的URL,因此我们定义了一个Controller分别来处理这个请求的GET和POST请求。下面让我们来看看这个Controller: 

 

1.我们通过@Controller声明这个类为Spring组件,告知Spring容器在初始化的时候需要加载该类实例到Spring Context Container中。 

2.通过@RequestMapping("/sec_upload.do")将sec_upload.do的请求指向该Controller处理。 

Java代码  

@Controller  

@RequestMapping("/sec_upload.do")  

public class UploadController {  

    //...  

}  

 

3.定义一个处理GET请求的方法,该方法简单的将选择文件Form表单页展现给用户: 

Java代码  

@RequestMapping(method = RequestMethod.GET)  

    public String handleUploadShow() {  

        return "uploadView";  

    }  

 

4.定义一个处理POST请求的方法,该方法进行用户文件上传处理: 

Java代码  

@RequestMapping(method = RequestMethod.POST)  

    public String handleUploadProcess(  

            @RequestParam("imageFile") MultipartFile file, Model model)  

            throws Exception {  

        //具体的业务逻辑操作。。。  

        model.addAttribute("success", "true");  

        return "uploadView";  

    }  

 

通过@RequestParam("imageFile")注解,Spring会将request请求中的imageFile的文件信息自动绑定到MultipartFile对象。 

 

上面的Controller方法解决的文件绑定的问题,但假设我们的Form表单中除了文件选择框还有其他一些用户填写的信息,那么我们怎么处理呢?仿照上面的方法,我们可以为多个参数提供多个@RequestParam注解来完成数据绑定工作,但我们也可以通过MultipartHttpServletRequest对象来获取这些信息,因为在DispatcherServlet中Spring已经将一个普通的HttpServletRequest转换为了一个MultipartHttpServletRequest: 

Java代码  

@RequestMapping(method = RequestMethod.POST)  

public String handleAnotherUploadProcess(  

        MultipartHttpServletRequest request, Model model) throws Exception {  

    MultipartFile file = request.getFile("imageFile");  

    //request.getParameter("xxx");  

    //request.getContentType();  

    //request.getContentLength();  

    //some other processing...  

    model.addAttribute("success", "true");  

    return "uploadView";  

}  

 

 

这种方式还是需要我们不断的通过request.getParameter("xxx")方式来获得参数,了解Spring MVC的同学可能想到了,使用CommandObject绑定-回答正确。假设我们定义了一个POJO对象: 

Java代码  

public class BoUploadFile {  

    private MultipartFile imageFile;  

  

    public MultipartFile getImageFile() {  

        return imageFile;  

    }  

  

    public void setImageFile(MultipartFile imageFile) {  

        this.imageFile = imageFile;  

    }  

  

    private String name;  

  

    public String getName() {  

        return name;  

    }  

  

    public void setName(String name) {  

        this.name = name;  

    }  

}  

 

这个对象中不仅包括需要封装的上传文件信息,还包括其他一些用户输入的普通信息。那么有了这个封装对象,我们的Controller可以变成如下的样子: 

Java代码  

@RequestMapping(method = RequestMethod.POST)  

public String handleThirdUploadProcess(BoUploadFile uploadFile, Model model) throws Exception{  

    MultipartFile file = uploadFile.getImageFile();  

    //这里你可以通过uploadFile.getName()...等等获取用户输入的其他普通信息了。  

    model.addAttribute("success", "true");  

    return "uploadView";  

}  

 

 

5.自定义一个文件验证类,来验证文件的合法性。 

Java代码  

/** 

 * 用户文件上传验证类 

 *  

 * @author Jacky Lau created at 2008-8-27 

 * @since 1.0 

 * @version 1.0 

 */  

public class MultipartFileValidator {  

  

    private final static long MAX_SIZE = 1024 * 1024;  

  

    /** 

     * 文件大小上限 

     */  

    private long maxSize = MAX_SIZE;  

  

    /** 

     * 可接受的文件content-type 

     */  

    private String[] allowedContentTypes;  

  

    @PostConstruct  

    public void afterPropertiesSet() {  

        Assert  

                .notEmpty(allowedContentTypes,  

                        "The content types allowed to be uploaded must contain one at least!");  

    }  

  

    /** 

     * 验证上传文件是否合法,如果不合法那么会抛出异常 

     *  

     * @param file 

     *            用户上传的文件封装类 

     */  

    public void validate(MultipartFile file) {  

        Assert.notNull(file, "The multipart file is null!");  

        if (file.getSize() > maxSize)  

            throw new FileOutOfMaxLengthException("error.upload.outmaxlen",  

                    new Object[] { maxSize },  

                    "The file uploaded is out of max file size!");  

        if (!ArrayUtils.contains(allowedContentTypes, file.getContentType()))  

            throw new ContentTypeNotSupportException("error.upload.content.notsupported", null,  

                    "The content type '"+file .getContentType()+"' is not a valid content type !");  

    }  

  

    /** 

     * 设置文件上传大小上限 

     *  

     * @param maxSize 

     *            文件上传大小上限 

     */  

    public void setMaxSize(long maxSize) {  

        this.maxSize = maxSize;  

    }  

  

    /** 

     * 设置可接受的文件content-type数组 

     *  

     * @param allowedContentTypes 

     *            可接受的文件content-type数组 

     */  

    public void setAllowedContentTypes(String[] allowedContentTypes) {  

        this.allowedContentTypes = allowedContentTypes;  

    }  

  

}  

 

 

这样我们可以通过这个validator判断上传的文件是否超出了最大限制,文件格式是否正确等判断。我们可以通过配置文件配置该验证器,在这里为了方便起见在类中我用以下方式来初始化该验证器: 

Java代码  

private MultipartFileValidator validator;  

  

@PostConstruct  

public void init() {  

    validator = new MultipartFileValidator();  

    validator.setAllowedContentTypes(new String[] { "image/jpeg",  

            "image/pjpeg" });  

}  

 

 

至此,我们已经完成了文件上传的开发,可以看出这和普通的Controller开发没有任何区别,简单而且灵活。以下是该Controller的全部代码: 

 

Java代码  

@Controller  

@RequestMapping("/sec_upload.do")  

public class UploadController {  

  

    private MultipartFileValidator validator;  

  

    @PostConstruct  

    public void init() {  

        validator = new MultipartFileValidator();  

        validator.setAllowedContentTypes(new String[] { "image/jpeg",  

                "image/pjpeg" });  

    }  

  

    @RequestMapping(method = RequestMethod.GET)  

    public String handleUploadShow() {  

        return "uploadView";  

    }  

  

    @RequestMapping(method = RequestMethod.POST)  

    public String handleUploadProcess(  

            @RequestParam("imageFile") MultipartFile file, Model model)  

            throws Exception {  

        validator.validate(file);  

        String path = "d:\\temp\\ftp\\" + file.getOriginalFilename();  

        String resizePath = "d:\\temp\\ftp\\resize\\"  

                + file.getOriginalFilename();  

        FileHelper.save(path, file.getBytes());  

        if (ImageHelper.isJpg(ImageHelper.getImageType(path)))  

            ImageHelper.resizeJPG(path, resizePath, 120, 118);  

        model.addAttribute("success", "true");  

        return "uploadView";  

    }  

}  

posted @ 2012-09-01 15:29  daveztong  阅读(1760)  评论(0编辑  收藏  举报