Spring Boot Dodanie przechwytywaczy żądań HTTP

108

Jaki jest właściwy sposób dodawania przechwytywaczy HttpRequest w aplikacji rozruchu sprężynowego? Chcę rejestrować żądania i odpowiedzi na każde żądanie http.

Dokumentacja Spring Boot w ogóle nie obejmuje tego tematu. ( http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ )

Znalazłem kilka przykładów internetowych pokazujących, jak zrobić to samo ze starszymi wersjami Spring, ale te działają z applicationcontext.xml. Proszę pomóż.

riship89
źródło
Cześć @ riship89 ... Zaimplementowałem HandlerInterceptorpomyślnie. Działa dobrze. Tylko problem polega na tym, że niektórzy internal HandlerInterceptorzgłaszają wyjątek, zanim zostanie on obsłużony przez custom HandlerInterceptor. afterCompletion()Metoda, która jest zastępowana jest nazywany po błędzie jest wyrzucane przez wewnętrzny realizacji HandlerInterceptor. Czy masz na to rozwiązanie?
Chetan Oswal

Odpowiedzi:

157

Ponieważ używasz Spring Boot, zakładam, że wolisz w miarę możliwości polegać na automatycznej konfiguracji Springa. Aby dodać dodatkową konfigurację niestandardową, taką jak przechwytywacze, wystarczy podać konfigurację lub element bean WebMvcConfigurerAdapter.

Oto przykład klasy config:

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

UWAGA nie dodawaj adnotacji @EnableWebMvc, jeśli chcesz zachować automatyczną konfigurację Spring Boots dla mvc .

ikumen
źródło
Miły! Wygląda dobrze! Jakikolwiek przykład tego, co dzieje się w rejestrze.addInterceptor (...)?
Jestem po
6
użyj adnotacji @Component na yourInjectedInceptor
Paolo Biavati
1
@ riship89 Sprawdź to na przykład: mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example
Vlad Manuel Mureșan
4
Otrzymuję błąd The type WebMvcConfigurerAdapter is deprecated. Używam Spring Web MVC 5.0.6
John Henckel
7
Wiosną 5 po prostu zaimplementuj WebMvcConfigurer zamiast rozszerzać WebMvcConfigurerAdapter. Ponieważ interfejsy Java 8 zezwalają na domyślne implementacje, nie jest już wymagane używanie adaptera (dlatego jest on nieaktualny).
edgraaff
89

WebMvcConfigurerAdapterzostaną wycofane wraz z Spring 5. Z jego Javadoc :

@deprecated od 5.0 {@link WebMvcConfigurer} ma domyślne metody (możliwe dzięki wersji bazowej Java 8) i można je zaimplementować bezpośrednio bez potrzeby stosowania tego adaptera

Jak wspomniano powyżej, należy zaimplementować WebMvcConfigureri zastąpić addInterceptorsmetodę.

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyCustomInterceptor());
    }
}
sedooe
źródło
10
Twoja odpowiedź jest niekompletna, ponieważ brakuje w niej realizacjiMyCustomInterceptor
peterchaula
33

Aby dodać interceptor do aplikacji rozruchu sprężynowego, wykonaj następujące czynności

  1. Utwórz klasę przechwytywacza

    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. Zdefiniuj klasę konfiguracji

    @Configuration
    public class MyConfig extends WebMvcConfigurerAdapter{
        @Override
        public void addInterceptors(InterceptorRegistry registry){
            registry.addInterceptor(new MyCustomInterceptor()).addPathPatterns("/**");
        }
    }
  3. Otóż ​​to. Teraz wszystkie twoje żądania będą przechodzić przez logikę zdefiniowaną w metodzie preHandle () MyCustomInterceptor.

sunitha
źródło
1
Postępowałem w ten sposób, aby przechwycić żądania rejestracji przychodzące do mojej aplikacji w celu wykonania niektórych typowych weryfikacji. Ale problem polega na tym, że pojawia się getReader() has already been called for this requestbłąd. Czy istnieje prostszy sposób na rozwiązanie tego problemu bez używania kopii rzeczywistego wniosku?
vigamage
Gdy wywoływana jest procedura obsługi wstępnej, treść żądania nie jest dostępna, a jedynie parametry. Aby przeprowadzić walidację treści żądania, preferowanym sposobem będzie użycie aspektu J i utworzenieAdvice
Hima
12

Ponieważ wszystkie odpowiedzi na to wykorzystują od dawna przestarzały abstrakcyjny adapter WebMvcConfigurer zamiast WebMvcInterface (jak już zauważył @sebdooe), oto działający minimalny przykład aplikacji SpringBoot (2.1.4) z przechwytywaczem:

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

działa jak w reklamie

Wynik da ci coś takiego:

> 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
Xenonite
źródło
Ale będzie to wymagało zaimplementowania wszystkich metod z WebMvcConfigurer, prawda?
Steven
Nie, jest to interfejs (Java 8) z pustymi domyślnymi implementacjami
Tom
9

Miałem ten sam problem z wycofywaniem WebMvcConfigurerAdapter. Kiedy szukałem przykładów, prawie nie znalazłem zaimplementowanego kodu. Oto fragment działającego kodu.

utwórz klasę, która rozszerza 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;
    }

}

następnie zaimplementuj interfejs 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
źródło
4
w jaki sposób można zastąpić tylko jedną metodę w interfejsie bez problemów z kompilacją?
xetra11
1
@ xetra11 Próbuję również sprawdzić, czy możemy zaimplementować tylko jedną metodę zamiast wszystkich innych metod, które nie są używane w tym przypadku. Czy to możliwe? Rozgryzłeś to?
user09
3
@arjun Pozostałe są zaimplementowane jako metody domyślne dzięki Javie 8. Takie rozumowanie jest dogodnie udokumentowane w przestarzałej klasie.
Bob
8

Możesz również rozważyć użycie biblioteki SpringSandwich o otwartym kodzie źródłowym, która umożliwia bezpośrednie dodawanie adnotacji w kontrolerach Spring Boot, które przechwytywacze zastosować, w podobny sposób, w jaki dodajesz adnotacje do tras adresów URL.

W ten sposób nie ma żadnych podatnych na literówki ciągów znaków - metoda SpringSandwich i adnotacje klas z łatwością przetrwają refaktoryzację i wyjaśniają, co jest stosowane i gdzie. (Ujawnienie: jestem autorem).

http://springsandwich.com/

Magnus
źródło
To wygląda niesamowicie! Utworzyłem problem z prośbą o udostępnienie SpringSandwich w maven central, aby ułatwić korzystanie z projektów, które są budowane w ramach CI lub które są wdrażane za pośrednictwem Heroku.
Brendon Dugan,
Wspaniały. Czy jest dostępny w centralnym repozytorium maven? Re - blogowanie springsandwich.com na mojej stronie z odniesieniem do twojego repozytorium git i referencjami
Arpan Das
1
SpringSandwich jest teraz w Maven Central
Magnus
0

Poniżej znajduje się implementacja, której używam do przechwytywania każdego żądania HTTP przed jego wysłaniem i odpowiedzią, która wróci. Dzięki tej implementacji mam również pojedynczy punkt, w którym mogę przekazać dowolną wartość nagłówka z żądaniem.

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

Poniżej znajduje się fasola szablonu Rest

@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
źródło
1
OK, ale to, co tutaj faktycznie zrobiłeś, to przechwytywacz dla RestTemplate (tj. Kiedy TY wykonujesz wywołania HTTP) ... a nie przechwytywacz dla kontrolerów Spring REST (tj. Gdy wywołania HTTP są wykonywane na Twojej aplikacji Spring / usługach internetowych REST).
maxxyme