1. Overview
1.概述
In this tutorial, we’ll introduce Feign — a declarative HTTP client developed by Netflix.
在本教程中,我们将介绍Feign–一个由Netflix开发的声明式HTTP客户端。
Feign aims at simplifying HTTP API clients. Simply put, the developer needs only to declare and annotate an interface while the actual implementation is provisioned at runtime.
Feign的目标是简化HTTP API客户端。简单地说,开发者只需要声明和注解一个接口,而实际的实现是在运行时提供的。
2. Example
2.实例
Throughout this tutorial, we’ll be using an example bookstore application that exposes the REST API endpoint.
在本教程中,我们将使用一个示例书店应用程序,它暴露了REST API端点。
We can easily clone the project and run it locally:
我们可以很容易地克隆该项目并在本地运行。
mvn install spring-boot:run
3. Setup
3.设置
First, let’s add the needed dependencies:
首先,让我们添加所需的依赖项。
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>10.11</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>10.11</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>10.11</version>
</dependency>
Besides the feign-core dependency (which is also pulled in), we’ll use a few plugins, especially feign-okhttp for internally using Square’s OkHttp client to make requests, feign-gson for using Google’s GSON as JSON processor and feign-slf4j for using the Simple Logging Facade to log requests.
除了feign-core依赖(也被拉入),我们将使用一些插件,特别是feign-okhttp,用于在内部使用Square的OkHttp客户端来发出请求,feign-gson用于使用 Google 的 GSON 作为 JSON 处理器,以及feign-slf4j用于使用Simple Logging Facade来记录请求。
To actually get some log output, we’ll need our favorite SLF4J-supported logger implementation on the classpath.
为了真正得到一些日志输出,我们需要在classpath上有我们最喜欢的支持SLF4J的日志器实现。
Before we proceed to create our client interface, first we’ll set up a Book model for holding the data:
在我们继续创建我们的客户接口之前,首先我们要建立一个Book模型来保存数据。
public class Book {
private String isbn;
private String author;
private String title;
private String synopsis;
private String language;
// standard constructor, getters and setters
}
NOTE: At least a “no arguments constructor” is needed by a JSON processor.
注意:一个JSON处理器至少需要一个 “无参数构造函数”。
In fact, our REST provider is a hypermedia-driven API, so we’ll additionally need a simple wrapper class:
事实上,我们的REST提供者是一个超媒体驱动的API,所以我们将另外需要一个简单的封装类。
public class BookResource {
private Book book;
// standard constructor, getters and setters
}
Note: We’ll keep the BookResource simple because our sample Feign client doesn’t benefit from hypermedia features!
注意:我们将保持BookResource的简单性,因为我们的样本Feign客户端并没有受益于超媒体功能
4. Server Side
4.服务器端
To understand how to define a Feign client, we’ll first look into some of the methods and responses supported by our REST provider.
为了了解如何定义一个Feign客户端,我们先来看看我们的REST提供者所支持的一些方法和响应。
Let’s try it out with a simple curl shell command to list all the books.
让我们用一个简单的curl shell命令来试试,列出所有的书。
We need to remember to prefix all the calls with /api, which is the application’s servlet-context:
我们需要记住在所有的调用前加上/api,也就是应用程序的servlet-context。
curl http://localhost:8081/api/books
As a result, we’ll get a complete book repository represented as JSON:
因此,我们将得到一个完整的书库,以JSON表示。
[
{
"book": {
"isbn": "1447264533",
"author": "Margaret Mitchell",
"title": "Gone with the Wind",
"synopsis": null,
"language": null
},
"links": [
{
"rel": "self",
"href": "http://localhost:8081/api/books/1447264533"
}
]
},
...
{
"book": {
"isbn": "0451524934",
"author": "George Orwell",
"title": "1984",
"synopsis": null,
"language": null
},
"links": [
{
"rel": "self",
"href": "http://localhost:8081/api/books/0451524934"
}
]
}
]
We can also query an individual Book resource, by appending the ISBN to a get request:
我们还可以通过在获取请求中附加ISBN来查询单个图书资源。
curl http://localhost:8081/api/books/1447264533
5. Feign Client
5.佯装客户
Finally, let’s define our Feign client.
最后,让我们定义我们的Feign客户端。
We’ll use the @RequestLine annotation to specify the HTTP verb and a path part as an argument.
我们将使用@RequestLine注解来指定HTTP动词和一个路径部分作为参数。
The parameters will be modeled using the @Param annotation:
这些参数将使用@Param注解来建模。
public interface BookClient {
@RequestLine("GET /{isbn}")
BookResource findByIsbn(@Param("isbn") String isbn);
@RequestLine("GET")
List<BookResource> findAll();
@RequestLine("POST")
@Headers("Content-Type: application/json")
void create(Book book);
}
NOTE: Feign clients can be used to consume text-based HTTP APIs only, which means that they cannot handle binary data, e.g., file uploads or downloads.
注意:Feign客户端只能用于消费基于文本的HTTP API,这意味着它们不能处理二进制数据,例如,文件上传或下载。
That’s all! Now we’ll use the Feign.builder() to configure our interface-based client.
这就是全部!现在我们将使用Feign.builder()来配置我们基于接口的客户端。
The actual implementation will be provisioned at runtime:
实际的实施将在运行时进行规定。
BookClient bookClient = Feign.builder()
.client(new OkHttpClient())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.logger(new Slf4jLogger(BookClient.class))
.logLevel(Logger.Level.FULL)
.target(BookClient.class, "http://localhost:8081/api/books");
Feign supports various plugins such as JSON/XML encoders and decoders or an underlying HTTP client for making the requests.
Feign支持各种插件,如JSON/XML编码器和解码器或用于提出请求的底层HTTP客户端。
6. Unit Test
6.单元测试
Let’s create three test cases to test our client.
让我们创建三个测试案例来测试我们的客户。
Note that we use static imports for org.hamcrest.CoreMatchers.* and org.junit.Assert.*:
注意,我们对org.hamcrest.CoreMatchers.*和org.junit.Assert.*使用静态导入。
@Test
public void givenBookClient_shouldRunSuccessfully() throws Exception {
List<Book> books = bookClient.findAll().stream()
.map(BookResource::getBook)
.collect(Collectors.toList());
assertTrue(books.size() > 2);
}
@Test
public void givenBookClient_shouldFindOneBook() throws Exception {
Book book = bookClient.findByIsbn("0151072558").getBook();
assertThat(book.getAuthor(), containsString("Orwell"));
}
@Test
public void givenBookClient_shouldPostBook() throws Exception {
String isbn = UUID.randomUUID().toString();
Book book = new Book(isbn, "Me", "It's me!", null, null);
bookClient.create(book);
book = bookClient.findByIsbn(isbn).getBook();
assertThat(book.getAuthor(), is("Me"));
}
7. Further Reading
7.进一步阅读
If we need some kind of a fallback in case of the service unavailability, we could add HystrixFeign to the classpath and build our client with HystrixFeign.builder().
如果我们在服务不可用的情况下需要某种回退,我们可以将HystrixFeign添加到classpath中,并使用HystrixFeign.builder()构建我们的客户端。
Check out this dedicated tutorial series to learn more about Hystrix.
请查看这个专门的教程系列以了解更多关于Hystrix的信息。
Additionally, if we’d like to integrate Spring Cloud Netflix Hystrix with Feign, there’s a dedicated article over here.
此外,如果我们想将Spring Cloud Netflix Hystrix与Feign集成,在这里有一篇专门的文章。
Moreover, it’s also possible to add client-side load balancing and/or service discovery to our client.
此外,还可以在我们的客户端添加客户端的负载平衡和/或服务发现。
We could achieve this by adding Ribbon to our classpath and using the builder:
我们可以通过将Ribbon添加到我们的classpath中并使用构建器来实现这一目标。
BookClient bookClient = Feign.builder()
.client(RibbonClient.create())
.target(BookClient.class, "http://localhost:8081/api/books");
For service discovery, we have to build up our service with Spring Cloud Netflix Eureka enabled. Then we simply integrate with Spring Cloud Netflix Feign. As a result, we get Ribbon load balancing for free. More about this can be found here.
对于服务发现,我们必须在启用Spring Cloud Netflix Eureka后建立我们的服务。然后我们简单地与Spring Cloud Netflix Feign集成。因此,我们可以免费获得Ribbon负载均衡。关于这一点的更多信息可在此处找到。
8. Conclusion
8.结论
In this article, we’ve explained how to build a declarative HTTP client using Feign to consume text-based APIs.
在这篇文章中,我们已经解释了如何使用Feign建立一个声明式的HTTP客户端来消费基于文本的API。
As usual, all code samples shown in this tutorial are available over on GitHub.
像往常一样,本教程中显示的所有代码样本都可以在GitHub上找到。。