Este artículo explora varias funcionalidades clave dentro del framework Spring MVC, incluyendo la validación de datos, el manejo centralizado de excepciones, la implementación de servicios RESTful y el uso de interceptores.
Validación de Datos
Spring MVC adopta la especificación de validación JSR-303 (parte del estándar Java EE 6), utilizando Hibernate Validator como implementación (sin relación con el ORM Hibernate).
Configuración Inicial
Se requieren los siguientes beanss en la configuración de Spring para habilitar la validación:
<!-- Bean para el validador -->
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!-- Especifica la implementación del validador -->
<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
<!-- Define el archivo de recursos para mensajes de validación -->
<property name="validationMessageSource" ref="messageSource" />
</bean>
<!-- Bean para la carga de mensajes de validación -->
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!-- Rutas a los archivos de propiedades de validación -->
<property name="basenames">
<list>
<value>classpath:CustomValidationMessages</value>
</list>
</property>
<!-- Codificación de los archivos de propiedades -->
<property name="fileEncodings" value="utf-8" />
<!-- Tiempo de caché para los archivos de propiedades (en segundos) -->
<property name="cacheSeconds" value="120" />
</bean>
<!-- Inicializador de enlace web personalizado -->
<bean id="customBinder"
class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<!-- Asigna el validador configurado -->
<property name="validator" ref="validator" />
</bean>
<!-- Adaptador de métodos con anotaciones -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<!-- Inyecta el inicializador de enlace web personalizado -->
<property name="webBindingInitializer" ref="customBinder"></property>
</bean>
Definición de Reglas de Validación
Las reglas se definen mediante anotaciones en la clase del modelo:
package entity;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
public class Items {
private Integer id;
@Size(min=1, max=30, message="{items.name.length.error}")
private String name;
private Float price;
private String pic;
@NotNull(message="{items.createtime.is.notnull}")
private Date createtime;
private String detail;
// Getters y Setters...
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = (name == null ? null : name.trim()); }
public Float getPrice() { return price; }
public void setPrice(Float price) { this.price = price; }
public String getPic() { return pic; }
public void setPic(String pic) { this.pic = (pic == null ? null : pic.trim()); }
public Date getCreatetime() { return createtime; }
public void setCreatetime(Date createtime) { this.createtime = createtime; }
public String getDetail() { return detail; }
public void setDetail(String detail) { this.detail = (detail == null ? null : detail.trim()); }
}
Uso en el Controlador
En el controlador, se utiliza la anotación @Validated en el parámetro del modelo y se recibe un objeto BindingResult para capturar los errores:
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.Validated;
import java.util.List;
@Controller
public class ItemController {
@RequestMapping("/validationExample")
public void validateItem(@Validated Items items, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<objecterror> allErrors = bindingResult.getAllErrors();
for (ObjectError error : allErrors) {
System.out.println(error.getDefaultMessage());
}
} else {
// Procesar el objeto Items si la validación es exitosa
System.out.println("Validación exitosa: " + items.getName());
}
}
}
</objecterror>
Validación por Grupos
Para aplicar diferentes conjuntos de reglas de validación según el contexto (por ejemplo, al crear vs. al actualizar un ítem), se pueden definir grupos de validación. Esto implica definir interfaces marcadoras para los grupos, anotarlas en los campos del modelo y especificar el grupo a utilizar en la anotación @Validated del controlador.
Manejo Centralizado de Excepciones
Spring MVC proporciona un mecanismo para centralizar el manejo de excepciones mediante la implementación de la interfaz HandlerExceptionResolver.
Creación de una Excepción Personalizada
Se puede definir una excepción específica para el dominio de la aplicación:
public class CustomAppException extends Exception {
private String message;
public CustomAppException(String message) {
super(message);
this.message = message;
}
@Override
public String getMessage() {
return message;
}
}
Implementación del Resolutor de Excepciones
Esta clase intercepta las excepciones lanzadas por los controladores y las maneja de forma unificada:
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class GlobalExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
ex.printStackTrace(); // Log the exception
String errorMessage;
if (ex instanceof CustomAppException) {
errorMessage = ex.getMessage();
} else {
errorMessage = "Ocurrió un error inesperado."; // Default error message
}
request.setAttribute("errorMessage", errorMessage);
// Redirigir a una página de error genérica
return new ModelAndView("/WEB-INF/jsp/error.jsp");
}
}
Configuración del Resolutor
Se registra el resolutor de excepciones como un bean en la configuración de Spring MVC:
<!-- Configuración del manejador global de excepciones -->
<bean class="com.example.exception.GlobalExceptionHandler"></bean>
Soporte RESTful
Spring MVC facilita la creación de servicios RESTful mediante el mapeo de URLs y el uso de anotaciones específicas.
Configuración del DispatcherServlet
Para admitir URLs sin la extensión ".action", se configura el DispatcherServlet para manejar todas las peticiones entrantes:
<!-- Configuración para RESTful -->
<servlet>
<servlet-name>springMvcRest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc-config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springMvcRest</servlet-name>
<!-- Mapea todas las rutas para habilitar RESTful -->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- Manejo de recursos estáticos (imágenes, CSS, JS) -->
<mvc:resources location="/resources/js/" mapping="/js/**" />
<mvc:resources location="/resources/img/" mapping="/img/**" />
Uso de @PathVariable
La anotación @PathVariable se utiliza para capturar sgementos de la URL y vincularlos a parámetros del método del controlador:
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/api/items")
public class ItemApiController {
@RequestMapping(value = "/view/{itemId}", method = RequestMethod.GET)
public @ResponseBody ItemsCustom getItemById(@PathVariable("itemId") Integer id) throws Exception {
// Lógica para obtener el ítem por ID usando el servicio
ItemsCustom item = itemService.findItemById(id);
return item;
}
@RequestMapping(value = "/create", method = RequestMethod.POST)
public @ResponseBody ResponseEntity<string> createItem(@RequestBody Items newItem) {
// Lógica para crear un nuevo ítem
// Retorna una respuesta HTTP apropiada
return new ResponseEntity<>("Item created successfully", HttpStatus.CREATED);
}
}
</string>
Interceptores de Spring MVC
Los interceptores permiten interceptar las solicitudes antes de que lleguen al controlador, después de que el controlador se ejecuta pero antes de que se envíe la vista, y después de que la respuesta se completa.
Implementación de un Interceptor
Se implementa la interfaz HandlerInterceptor:
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AuditInterceptor implements HandlerInterceptor {
// Se ejecuta antes de que el handler (controlador) sea invocado
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor: preHandle - " + request.getRequestURI());
// Retornar 'false' para detener la ejecución; 'true' para continuar
return true;
}
// Se ejecuta después de que el handler es invocado, pero antes de renderizar la vista
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor: postHandle - " + request.getRequestURI());
}
// Se ejecuta después de que la respuesta ha sido completada (incluso si hubo excepciones)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor: afterCompletion - " + request.getRequestURI());
if (ex != null) {
System.err.println("Interceptor: Exception occurred - " + ex.getMessage());
}
}
}
Configuración de Interceptores
Los interceptores se configuran en el archivo de Spring MVC usando la etiqueta <mvc:interceptors>:
<!-- Configuración de interceptores -->
<mvc:interceptors>
<!-- Interceptor para auditoría -->
<mvc:interceptor>
<!-- Aplica a todas las rutas -->
<mvc:mapping path="/**" />
<bean class="com.example.interceptors.AuditInterceptor"></bean>
</mvc:interceptor>
<!-- Interceptor de autenticación (ejemplo) -->
<mvc:interceptor>
<mvc:mapping path="/secure/**" /> <!-- Aplica solo a rutas seguras -->
<bean class="com.example.interceptors.AuthenticationInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
Orden de Ejecución
Los métodos preHandle se ejecutan en el orden en que se definen los interceptores. Los métodos postHandle y afterCompletion se ejecutan en orden inverso.
Caso de Uso: Autenticación de Usuario
Un interceptor común es el de autenticación, que verifica si el usuario ha iniciado sesión antes de permitir el acceso a recursos protegidos.
import javax.servlet.http.HttpSession;
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestUrl = request.getRequestURI();
// Rutas públicas que no requieren autenticación
if (requestUrl.contains("/login") || requestUrl.contains("/public")) {
return true;
}
HttpSession session = request.getSession(false); // No crear sesión si no existe
if (session != null && session.getAttribute("userLogged") != null) {
return true; // Usuario autenticado
}
// Redirigir a la página de login si no está autenticado
response.sendRedirect(request.getContextPath() + "/login");
return false; // Interrumpir la solicitud
}
// Implementación de postHandle y afterCompletion si es necesario
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
@Controller
public class LoginController {
@RequestMapping("/login")
public String showLoginPage() {
return "login"; // Retorna el nombre de la vista de login
}
@RequestMapping(value = "/performLogin", method = RequestMethod.POST)
public String performLogin(HttpSession session, @RequestParam String username, @RequestParam String password) {
// Lógica de autenticación (verificar usuario/contraseña)
boolean isAuthenticated = authenticateUser(username, password);
if (isAuthenticated) {
session.setAttribute("userLogged", username);
return "redirect:/dashboard"; // Redirigir al dashboard si es exitoso
} else {
// Manejar credenciales incorrectas
return "login"; // Volver a mostrar la página de login
}
}
@RequestMapping("/logout")
public String logout(HttpSession session) {
session.invalidate(); // Invalidar la sesión
return "redirect:/login"; // Redirigir a la página de login
}
private boolean authenticateUser(String username, String password) {
// Simulación de autenticación
return "admin".equals(username) && "password123".equals(password);
}
}