IntrotoFeign

编程

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 FeignHTTP 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"

,

        

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

)

    

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.

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)

  

.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.

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())

  

.target(BookClient.

class

,

"http://localhost:8081/api/books"

);

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.

以上是 IntrotoFeign 的全部内容, 来源链接: utcz.com/z/513098.html

回到顶部