Spring Boot Ajout d'intercepteurs de requête Http

107

Quelle est la bonne façon d'ajouter des intercepteurs HttpRequest dans l'application Spring Boot? Ce que je veux faire, c'est enregistrer les demandes et les réponses pour chaque demande http.

La documentation de Spring Boot ne couvre pas du tout ce sujet. ( http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ )

J'ai trouvé des exemples Web sur la façon de faire de même avec les anciennes versions de Spring, mais ceux-ci fonctionnent avec applicationcontext.xml. Veuillez aider.

riship89
la source
Salut @ riship89 ... J'ai mis en œuvre HandlerInterceptoravec succès. Cela fonctionne bien. Seul le problème est que certains internal HandlerInterceptorlèvent une exception avant qu'elle ne soit gérée par le custom HandlerInterceptor. La afterCompletion()méthode qui est remplacée est appelée après que l'erreur est levée par l'implémentation interne de HandlerInterceptor. Avez-vous une solution pour cela?
Chetan Oswal

Réponses:

155

Puisque vous utilisez Spring Boot, je suppose que vous préférez vous fier à la configuration automatique de Spring lorsque cela est possible. Pour ajouter une configuration personnalisée supplémentaire comme vos intercepteurs, fournissez simplement une configuration ou un bean de WebMvcConfigurerAdapter.

Voici un exemple de classe de configuration:

@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() {
        ...
    });
  }
}

REMARQUE ne pas annoter cela avec @EnableWebMvc, si vous souhaitez conserver la configuration automatique de Spring Boots pour mvc .

Ikumen
la source
Agréable! Cela semble bon! Un exemple de ce qui se passe à l'intérieur de registry.addInterceptor (...)? Juste curieux de connaître un échantillon de "..."
riship89
6
utiliser l'annotation @Component sur yourInjectedInceptor
Paolo Biavati
1
@ riship89 Consultez ceci pour un exemple: mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example
Vlad Manuel Mureșan
4
J'obtiens une erreur The type WebMvcConfigurerAdapter is deprecated. J'utilise Spring Web MVC 5.0.6
John Henckel
7
Au printemps 5, implémentez simplement WebMvcConfigurer au lieu d'étendre WebMvcConfigurerAdapter. Étant donné que les interfaces Java 8 autorisent les implémentations par défaut, il n'est plus nécessaire d'utiliser l'adaptateur (c'est pourquoi il est obsolète).
edgraaff
88

WebMvcConfigurerAdaptersera obsolète avec Spring 5. Depuis sa Javadoc :

@deprecated à partir de 5.0 {@link WebMvcConfigurer} a des méthodes par défaut (rendues possibles par une ligne de base Java 8) et peut être implémenté directement sans avoir besoin de cet adaptateur

Comme indiqué ci-dessus, vous devez implémenter WebMvcConfigureret remplacer la addInterceptorsméthode.

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyCustomInterceptor());
    }
}
sedooe
la source
10
Votre réponse est incomplète car il manque l'implémentation deMyCustomInterceptor
peterchaula
33

Pour ajouter un intercepteur à une application de démarrage à ressort, procédez comme suit

  1. Créer une classe d'intercepteur

    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. Définir une classe de configuration

    @Configuration
    public class MyConfig extends WebMvcConfigurerAdapter{
        @Override
        public void addInterceptors(InterceptorRegistry registry){
            registry.addInterceptor(new MyCustomInterceptor()).addPathPatterns("/**");
        }
    }
  3. C'est tout. Désormais, toutes vos requêtes passeront par la logique définie sous la méthode preHandle () de MyCustomInterceptor.

sunitha
la source
1
J'ai suivi cette méthode pour intercepter les demandes d'inscription venant de mon application afin de faire quelques validations courantes. Mais le problème est que j'obtiens l' getReader() has already been called for this requesterreur. Existe-t-il un moyen plus simple de surmonter cela sans utiliser une copie de la demande réelle?
vigamage
Lorsque le pré-gestionnaire est appelé, le corps de la requête n'est pas disponible, mais uniquement les paramètres.Pour faire la validation sur le corps de la requête, la méthode préférée sera d'utiliser Aspect J et de créerAdvice
Hima
12

Étant donné que toutes les réponses à cela utilisent l'adaptateur abstrait WebMvcConfigurer maintenant obsolète depuis longtemps au lieu de WebMvcInterface (comme déjà noté par @sebdooe), voici un exemple minimal de travail pour une application SpringBoot (2.1.4) avec un intercepteur:

Minimal.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");
    }
}

fonctionne comme annoncé

La sortie vous donnera quelque chose comme:

> 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
Xénonite
la source
Mais cela vous obligera à implémenter toutes les méthodes du WebMvcConfigurer, non?
Steven le
Non, c'est une interface (Java 8) avec des implémentations par défaut vides
Tom
9

J'ai eu le même problème de WebMvcConfigurerAdapter étant obsolète. Quand j'ai cherché des exemples, je n'ai pratiquement trouvé aucun code implémenté. Voici un morceau de code fonctionnel.

créer une classe qui étend 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;
    }

}

puis implémenter l'interface 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");
    }
}
user2532195
la source
4
Comment pouvez-vous remplacer une seule méthode sur une interface sans problèmes de compilation?
xetra11
1
@ xetra11 J'essaie également de voir si nous ne pouvons implémenter qu'une seule méthode au lieu de toutes les autres méthodes non utilisées dans ce cas. C'est possible? Avez-vous compris cela?
user09
3
@arjun Les autres sont implémentées comme méthodes par défaut grâce à Java 8. Ce raisonnement est, commodément, documenté dans la classe obsolète.
Bob
7

Vous pouvez également envisager d'utiliser la bibliothèque open source SpringSandwich qui vous permet d'annoter directement dans vos contrôleurs Spring Boot les intercepteurs à appliquer, de la même manière que vous annotez vos routes d'url.

De cette façon, pas de chaînes sujettes à des fautes de frappe - la méthode et les annotations de classe de SpringSandwich survivent facilement à la refactorisation et indiquent clairement ce qui est appliqué et où. (Divulgation: je suis l'auteur).

http://springsandwich.com/

Magnus
la source
Cela a l'air génial! J'ai créé un problème demandant que SpringSandwich soit disponible dans maven central pour le rendre plus facile à utiliser pour les projets qui sont construits sous CI ou qui se déploient via Heroku.
Brendon Dugan
Génial. Est-il disponible dans le référentiel central de maven? Re - blogging springsandwich.com dans mon site Web avec la référence de votre référentiel git et référence
Arpan Das
1
SpringSandwich est maintenant à Maven Central
Magnus
1

Vous trouverez ci-dessous une implémentation que j'utilise pour intercepter chaque requête HTTP avant qu'elle ne s'éteigne et la réponse qui revient. Avec cette implémentation, j'ai également un point unique où je peux passer n'importe quelle valeur d'en-tête avec la demande.

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

Vous trouverez ci-dessous le bean de modèle de repos

@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;
}
Sylvestre
la source