Spring Boot agregando interceptores de solicitud Http

107

¿Cuál es la forma correcta de agregar interceptores HttpRequest en la aplicación Spring Boot? Lo que quiero hacer es registrar solicitudes y respuestas para cada solicitud http.

La documentación de Spring Boot no cubre este tema en absoluto. ( http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ )

Encontré algunas muestras web sobre cómo hacer lo mismo con versiones anteriores de Spring, pero funcionan con applicationcontext.xml. Por favor ayuda.

riship89
fuente
Hola @ riship89 ... Lo he implementado con HandlerInterceptoréxito. Funciona bien. El único problema es que algunos internal HandlerInterceptorlanzan una excepción antes de que sea manejada por custom HandlerInterceptor. El afterCompletion()método que se reemplaza se llama después de que la implementación interna de HandlerInterceptor arroja el error. ¿Tienes una solución para esto?
Chetan Oswal

Respuestas:

155

Dado que está utilizando Spring Boot, supongo que prefiere confiar en la configuración automática de Spring siempre que sea posible. Para agregar una configuración personalizada adicional como sus interceptores, solo proporcione una configuración o un bean de WebMvcConfigurerAdapter.

Aquí hay un ejemplo de una clase de configuración:

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

  @Autowired 
  HandlerInterceptor yourInjectedInterceptor;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(...)
    ...
    registry.addInterceptor(getYourInterceptor()); 
    registry.addInterceptor(yourInjectedInterceptor);
    // next two should be avoid -- tightly coupled and not very testable
    registry.addInterceptor(new YourInterceptor());
    registry.addInterceptor(new HandlerInterceptor() {
        ...
    });
  }
}

NOTA no anote esto con @EnableWebMvc, si desea mantener la configuración automática de Spring Boots para mvc .

ikumen
fuente
¡Agradable! ¡Se ve bien! ¿Algún ejemplo de lo que entra en el registro.addInterceptor (...)? Solo
tengo
6
use la anotación @Component en yourInjectedInceptor
Paolo Biavati
1
@ riship89 Vea esto para ver un ejemplo: mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example
Vlad Manuel Mureșan
4
Me sale un error The type WebMvcConfigurerAdapter is deprecated. Estoy usando Spring Web MVC 5.0.6
John Henckel
7
En Spring 5, simplemente implemente WebMvcConfigurer en lugar de extender WebMvcConfigurerAdapter. Dado que las interfaces de Java 8 permiten implementaciones predeterminadas, ya no es necesario utilizar el adaptador (razón por la cual está obsoleto).
edgraaff
88

WebMvcConfigurerAdapterquedará obsoleto con Spring 5. Desde su Javadoc :

@deprecated a partir de 5.0 {@link WebMvcConfigurer} tiene métodos predeterminados (que son posibles gracias a una línea de base de Java 8) y se puede implementar directamente sin la necesidad de este adaptador

Como se indicó anteriormente, lo que debe hacer es implementar WebMvcConfigurery anular el addInterceptorsmétodo.

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyCustomInterceptor());
    }
}
sedooe
fuente
10
Su respuesta está incompleta porque le falta la implementación deMyCustomInterceptor
peterchaula
33

Para agregar interceptor a una aplicación de arranque de primavera, haga lo siguiente

  1. Crea una clase de interceptor

    public class MyCustomInterceptor implements HandlerInterceptor{
    
        //unimplemented methods comes here. Define the following method so that it     
        //will handle the request before it is passed to the controller.
    
        @Override
        public boolean preHandle(HttpServletRequest request,HttpServletResponse  response){
        //your custom logic here.
            return true;
        }
    }
    
  2. Definir una clase de configuración

    @Configuration
    public class MyConfig extends WebMvcConfigurerAdapter{
        @Override
        public void addInterceptors(InterceptorRegistry registry){
            registry.addInterceptor(new MyCustomInterceptor()).addPathPatterns("/**");
        }
    }
    
  3. Eso es. Ahora todas sus solicitudes pasarán por la lógica definida en el método preHandle () de MyCustomInterceptor.

Sunitha
fuente
1
He seguido este camino para interceptar las solicitudes de registro que llegan a mi aplicación para hacer algunas validaciones comunes. Pero el problema es que obtengo el getReader() has already been called for this requesterror. ¿Existe alguna forma más sencilla de superar esto sin usar una copia de la solicitud real?
vigamage
Cuando se llama al pre-handler, el cuerpo de la solicitud no está disponible, sino solo los parámetros. Para realizar la validación en el cuerpo de la solicitud, la forma preferida será usar el aspecto J y crearAdvice
Hima
12

Dado que todas las respuestas a esto hacen uso del Adaptador WebMvcConfigurer abstracto ahora obsoleto en lugar de WebMvcInterface (como ya lo señaló @sebdooe), aquí hay un ejemplo mínimo funcional para una aplicación SpringBoot (2.1.4) con un Interceptor:

Mínimo.java:

@SpringBootApplication
public class Minimal
{
    public static void main(String[] args)
    {
        SpringApplication.run(Minimal.class, args);
    }
}

MinimalController.java:

@RestController
@RequestMapping("/")
public class Controller
{
    @GetMapping("/")
    @ResponseBody
    public ResponseEntity<String> getMinimal()
    {
        System.out.println("MINIMAL: GETMINIMAL()");

        return new ResponseEntity<String>("returnstring", HttpStatus.OK);
    }
}

Config.java:

@Configuration
public class Config implements WebMvcConfigurer
{
    //@Autowired
    //MinimalInterceptor minimalInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(new MinimalInterceptor());
    }
}

MinimalInterceptor.java:

public class MinimalInterceptor extends HandlerInterceptorAdapter
{
    @Override
    public boolean preHandle(HttpServletRequest requestServlet, HttpServletResponse responseServlet, Object handler) throws Exception
    {
        System.out.println("MINIMAL: INTERCEPTOR PREHANDLE CALLED");

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception
    {
        System.out.println("MINIMAL: INTERCEPTOR POSTHANDLE CALLED");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception
    {
        System.out.println("MINIMAL: INTERCEPTOR AFTERCOMPLETION CALLED");
    }
}

funciona como se anuncia

La salida le dará algo como:

> Task :Minimal.main()

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.4.RELEASE)

2019-04-29 11:53:47.560  INFO 4593 --- [           main] io.minimal.Minimal                       : Starting Minimal on y with PID 4593 (/x/y/z/spring-minimal/build/classes/java/main started by x in /x/y/z/spring-minimal)
2019-04-29 11:53:47.563  INFO 4593 --- [           main] io.minimal.Minimal                       : No active profile set, falling back to default profiles: default
2019-04-29 11:53:48.745  INFO 4593 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-04-29 11:53:48.780  INFO 4593 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-04-29 11:53:48.781  INFO 4593 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.17]
2019-04-29 11:53:48.892  INFO 4593 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-04-29 11:53:48.893  INFO 4593 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1269 ms
2019-04-29 11:53:49.130  INFO 4593 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-04-29 11:53:49.375  INFO 4593 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-04-29 11:53:49.380  INFO 4593 --- [           main] io.minimal.Minimal                       : Started Minimal in 2.525 seconds (JVM running for 2.9)
2019-04-29 11:54:01.267  INFO 4593 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-04-29 11:54:01.267  INFO 4593 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-04-29 11:54:01.286  INFO 4593 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 19 ms
MINIMAL: INTERCEPTOR PREHANDLE CALLED
MINIMAL: GETMINIMAL()
MINIMAL: INTERCEPTOR POSTHANDLE CALLED
MINIMAL: INTERCEPTOR AFTERCOMPLETION CALLED
Xenonita
fuente
Pero esto requerirá que implemente todos los métodos del WebMvcConfigurer, ¿verdad?
Steven
No, es una interfaz (Java 8) con implementaciones predeterminadas vacías
Tom
9

Tuve el mismo problema de que WebMvcConfigurerAdapter estaba en desuso. Cuando busqué ejemplos, casi no encontré ningún código implementado. Aquí hay un fragmento de código de trabajo.

crear una clase que amplíe HandlerInterceptorAdapter

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import me.rajnarayanan.datatest.DataTestApplication;
@Component
public class EmployeeInterceptor extends HandlerInterceptorAdapter {
    private static final Logger logger = LoggerFactory.getLogger(DataTestApplication.class);
    @Override
    public boolean preHandle(HttpServletRequest request, 
            HttpServletResponse response, Object handler) throws Exception {

            String x = request.getMethod();
            logger.info(x + "intercepted");
        return true;
    }

}

luego implementar la interfaz WebMvcConfigurer

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import me.rajnarayanan.datatest.interceptor.EmployeeInterceptor;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    EmployeeInterceptor employeeInterceptor ;

    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(employeeInterceptor).addPathPatterns("/employee");
    }
}
usuario2532195
fuente
4
¿Cómo se puede anular solo un método en una interfaz sin problemas de compilación?
xetra11
1
@ xetra11 También estoy tratando de ver si solo podemos implementar un método en lugar de todos los demás métodos que no se usan en este caso. ¿Es posible? ¿Te diste cuenta de eso?
usuario09
3
@arjun Los demás se implementan como métodos predeterminados gracias a Java 8. Este razonamiento está, convenientemente, documentado en la clase obsoleta.
Bob
7

También puede considerar usar la biblioteca SpringSandwich de código abierto que le permite anotar directamente en sus controladores Spring Boot qué interceptores aplicar, de la misma manera que anota sus rutas de URL.

De esa manera, no hay cadenas propensas a errores tipográficos flotando: el método de SpringSandwich y las anotaciones de clase sobreviven fácilmente a la refactorización y dejan en claro qué se está aplicando y dónde. (Divulgación: soy el autor).

http://springsandwich.com/

Magnus
fuente
¡Eso se ve increible! Creé un problema solicitando que SpringSandwich esté disponible en maven central para que sea más fácil de usar para proyectos que se están construyendo bajo CI o que se están implementando a través de Heroku.
Brendon Dugan
Excelente. ¿Está disponible en el repositorio central de maven? Re - blogging springsandwich.com en mi sitio web con la referencia de su repositorio y referencia de git
Arpan Das
1
SpringSandwich está ahora en Maven Central
Magnus
1

A continuación se muestra una implementación que utilizo para interceptar cada solicitud HTTP antes de que salga y la respuesta que regresa. Con esta implementación, también tengo un solo punto donde puedo pasar cualquier valor de encabezado con la solicitud.

public class HttpInterceptor implements ClientHttpRequestInterceptor {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public ClientHttpResponse intercept(
        HttpRequest request, byte[] body,
        ClientHttpRequestExecution execution
) throws IOException {
    HttpHeaders headers = request.getHeaders();
    headers.add("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
    headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
    traceRequest(request, body);
    ClientHttpResponse response = execution.execute(request, body);
    traceResponse(response);
    return response;
}

private void traceRequest(HttpRequest request, byte[] body) throws IOException {
    logger.info("===========================Request begin======================================");
    logger.info("URI         : {}", request.getURI());
    logger.info("Method      : {}", request.getMethod());
    logger.info("Headers     : {}", request.getHeaders() );
    logger.info("Request body: {}", new String(body, StandardCharsets.UTF_8));
    logger.info("==========================Request end=========================================");
}

private void traceResponse(ClientHttpResponse response) throws IOException {
    logger.info("============================Response begin====================================");
    logger.info("Status code  : {}", response.getStatusCode());
    logger.info("Status text  : {}", response.getStatusText());
    logger.info("Headers      : {}", response.getHeaders());
    logger.info("=======================Response end===========================================");
}}

A continuación se muestra el frijol de plantilla de descanso

@Bean
public RestTemplate restTemplate(HttpClient httpClient)
{
    HttpComponentsClientHttpRequestFactory requestFactory =
            new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    RestTemplate restTemplate=  new RestTemplate(requestFactory);
    List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
    if (CollectionUtils.isEmpty(interceptors))
    {
        interceptors = new ArrayList<>();
    }
    interceptors.add(new HttpInterceptor());
    restTemplate.setInterceptors(interceptors);

    return restTemplate;
}
Sylvester
fuente