1. Introducción
En este tutorial nos centraremos en comprender Spring MVC HandlerInterceptor y cómo usarlo correctamente.
2. Controlador Spring MVC
Para entender el interceptor, demos un paso atrás y observemos HandlerMapping . Esto asigna un método a una URL, de modo que DispatcherServlet podrá invocarlo cuando procese una solicitud.
Y el DispatcherServlet usa el HandlerAdapter para invocar el método.
Ahora que entendemos el contexto general, aquí es donde entra el interceptor del controlador . Usaremos HandlerInterceptor para realizar acciones antes de manejar, después de manejar o después de completar (cuando se renderiza la vista), de una solicitud.
El interceptor se puede utilizar para cuestiones transversales y para evitar el código de controlador repetitivo como el registro, el cambio de parámetros utilizados globalmente en el modelo Spring, etc.
En las siguientes secciones, eso es exactamente lo que veremos: las diferencias entre varias implementaciones de interceptores.
3. Dependencias de Maven
Para utilizar Interceptores , debe incluir la siguiente sección en una sección de dependencias de su archivo pom.xml :
org.springframework spring-web 5.2.8.RELEASE
La última versión se puede encontrar aquí.
4. Interceptor de manipulador de resortes
Los interceptores que trabajan con HandlerMapping en el marco deben implementar la interfaz HandlerInterceptor .
Esta interfaz contiene tres métodos principales:
- prehandle () : se llama antes de que se ejecute el controlador real, pero la vista aún no se genera
- postHandle () : se llama después de que se ejecuta el controlador
- afterCompletion (): se llama después de que finaliza la solicitud completa y se genera la vista
Estos tres métodos brindan flexibilidad para realizar todo tipo de procesamiento previo y posterior.
Y una nota rápida: la principal diferencia entre HandlerInterceptor y HandlerInterceptorAdapter es que en el primero debemos anular los tres métodos: preHandle () , postHandle () y afterCompletion () , mientras que en el segundo podemos implementar solo los métodos requeridos.
Una nota rápida antes de continuar: si desea omitir la teoría y pasar directamente a los ejemplos, vaya directamente a la sección 5.
Así es como se verá una implementación simple de preHandle () :
@Override public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // your code return true; }
Observe que el método devuelve un valor booleano , que le dice a Spring si la solicitud debe ser procesada más por un controlador ( verdadero ) o no ( falso ).
A continuación, tenemos una implementación de postHandle () :
@Override public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // your code }
Este método se llama inmediatamente después de que HandlerAdapter procesa la solicitud , pero antes de generar una vista.
Y, por supuesto, se puede usar de muchas maneras; por ejemplo, podemos agregar un avatar de un usuario que inició sesión en un modelo.
El método final que debemos implementar en la implementación personalizada de HandlerInterceptor es afterCompletion ():
@Override public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // your code }
Cuando la vista se genera correctamente, podemos usar este gancho para hacer cosas como recopilar estadísticas adicionales relacionadas con la solicitud.
Una nota final para recordar es que un HandlerInterceptor está registrado en el bean DefaultAnnotationHandlerMapping , que es responsable de aplicar interceptores a cualquier clase marcada con una anotación @Controller . Además, puede especificar cualquier número de interceptores en su aplicación web.
5. Interceptor de registrador personalizado
En este ejemplo, nos centraremos en iniciar sesión en nuestra aplicación web. Primero que nada, nuestra clase necesita extender HandlerInterceptorAdapter :
public class LoggerInterceptor extends HandlerInterceptorAdapter { ... }
También necesitamos habilitar el registro en nuestro interceptor:
private static Logger log = LoggerFactory.getLogger(LoggerInterceptor.class);
Esto permite a Log4J mostrar registros, así como indicar, qué clase está registrando información en la salida especificada.
A continuación, centrémonos en las implementaciones de interceptores personalizados:
5.1. Método preHandle ()
Este método se llama antes de manejar una solicitud; devuelve verdadero, para permitir que el marco envíe la solicitud al método del controlador (o al siguiente interceptor). Si el método devuelve falso , Spring asume que la solicitud se ha manejado y no se necesita ningún procesamiento adicional.
Podemos usar el gancho para registrar información sobre los parámetros de las solicitudes: de dónde proviene la solicitud, etc.
En nuestro ejemplo, estamos registrando esta información usando un registrador Log4J simple:
@Override public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("[preHandle][" + request + "]" + "[" + request.getMethod() + "]" + request.getRequestURI() + getParameters(request)); return true; }
Como podemos ver, estamos registrando información básica sobre la solicitud.
En caso de que encontremos una contraseña aquí, tendremos que asegurarnos de no registrarla, por supuesto.
Una opción simple es reemplazar las contraseñas y cualquier otro tipo de datos confidenciales por estrellas.
Aquí hay una implementación rápida de cómo se puede hacer:
private String getParameters(HttpServletRequest request) { StringBuffer posted = new StringBuffer(); Enumeration e = request.getParameterNames(); if (e != null) { posted.append("?"); } while (e.hasMoreElements()) { if (posted.length() > 1) { posted.append("&"); } String curr = (String) e.nextElement(); posted.append(curr + "="); if (curr.contains("password") || curr.contains("pass") || curr.contains("pwd")) { posted.append("*****"); } else { posted.append(request.getParameter(curr)); } } String ip = request.getHeader("X-FORWARDED-FOR"); String ipAddr = (ip == null) ? getRemoteAddr(request) : ip; if (ipAddr!=null && !ipAddr.equals("")) { posted.append("&_psip=" + ipAddr); } return posted.toString(); }
Finally, we're aiming to get the source IP address of the HTTP request.
Here's a simple implementation:
private String getRemoteAddr(HttpServletRequest request) { String ipFromHeader = request.getHeader("X-FORWARDED-FOR"); if (ipFromHeader != null && ipFromHeader.length() > 0) { log.debug("ip from proxy - X-FORWARDED-FOR : " + ipFromHeader); return ipFromHeader; } return request.getRemoteAddr(); }
5.2. Method postHandle()
This hook runs when the HandlerAdapter is invoked the handler but DispatcherServlet is yet to render the view.
We can use this method to add additional attributes to the ModelAndView or to determine the time taken by handler method to process a client's request.
In our case, we simply log a request just before DispatcherServlet is going to render a view.
@Override public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("[postHandle][" + request + "]"); }
5.3. Method afterCompletion()
When a request is finished and the view is rendered, we may obtain request and response data, as well as information about exceptions, if any occurred:
@Override public void afterCompletion( HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception { if (ex != null){ ex.printStackTrace(); } log.info("[afterCompletion][" + request + "][exception: " + ex + "]"); }
6. Configuration
To add our interceptors into Spring configuration, we need to override addInterceptors() method inside WebConfig class that implements WebMvcConfigurer:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoggerInterceptor()); }
We may achieve the same configuration by editing our XML Spring configuration file:
With this configuration active, the interceptor will be active and all requests in the application will be properly logged.
Please notice, if multiple Spring interceptors are configured, the preHandle() method is executed in the order of configuration, whereas postHandle() and afterCompletion() methods are invoked in the reverse order.
If we're using Spring Boot instead of vanilla Spring, we should keep in mind to not annotate our configuration class with @EnableWebMvc, or we'll lose out on Boot's auto configurations.
7. Conclusion
This tutorial is a quick introduction to intercepting HTTP requests using Spring MVC Handler Interceptor.
All examples and configurations are available here on GitHub.