1. 概述
在使用Feign时,有时我们需要设置HTTP调用的请求头。Feign允许我们仅通过声明性语法构建HTTP客户端。
在这篇简短教程中,我们将学习如何使用注解配置请求头,并通过拦截器包含常见的请求头。
2. 示例
在整个教程中,我们将使用一个示例的图书商店应用,它提供了REST API端点。
我们可以轻松地克隆项目并在本地运行:
$ mvn install spring-boot:run
现在让我们深入研究客户端实现。
3. 使用Header
注解
设想一个场景:某些API调用始终应包含静态请求头。在这种情况下,我们可以在客户端中配置这些请求头。一个典型例子是包括Content-Type
头。
使用@Header
注解,我们可以轻松配置静态请求头。我们可以将头的值定义为静态的,也可以动态定义。
3.1. 设置静态头值
让我们在BookClient
中配置两个静态头,即Accept-Language
和Content-Type
:
@Headers("Accept-Language: en-US")
public interface BookClient {
@RequestLine("GET /{isbn}")
BookResource findByIsbn(@Param("isbn") String isbn);
@RequestLine("POST")
@Headers("Content-Type: application/json")
void create(Book book);
}
在上述代码中,由于Accept-Language
应用到了BookClient
,所以所有API都将包含它。然而,create
方法还有额外的Content-Type
头。
接下来,让我们使用Feign的Builder
方法创建BookClient
,并传递HEADERS
日志级别:
Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.logger(new Slf4jLogger(type))
.logLevel(Logger.Level.HEADERS)
.target(BookClient.class, "http://localhost:8081/api/books");
现在,让我们测试create
方法:
String isbn = UUID.randomUUID().toString();
Book book = new Book(isbn, "Me", "It's me!", null, null);
bookClient.create(book);
book = bookClient.findByIsbn(isbn).getBook();
然后,我们在输出日志中验证头信息:
18:01:15.039 [main] DEBUG c.b.f.c.h.staticheader.BookClient - [BookClient#create] Accept-Language: en-US
18:01:15.039 [main] DEBUG c.b.f.c.h.staticheader.BookClient - [BookClient#create] Content-Type: application/json
18:01:15.096 [main] DEBUG c.b.f.c.h.staticheader.BookClient - [BookClient#findByIsbn] Accept-Language: en-US
需要注意的是,如果客户端接口和API方法中的头名称相同,它们不会互相覆盖。相反,请求将包含所有这样的值。
3.2. 设置动态头值
使用@Header
注解,我们还可以设置动态头值。为此,我们需要将值表示为占位符。
让我们在BookClient
中添加一个名为requester
的占位符来包含x-requester-id
头:
@Headers("x-requester-id: {requester}")
public interface BookClient {
@RequestLine("GET /{isbn}")
BookResource findByIsbn(@Param("requester") String requester, @Param("isbn") String isbn);
}
这里,我们将x-requester-id
设置为一个可传递到每个方法的变量。我们使用了@Param
注解来匹配变量的名称,它会在运行时扩展以满足由@Headers
注解指定的头。
现在,让我们使用带有x-requester-id
头的调用BookClient
API:
String requester = "test";
book = bookClient.findByIsbn(requester, isbn).getBook();
然后,我们在输出日志中验证请求头:
18:04:27.515 [main] DEBUG c.b.f.c.h.s.parameterized.BookClient - [BookClient#findByIsbn] x-requester-id: test
4. 使用HeaderMaps
注解
设想一个场景,头的键和值都是动态的,且可能的键范围在调用之前未知。此外,同一个客户端的不同方法调用之间,头可能有所变化。一个典型例子是设置某些元数据头。
使用一个被@HeaderMap
注解的Map
参数可以设置动态头:
@RequestLine("POST")
void create(@HeaderMap Map<String, Object> headers, Book book);
现在,让我们尝试使用头映射测试create
方法:
Map<String,Object> headerMap = new HashMap<>();
headerMap.put("metadata-key1", "metadata-value1");
headerMap.put("metadata-key2", "metadata-value2");
bookClient.create(headerMap, book);
然后,我们在输出日志中验证头信息:
18:05:03.202 [main] DEBUG c.b.f.c.h.dynamicheader.BookClient - [BookClient#create] metadata-key1: metadata-value1
18:05:03.202 [main] DEBUG c.b.f.c.h.dynamicheader.BookClient - [BookClient#create] metadata-key2: metadata-value2
5. 请求拦截器
拦截器可以为每次请求或响应执行各种隐式任务,如日志记录或身份验证。
Feign提供了一个RequestInterceptor
接口,我们可以借此为每个请求添加头。
当知道某个头应该包含在每次调用中时,添加请求拦截器是有意义的。这消除了调用代码实现诸如身份验证或跟踪等非功能性需求的依赖。
让我们尝试实现一个AuthorisationService
,用于生成授权令牌:
public class ApiAuthorisationService implements AuthorisationService {
@Override
public String getAuthToken() {
return "Bearer " + UUID.randomUUID();
}
}
现在,让我们实现自定义请求拦截器:
public class AuthRequestInterceptor implements RequestInterceptor {
private AuthorisationService authTokenService;
public AuthRequestInterceptor(AuthorisationService authTokenService) {
this.authTokenService = authTokenService;
}
@Override
public void apply(RequestTemplate template) {
template.header("Authorisation", authTokenService.getAuthToken());
}
}
请注意,请求拦截器可以读取、移除或修改请求模板的任何部分。
现在,我们使用builder
方法将AuthInterceptor
添加到BookClient
:
Feign.builder()
.requestInterceptor(new AuthInterceptor(new ApiAuthorisationService()))
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.logger(new Slf4jLogger(type))
.logLevel(Logger.Level.HEADERS)
.target(BookClient.class, "http://localhost:8081/api/books");
然后,让我们带有Authorization
头测试BookClient
API:
bookClient.findByIsbn("0151072558").getBook();
现在,我们在输出日志中验证头信息:
18:06:06.135 [main] DEBUG c.b.f.c.h.staticheader.BookClient - [BookClient#findByIsbn] Authorisation: Bearer 629e0af7-513d-4385-a5ef-cb9b341cedb5
也可以在Feign客户端上应用多个请求拦截器,尽管没有关于它们应用顺序的确切保证。
6. 总结
在这篇文章中,我们讨论了Feign客户端如何支持设置请求头。我们使用@Headers
、@HeaderMaps
注解和请求拦截器实现了这一功能。
如往常一样,本教程的所有示例代码可在GitHub上找到。