Spring Boot Adicionando Interceptadores de Solicitação Http

107

Qual é a maneira certa de adicionar interceptores HttpRequest no aplicativo Spring Boot? O que eu quero fazer é registrar solicitações e respostas para cada solicitação http.

A documentação do Spring boot não cobre este tópico. ( http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ )

Encontrei alguns exemplos da web sobre como fazer o mesmo com versões anteriores do spring, mas funcionam com applicationcontext.xml. Por favor ajude.

Riship89
fonte
Olá @ riship89 ... implementei com HandlerInterceptorsucesso. Está funcionando bem. O único problema é que alguns internal HandlerInterceptorlançam uma exceção antes de ser tratada pelo custom HandlerInterceptor. O afterCompletion()método que é sobrescrito é chamado depois que o erro é lançado pela implementação interna de HandlerInterceptor. Você tem solução para isso?
Chetan Oswal

Respostas:

155

Já que você está usando Spring Boot, suponho que você prefere confiar na configuração automática do Spring sempre que possível. Para adicionar configuração customizada adicional como seus interceptores, basta fornecer uma configuração ou bean de WebMvcConfigurerAdapter.

Aqui está um exemplo de uma classe de configuração:

@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 não anote isso com @EnableWebMvc, se você deseja manter a configuração automática do Spring Boots para mvc .

ikumen
fonte
Agradável! Parece bom! Algum exemplo do que está dentro de registry.addInterceptor (...)? Apenas curioso para saber uma amostra de "..."
riship89
6
use a anotação @Component em yourInjectedInceptor
Paolo Biavati
1
@ riship89 Veja um exemplo: mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example
Vlad Manuel Mureșan
4
Recebo erro The type WebMvcConfigurerAdapter is deprecated. Estou usando o Spring Web MVC 5.0.6
John Henckel de
7
No Spring 5, apenas implemente WebMvcConfigurer em vez de estender WebMvcConfigurerAdapter. Como as interfaces Java 8 permitem implementações padrão, não é mais necessário usar o adaptador (é por isso que ele está obsoleto).
edgraaff
88

WebMvcConfigurerAdapterserá descontinuado com Spring 5. De seu Javadoc :

@depreciado a partir do 5.0 {@link WebMvcConfigurer} tem métodos padrão (possibilitados por uma linha de base Java 8) e pode ser implementado diretamente sem a necessidade deste adaptador

Conforme declarado acima, o que você deve fazer é implementar WebMvcConfigurere substituir o addInterceptorsmétodo.

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyCustomInterceptor());
    }
}
Sedooe
fonte
10
Sua resposta está incompleta porque está faltando a implementação deMyCustomInterceptor
peterchaula
33

Para adicionar interceptor a um aplicativo de inicialização de mola, faça o seguinte

  1. Crie uma classe 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. Defina uma classe de configuração

    @Configuration
    public class MyConfig extends WebMvcConfigurerAdapter{
        @Override
        public void addInterceptors(InterceptorRegistry registry){
            registry.addInterceptor(new MyCustomInterceptor()).addPathPatterns("/**");
        }
    }
  3. É isso aí. Agora todas as suas solicitações passarão pela lógica definida no método preHandle () de MyCustomInterceptor.

sunitha
fonte
1
Eu segui esse caminho para interceptar as solicitações de inscrição que chegam ao meu aplicativo para fazer algumas validações comuns. Mas o problema é que recebo o getReader() has already been called for this requesterro. Existe alguma maneira mais simples de superar isso sem usar uma cópia da solicitação real?
vigamage
Quando o pré-manipulador é chamado, o corpo da solicitação não está disponível, mas apenas parâmetros. Para fazer a validação no corpo da solicitação, a forma preferida será usar Aspect J e criarAdvice
Hima
12

Como todas as respostas a isso fazem uso do adaptador WebMvcConfigurer abstrato há muito obsoleto em vez do WebMvcInterface (como já observado por @sebdooe), aqui está um exemplo mínimo funcional para um aplicativo SpringBoot (2.1.4) com um Interceptor:

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

funciona como anunciado

A saída lhe 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
fonte
Mas isso exigirá que você implemente todos os métodos do WebMvcConfigurer, certo?
Steven
Não, é uma interface (Java 8) com implementações padrão vazias
Tom,
9

Eu tive o mesmo problema de WebMvcConfigurerAdapter sendo preterido. Quando procurei por exemplos, quase não encontrei nenhum código implementado. Aqui está um código de trabalho.

crie uma classe que estende 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;
    }

}

em seguida, implemente a 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
fonte
4
como você pode substituir apenas um método em uma interface sem problemas de compilação?
xetra11
1
@ xetra11 Também estou tentando ver se podemos implementar apenas um método em vez de todos os outros métodos não usados ​​neste caso. É possível? Você descobriu isso?
usuário09
3
@arjun Os outros são implementados como métodos padrão graças ao Java 8. Este raciocínio é, convenientemente, documentado na classe obsoleta.
Bob
7

Você também pode considerar o uso da biblioteca open source SpringSandwich, que permite anotar diretamente em seus controladores Spring Boot quais interceptadores aplicar, da mesma forma que você anota suas rotas de url.

Dessa forma, nenhuma string propensa a erros por aí - o método e as anotações de classe de SpringSandwich sobrevivem facilmente à refatoração e deixam claro o que está sendo aplicado onde. (Divulgação: eu sou o autor).

http://springsandwich.com/

Magnus
fonte
Isso parece incrível! Eu criei um problema solicitando que o SpringSandwich estivesse disponível no maven central para torná-lo mais fácil de usar para projetos que estão construindo sob CI ou que estão implementando via Heroku.
Brendon Dugan
Ótimo. Está disponível no repositório central do maven? Re-blogging springsandwich.com em meu site com a referência de seu repositório git e referência
Arpan Das
1
SpringSandwich está agora em Maven Central
Magnus
1

Abaixo está uma implementação que uso para interceptar cada solicitação HTTP antes de sair e a resposta que volta. Com essa implementação, também tenho um único ponto onde posso passar qualquer valor de cabeçalho com a solicitação.

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

Abaixo está o bean de modelo 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
fonte