File upload con Spring MVC Framework

De ChuWiki

Para hacer un file upload con spring mvc framework necesitamos los siguientes jar en nuestro classpath

  • spring-webmvc-3.1.2.RELEASE.jar (Spring Framework MVC con todas sus dependencias)
  • commons-fileupload-1.2.2.jar (Apache commons file upload)
  • commons-io-2.4.jar (Apache commons io)

En formato maven,

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>

En el fichero de configuración de spring applicationContext.xml necesitamos poner el siguiente bean

    <bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

En el formulario web donde queremos que aparezca la parte de subir el fichero ponemos lo siguiente

<form:form method="post" commandName="subeFichero"
    enctype="multipart/form-data">
    <form:input path="comentario" /><form:errors path="comentario"/><br/>
    <form:input path="multipartFile" type="file" /><form:errors path="multipartFile"/><br/>
    <input type="submit" value="Subir">
</form:form>

Simplemente es un formulario POST con dos campos, uno normalito de texto (comentario) y el otro que es el de subir el fichero. Es importante que enctype sea "multipart/form-data". El commandName referencia al controlador Spring que se va a encargar de tratar el fichero (y el resto del formulario).

El bean (Fichero) que se usará entre el formulario y el controlador tendrá los siguientes atributos (omitimos setters y getters), coindiciendo con los "path" que hemos puesto en el formulario.

public class Fichero {
   private String comentario;  
   private MultipartFile multipartFile;
}

En nuestro Validator del controlador, podemos verificar que el usuario ha elegido un fichero de esta forma

    public void validate(Object obj, Errors errors) {
        Fichero fi = (Instalable) obj;
        if (fi == null) {
            errors.reject("error.fichero.null");
        } else {
            ValidationUtils.rejectIfEmptyOrWhitespace(errors, "comentario",
                    "error.fichero.comentario.null");
            ValidationUtils.rejectIfEmpty(errors, "multipartFile",
                    "error.fichero.multipartfile.null");
            if (null != fi.getMultipartFile()) {
                if (fi.getMultipartFile().isEmpty()) {
                    errors.rejectValue("multipartFile",
                            "error.fichero.multipartfile.null");
                }
            }
        }
    }

donde las etiquetas "error.fichero.null", "error.fichero.comentario.null" y "error.fichero.multipartfile.null" son las claves para los textos de error que se suelen guardar en un fichero de properties, permitiendo así mostrar el texto en diferentes idiomas.

Finalmente, en nuestro controlador, debemos copiar el fichero en algún path del servidor de nuestro gusto, almacenarlo en base de datos o lo que sea. El código puede ser como este

    public ModelAndView onSubmit(Object comando) throws Exception {
        Fichero fichero= (Fichero) comando;

        // Hacer lo que sea oportuno con el comentario.
        String comentario = fichero.getComentario(); 

        // Mover el fichero a un path adecuado
        MultipartFile multipart = fichero.getMultipartFile();
        try {
             File path = new File("un path");
             multipart.transferTo(new File(path, multipart.getOriginalFilename()));
        } catch (Exception e) {
             logger.error("Error al copiar fichero", e);
             throw e;
        }

        return new ModelAndView(new RedirectView(getSuccessView()));
    }

    protected Object formBackingObject(HttpServletRequest request)
         throws ServletException {
        Fichero fichero= new Fichero();
        fichero.setComentario("un comentario");
        return fichero;
    }

Si en vez de mover el fichero a un path adecuado queremos hacer algo con él directamente, la clase MultipartFile tiene métodos getBytes() y getInputStream() que nos permitirá ir leyendo el fichero y haciendo lo que corresponda con él.