1. 概述

在这个简短的教程中,我们将探讨在Spring应用程序中提取请求头的各种方法。我们将学习如何针对特定端点操作,并随后创建一个HandlerInterceptor,它拦截所有入站请求并提取头部信息。

2. 使用HttpServletRequest

为了能够访问HTTP请求的信息,我们可以在端点的参数中声明一个HttpServletRequest对象。这样,我们可以查看路径、查询参数、cookies和头部等详细内容。

例如,当我们接收到请求时,可以使用HttpServletRequest来提取自定义头部。要获取特定头部,我们可以使用getHeader()方法,指定头部的键:

@RestController
public class FooBarController {

    @GetMapping("foo")
    public String foo(HttpServletRequest request) {
        String operator = request.getHeader("operator");
        return "hello, " + operator;
    }

}

我们可以使用MockMvc发送一个包含自定义头部的GET请求。如果我们将操作员头部设置为"John.Doe",则期望响应为"hello, John.Doe"

@Test
public void givenARequestWithOperatorHeader_whenWeCallFooEndpoint_thenOperatorIsExtracted() throws Exception {
    MockHttpServletResponse response = this.mockMvc.perform(get("/foo").header("operator", "John.Doe"))
      .andDo(print())
      .andReturn()
      .getResponse();

    assertThat(response.getContentAsString()).isEqualTo("hello, John.Doe");
}

然而,如果我们只需要请求中的一个特定头部,将整个HttpServletRequest作为参数可能会违反接口分离原则,即SOLID设计模式中的“I”。

3. 使用@RequestHeader

对于特定端点访问头部的另一种简单方式是使用@RequestHeader注解:

@GetMapping("bar")
public String bar(@RequestHeader("operator") String operator) {
    return "hello, " + operator;
}

因此,我们的代码不再与整个HttpServletRequest对象耦合,现在我们的方法使用作为参数传递的所有数据。

让我们为这个端点编写类似的测试,期望得到相同的结果:

@Test
public void givenARequestWithOperatorHeader_whenWeCallBarEndpoint_thenOperatorIsExtracted() throws Exception {
    MockHttpServletResponse response = this.mockMvc.perform(get("/bar").header("operator", "John.Doe"))
      .andDo(print())
      .andReturn()
      .getResponse();

    assertThat(response.getContentAsString()).isEqualTo("hello, John.Doe");
}

4. 使用HandlerInterceptor

对于更复杂的用例,我们可以使用HandlerInterceptor对象。优点是它可以拦截所有入站请求,并提取头部的值。

此外,我们可以将头部值包装成一个具有request作用域的Spring Bean,并将其注入到可能需要它的不同组件中。

首先,我们将操作员名称封装到一个对象中:

public class OperatorHolder {
    private String operator;
    // getter and setter
}

然后,我们使用@Bean声明它。操作员可能因请求而异,因此我们应该将bean的作用域设置为SCOPE_REQUEST

@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public OperatorHolder operatorHolder() {
    return new OperatorHolder();
}

接下来,我们需要创建一个自定义的HandlerInterceptor实现,并重写preHandle()方法:

public class OperatorInterceptor implements HandlerInterceptor {
    private final OperatorHolder operatorHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String operator = request.getHeader("operator");
        operatorHolder.setOperator(operator);
        return true;
    }
    // constructor
}

这样,请求被拦截,操作员头部被提取,并且OperatorHolder Bean被更新。

最后,我们需要将自定义拦截器添加到Spring MVC的InterceptorRegistry中。我们可以通过实现WebMvcConfigurer并重写addInterceptor()方法来做到这一点:

@Configuration
public class HeaderInterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(final InterceptorRegistry registry) {
        registry.addInterceptor(operatorInterceptor());
    }

    @Bean
    public OperatorInterceptor operatorInterceptor() {
        return new OperatorInterceptor(operatorHolder());
    }
}

现在要访问操作员,只需注入OperatorHolder Bean并调用getOperator()方法即可:

@RestController
public class BuzzController {
    private final OperatorHolder operatorHolder;

    @GetMapping("buzz")
    public String buzz() {
        return "hello, " + operatorHolder.getOperator();
    }
    // constructor
}

5. 总结

在这篇文章中,我们探讨了为入站HTTP请求提取自定义头部的不同方法。

首先,我们了解了如何通过HttpServletRequest@RequestHeader为特定端点操作。然后,我们看到了HandlerInterceptor如何帮助我们从所有入站请求中提取头部,提供了一个更通用的解决方案。

如往常一样,教程的完整源代码可在GitHub上找到:https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-mvc-5