La carga de archivos es una tarea muy común en cualquier aplicación web. Anteriormente, hemos visto cómo subir archivos en Servlet y Struts2 File Uploading. Hoy aprenderemos sobre la carga de archivos en Spring, específicamente sobre la carga de archivos en Spring MVC para archivos individuales y múltiples.
Carga de archivos en Spring MVC
El framework Spring MVC brinda soporte para cargar archivos mediante la integración de la API Apache Commons FileUpload. El proceso para cargar archivos es muy sencillo y requiere configuraciones simples. Crearemos un proyecto Spring MVC simple en STS que se verá como la imagen a continuación. La mayor parte es código de plantilla generado por la herramienta STS; nos enfocaremos en los cambios necesarios para utilizar la integración de carga de archivos de Spring.
Dependencias Maven para Apache Commons FileUpload
En primer lugar, necesitamos agregar las dependencias de Apache Commons FileUpload en nuestro archivo pom.xml, para que los archivos jar necesarios formen parte de la aplicación web. A continuación se muestra el fragmento de dependencia de mi archivo pom.xml.
<!-- Apache Commons FileUpload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- Apache Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
Vistas de Formulario de Carga de Archivos de Spring
Crearemos dos páginas JSP para permitir la carga de archivos única y múltiple en la aplicación web de Spring. Código de vista upload.jsp:
<%@ taglib uri="https://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Upload File Request Page</title>
</head>
<body>
<form method="POST" action="uploadFile" enctype="multipart/form-data">
File to upload: <input type="file" name="file"><br />
Name: <input type="text" name="name"><br /> <br />
<input type="submit" value="Upload"> Press here to upload the file!
</form>
</body>
</html>
Código de vista uploadMultiple.jsp:
<%@ taglib uri="https://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Upload Multiple File Request Page</title>
</head>
<body>
<form method="POST" action="uploadMultipleFile" enctype="multipart/form-data">
File1 to upload: <input type="file" name="file"><br />
Name1: <input type="text" name="name"><br /> <br />
File2 to upload: <input type="file" name="file"><br />
Name2: <input type="text" name="name"><br /> <br />
<input type="submit" value="Upload"> Press here to upload the file!
</form>
</body>
</html>
Observa que estos archivos son simples archivos HTML, no estoy utilizando etiquetas JSP o de Spring para evitar complejidades. El punto importante a tener en cuenta es que el atributo enctype debe ser multipart/form-data, para que la aplicación web de Spring sepa que la solicitud contiene datos de archivo que deben procesarse. También ten en cuenta que para varios archivos, los campos de formulario “file” y “name” son iguales en los campos de entrada, de modo que los datos se enviarán en forma de un array. Tomaremos el array de entrada, analizaremos los datos del archivo y los almacenaremos en el nombre de archivo proporcionado.
Configuración de Multipart en Spring MVC
Para utilizar Apache Commons FileUpload para manejar solicitudes multipart, todo lo que necesitamos hacer es configurar el bean multipartResolver
con la clase org.springframework.web.multipart.commons.CommonsMultipartResolver
. Nuestro archivo de configuración final de Spring se ve así. Código servlet-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:beans="https://www.springframework.org/schema/beans"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing
infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving
up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/**" location="/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources
in the /WEB-INF/views directory -->
<beans:bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<beans:bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- setting maximum upload size -->
<beans:property name="maxUploadSize" value="100000" />
</beans:bean>
<context:component-scan base-package="com.journaldev.spring.controller" />
</beans:beans>
Observa que estoy estableciendo el límite máximo de tamaño de carga proporcionando el valor de la propiedad maxUploadSize para el bean multipartResolver. Si revisas el código fuente de la clase DispatcherServlet
, verás que se define y se inicializa una variable MultipartResolver con el nombre multipartResolver en el siguiente método.
private void initMultipartResolver(ApplicationContext context)
{
try
{
this.multipartResolver = ((MultipartResolver)context.getBean("multipartResolver", MultipartResolver.class));
if (this.logger.isDebugEnabled()) {
this.logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex)
{
this.multipartResolver = null;
if (this.logger.isDebugEnabled())
this.logger.debug("Unable to locate MultipartResolver with name 'multipartResolver': no multipart request handling provided");
}
}
Con esta configuración, cualquier solicitud con enctype como multipart/form-data será manejada por multipartResolver antes de pasar al controlador.
Clase Controladora de Subida de Archivos en Spring
La clase del controlador es muy simple, necesitamos definir métodos de controlador para las URI uploadFile y uploadMultipleFile. Código FileUploadController.java:
package com.journaldev.spring.controller;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
/**
* Handles requests for the application file upload requests
*/
@Controller
public class FileUploadController {
private static final Logger logger = LoggerFactory
.getLogger(FileUploadController.class);
/**
* Upload single file using Spring Controller
*/
@RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
public @ResponseBody
String uploadFileHandler(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
try {
byte[] bytes = file.getBytes();
// Creando el directorio para almacenar el archivo
String rootPath = System.getProperty("catalina.home");
File dir = new File(rootPath + File.separator + "tmpFiles");
if (!dir.exists())
dir.mkdirs();
// Crear el archivo en el servidor
File serverFile = new File(dir.getAbsolutePath()
+ File.separator + name);
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream(serverFile));
stream.write(bytes);
stream.close();
logger.info("Server File Location="
+ serverFile.getAbsolutePath());
return "You successfully uploaded file=" + name;
} catch (Exception e) {
return "You failed to upload " + name + " => " + e.getMessage();
}
} else {
return "You failed to upload " + name
+ " because the file was empty.";
}
}
/**
* Upload multiple file using Spring Controller
*/
@RequestMapping(value = "/uploadMultipleFile", method = RequestMethod.POST)
public @ResponseBody
String uploadMultipleFileHandler(@RequestParam("name") String[] names,
@RequestParam("file") MultipartFile[] files) {
if (files.length != names.length)
return "Mandatory information missing";
String message = "";
for (int i = 0; i < files.length; i++) {
MultipartFile file = files[i];
String name = names[i];
try {
byte[] bytes = file.getBytes();
// Creando el directorio para almacenar el archivo
String rootPath = System.getProperty("catalina.home");
File dir = new File(rootPath + File.separator + "tmpFiles");
if (!dir.exists())
dir.mkdirs();
// Crear el archivo en el servidor
File serverFile = new File(dir.getAbsolutePath()
+ File.separator + name);
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream(serverFile));
stream.write(bytes);
stream.close();
logger.info("Server File Location="
+ serverFile.getAbsolutePath());
message = message + "You successfully uploaded file=" + name
+ "
";
} catch (Exception e) {
return "You failed to upload " + name + " => " + e.getMessage();
}
}
return message;
}
}
Observa el uso de las anotaciones de Spring que facilitan nuestra vida y hacen que el código sea más legible. El método uploadFileHandler
se utiliza para manejar el escenario de carga de un solo archivo, mientras que el método uploadMultipleFileHandler
se utiliza para manejar el escenario de carga de varios archivos. De hecho, podríamos tener un solo método para manejar ambos escenarios. Ahora exporta la aplicación como archivo WAR y despliégala en el contenedor de servlets Tomcat. Cuando ejecutamos nuestra aplicación, las imágenes siguientes nos muestran las solicitudes y respuestas.
Ejemplo de carga de archivos de Spring MVC
Puedes verificar los registros del servidor para conocer la ubicación donde se han almacenado los archivos. Descarga el proyecto desde el enlace anterior y juega con él para aprender más.