1. Overview
In this tutorial, we will introduce and explain Feign, a declarative HTTP client developed by Netflix.
Feign aims at simplifying HTTP API clients. Simply put, the developer needs only to declare and annotate an interface while the actual implementation will be provisioned at runtime.
2. Example
We will present an example of a bookstore service REST API, that is queried and tested based on the Feign HTTP client.
Before we build a sample Feign client, we'll add the needed dependencies and startup the REST service.
The bookstore service example can be cloned from here.
After downloading the service application, we'll run it with:
1
|
$> mvn
install
spring-boot:run
|
3. Setup
First, we'll create a new Maven project and include these dependencies:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<
dependency
>
<
groupId
>io.github.openfeign</
groupId
>
<
artifactId
>feign-okhttp</
artifactId
>
<
version
>9.3.1</
version
>
</
dependency
>
<
dependency
>
<
groupId
>io.github.openfeign</
groupId
>
<
artifactId
>feign-gson</
artifactId
>
<
version
>9.3.1</
version
>
</
dependency
>
<
dependency
>
<
groupId
>io.github.openfeign</
groupId
>
<
artifactId
>feign-slf4j</
artifactId
>
<
version
>9.3.1</
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.
To actually get some log output, you'll need your favorite, SLF4J-supported logger implementation on your classpath.
Before we continue to create our client interface, we'll set up a Book model for holding our data:
1
2
3
4
5
6
7
8
9
|
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.
In fact, our REST provider is a hypermedia-driven API, so we'll need a simple wrapper class:
1
2
3
4
5
|
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!
4. Server Side
To understand how to define a Feign client, we'll first look into some of the methods and responses supported by our REST provider.
Let's try it out with a simple curl shell command to list all books. Don't forget to prefix your calls with ‘/api', which is the servlet-context defined in the application.properties:
1
|
$> curl http:
//localhost
:8081
/api/books
|
we will get a complete book repository represented as JSON:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
[
{
"book"
: {
"isbn"
:
"1447264533"
,
"author"
:
"Margaret Mitchell"
,
"title"
:
"Gone with the Wind"
,
"synopsis"
:
null
,
"language"
:
null
},
"links"
: [
{
"rel"
:
"self"
,
}
]
},
...
{
"book"
: {
"isbn"
:
"0451524934"
,
"author"
:
"George Orwell"
,
"title"
:
"1984"
,
"synopsis"
:
null
,
"language"
:
null
},
"links"
: [
{
"rel"
:
"self"
,
}
]
}
]
|
We can also query individual Book resource, by appending the ISBN to a get request:
1
|
$> curl http:
//localhost
:8081
/api/books/1447264533
|
5. Feign Client
Now we'll define our Feign client.
We will use the @RequestLine annotation to specify the HTTP verb and a path part as argument, and the parameters will be modeled using the @Param annotation:
1
2
3
4
5
6
7
8
9
10
11
|
public
interface
BookClient {
@RequestLine
(
"GET /{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.
That's all! Now we'll use the Feign.builder() to configure our interface-based client. The actual implementation will be provisioned at runtime:
1
2
3
4
5
6
7
|
BookClient bookClient = Feign.builder()
.client(
new
OkHttpClient())
.encoder(
new
GsonEncoder())
.decoder(
new
GsonDecoder())
.logger(
new
Slf4jLogger(BookClient.
class
))
.logLevel(Logger.Level.FULL)
|
Feign supports various plugins such as JSON/XML encoders and decoders or an underlying HTTP client for making the requests.
6. Unit Test
Let's create a unit test class, containing three @Test methods, to test our client. The test will use static imports from the packages org.hamcrest.CoreMatchers.* and org.junit.Assert.*:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@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"
));
}
|
These tests are pretty self-explanatory. To run it, simply execute the Maven test goal:
1
|
$> mvn
test
|
7. Further Reading
If you need some kind of fallback, in the case of service unavailability, you can add HystrixFeign to your classpath and build your client with the HystrixFeign.builder() instead.
To learn more about Hystrix, please follow this dedicated tutorial series.
If you want to integrate Spring Cloud Netflix Hystrix with Feign, you can read more about this here.
It's also possible to add client-side load-balancing and/or service discovery to your client.
The former is done by adding Ribbon to your classpath and call the builder like so:
1
2
3
|
BookClient bookClient = Feign.builder()
.client(RibbonClient.create())
|
For service discovery, you have to build up your service with Spring Cloud Netflix Eureka enabled. Then simply integrate with Spring Cloud Netflix Feign and you get Ribbon load-balancing for free. More about this can be found here.
8. Conclusion
This article explained how to build a declarative HTTP client using Feign to consume text-based APIs.
As usual, you'll find the sources on GitHub.
来源:oschina
链接:https://my.oschina.net/ciet/blog/3162121