Spring 引导添加 HTTP 请求拦截器

在弹簧启动应用程序中添加 HttpRequest 拦截器的正确方法是什么?我想做的是记录每个 http 请求的请求和响应。

Spring 引导文档根本不涉及这个主题

我找到了一些关于如何对旧版本的 spring 进行同样处理的 Web 示例,但是这些示例都可以用于 applicationcontext.xml。请帮帮我。

270698 次浏览

因为您使用的是 Spring Boot,所以我假设您更愿意在可能的情况下依赖 Spring 的自动配置。要添加额外的自定义配置(如拦截器) ,只需提供 WebMvcConfigurerAdapter的配置或 bean。

下面是一个配置类的例子:

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

注意 不要使用@EnableWebMvc 对此进行注释,如果您希望保留 Spring Boots 为 mvc 自动配置

您还可以考虑使用开源的 SpringSandwich 库,它允许您直接在 Spring Boot 控制器中注释要应用的拦截器,这与您注释 URL 路由的方式非常相似。

这样,就不会有易于输入错误的字符串漂浮在周围—— SpringSandwich 的方法和类注释很容易在重构中存活下来,并且能够清楚地表明在哪里应用了哪些内容。(披露: 我是作者)。

Http://springsandwich.com/

要将拦截器添加到弹簧引导应用程序,请执行下列操作

  1. 创建一个拦截器类

    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. Define a configuration class

    @Configuration
    public class MyConfig extends WebMvcConfigurerAdapter{
    @Override
    public void addInterceptors(InterceptorRegistry registry){
    registry.addInterceptor(new MyCustomInterceptor()).addPathPatterns("/**");
    }
    }
    
  3. Thats it. Now all your requests will pass through the logic defined under preHandle() method of MyCustomInterceptor.

WebMvcConfigurerAdapter将与 Spring 5一起废弃:

从5.0开始已不推荐使用{@link WebMvcConfigrer }具有默认方法(使用 基线) ,并且可以直接实现,而不需要这个适配器

如上所述,您应该做的是实现 WebMvcConfigurer并重写 addInterceptors方法。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {


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

我遇到过同样的 WebMvcConfigurerAdapter 被弃用的问题。当我搜索示例时,几乎没有找到任何实现的代码。下面是一段工作代码。

创建一个扩展 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;
}


}

然后实现 WebMvcConfigrer 接口

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

由于所有响应都使用了现在已经废弃很久的抽象 WebMvcConfigrer Adapter,而不是 WebMvcInterface (@sebdooe 已经提到过) ,下面是一个使用拦截器的 SpringBoot (2.1.4)应用程序的最小工作示例:

最小化.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);
}
}

Java:

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


@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(new 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");
}
}

就像广告上说的那样

输出结果将会给出类似下面的内容:

> 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

下面是我用来拦截每个 HTTP 请求的实现,然后在它发出之前拦截回来的响应。在这个实现中,我还有一个单点,可以在其中传递任何带有请求的头值。

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

下面是 Rest 模板 Bean

@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;
}

我在这个网站上找到了一个很好的教程,讲解如何使用注释将请求拦截器添加到特定的控制器:

  1. 定义注释
  2. 定义拦截器
  3. 将拦截器添加到路径
  4. 使用特定控制器上的注释

Https://programmer.group/how-do-spring-boot-2.x-add-interceptors.html

我知道这个问题是如何向所有请求添加拦截器,这个问题已经得到了回答。我一直在寻找使用注释将请求拦截器添加到特定控制器的解决方案,但是在 stackoverflow 中找不到解决方案。决定将此内容添加到这个问题,而不是提出一个新的问题。

定义注释 NeedLogin.class

package com.example.helloSpringBoot.annotation;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;




@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLogin {
}

然后定义感受器类

package com.example.helloSpringBoot.config;


import com.example.helloSpringBoot.annotation.NeedLogin;
import com.example.helloSpringBoot.util.WxUserInfoContext;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;


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


/**
* Logon interceptor
*
* @Author: Java Fragment
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {


//This method is executed before accessing the interface. We only need to write the business logic to verify the login status here to verify the login status before the user calls the specified interface.
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


if (handler instanceof HandlerMethod) {
NeedLogin needLogin = ((HandlerMethod) handler).getMethodAnnotation(NeedLogin.class);
if (null == needLogin) {
needLogin = ((HandlerMethod) handler).getMethod().getDeclaringClass()
.getAnnotation(NeedLogin.class);
}
// Check login if you have login validation annotations
if (null != needLogin) {
WxUserInfoContext curUserContext = (WxUserInfoContext) request.getSession()
.getAttribute("curUserContext");
//If session No, not logged in.
if (null == curUserContext) {
response.setCharacterEncoding("UTF-8");
response.getWriter().write("Not logged in!");
return false;
}
}


}
return true;
}


public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}


public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}

然后将感受器添加到 WebConfig 中

package com.example.helloSpringBoot.config;




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;


/**
* WebConfig
*
* @Author: Java Fragment
*
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;


@Override
public void addInterceptors(InterceptorRegistry registry) {
// Custom interceptor, add intercept path and exclude intercept path
registry.addInterceptor(loginInterceptor).addPathPatterns("/**");
}
}

最后,您可以使用新的注释@NeedLogin 来自由地使用新的拦截器

package com.example.helloSpringBoot.controller;


import com.example.helloSpringBoot.annotation.NeedLogin;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class HelloController {


/**
* Testing does not require login
*
*
*/
@RequestMapping("/testNoLogin")
public String testNoLogin (){
return "The call is successful, this interface does not need login validation!-Java Broken read!";
}


/**
* Testing requires login
*
*
*/
@NeedLogin
@RequestMapping("/testNeedLogin")
public String testNeedLogin (){
return "testNeedLogin!";
}
}

为了跟踪 spring-boot (java)应用程序中的所有请求响应,您可以创建一个过滤器类,如下所示-

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;


import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;


@Component
public class RequestResponseTracker extends OncePerRequestFilter {


@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);


filterChain.doFilter(requestWrapper, responseWrapper);


System.out.println("Request URI: " + request.getRequestURI());
System.out.println("Request Headers: " + Collections.list(request.getHeaderNames()).stream()
.map(headerName -> headerName + " -> " + request.getHeader(headerName)).collect(Collectors.toList()));
System.out.println("Request Method: " + request.getMethod());
System.out.println("Request Body: " + getStringValue(requestWrapper.getContentAsByteArray(),
responseWrapper.getCharacterEncoding()));


System.out.println("Response Code: " + response.getStatus());
System.out.println("Response Body: " + getStringValue(responseWrapper.getContentAsByteArray(),
responseWrapper.getCharacterEncoding()));
System.out.println("Response Headers: " + response.getHeaderNames().stream()
.map(headerName -> headerName + " -> " + response.getHeader(headerName)).collect(Collectors.toList()));


responseWrapper.copyBodyToResponse();   // Don't forget to add this at the end
}




private String getStringValue(byte[] contentAsByteArray, String characterEncoding) {
try {
return new String(contentAsByteArray, 0, contentAsByteArray.length,
Objects.nonNull(characterEncoding) ? characterEncoding : StandardCharsets.UTF_8.name());
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
}

我将推荐以下动态注入自定义拦截器的方法。
您不需要逐个添加拦截器。

定制拦截器:

public interface CustomInterceptor extends HandlerInterceptor {


default String[] excludePathPatterns() {
return new String[]{
"/*/*.html",
"/*/*.css",
"/*/*.js",
"/*/*.png",
"/*/*.xml",
"/*/*.json",
"/*/*.yaml",
"/*/*.yml",
"/swagger*/**"
};
}




default String[] pathPatterns() {
return new String[]{"/**"};
}
}

WebMvcConfig:

@Slf4j
@Configuration
@RequiredArgsConstructor
@SuppressWarnings("NullableProblems")
@ConditionalOnProperty(name = AutoConfigConstants.ENABLE_MVC, havingValue = "true")
public class DefaultMvcAutoConfiguration implements WebMvcConfigurer {


static {
log.info(AutoConfigConstants.LOADING_MVC_AUTO_CONFIGURE);
}


private final List<CustomInterceptor> customInterceptors;


@Override
public void addInterceptors(InterceptorRegistry registry) {
WebMvcConfigurer.super.addInterceptors(registry);
if (CollectionUtils.isNotEmpty(customInterceptors)) {
customInterceptors.forEach(customInterceptor ->
registry.addInterceptor(customInterceptor)
.addPathPatterns(customInterceptor.pathPatterns())
.excludePathPatterns(customInterceptor.excludePathPatterns()));
}
}
}