Spring MVC: Validadores, Manejo de Excepciones Centralizado, RESTful e Interceptores

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);
    }
}

Etiquetas: Spring MVC validacion jsr-303 Hibernate Validator manejo de excepciones

Publicado el 9-23 20:14