This is the 10th day of my participation in the August More text Challenge. For details, see: August More Text Challenge
Following on from the previous article:
“Original Fegin” opens the door to Fegin’s RPC technology. Will you use original Fegin? (on)
Why Feign over others
-
You can use Jersey and CXF to write a Java client for a Rest or SOAP service.
-
You can also do this directly using Apache HttpClient. But Feign’s goal is to minimize the resources and code needed to connect to the HTTP API.
-
With custom codecs and error handling, you can write any text-based HTTP API.
How Feign works
-
Feign works by injecting a template request through annotations. Just close it before sending, and the parameters can be applied directly to the template.
-
However, this also limits Feign to only supporting textual apis, which greatly simplifies the system in terms of responding to requests and so on.
Feign uses retrospectives
Basic usage
The basic usage is shown below
interface UserService {
// RequestLine specifies the request method and the request address, and can allow query parameters
@RequestLine("GET /repos/{userName}/{age}/users")
List<User> getUserList(@Param("userName") String userName, @Param("age") int age);
}
static class User {
String userName;
int age;
}
public static void main(String... args) {
User user = Feign.builder()
.decoder(new GsonDecoder())
.target(UserService.class, "https://api.github.com");
// Fetch and print a list of the contributors to this library.
List<User> userList = github.getUserList("libo".12);
for (User user : userList) {
System.out.println(user.userName + "(" + user.age + ")"); }}Copy the code
Custom implement interfaces
Feign has many aspects that you can customize. As a simple example, you can use feign.Builder () to construct an API with your own components, as follows:
interface Bank {
@RequestLine("POST /account/{id}")
Account getAccountInfo(@Param("id") String id);
}
Copy the code
// AccountDecoder() is a self-implemented Decoder
Bank bank = Feign.builder().decoder(new AccountDecoder()).target(Bank.class, https://api.examplebank.com);
Copy the code
A variety of interface
Feign can provide a variety of apis, all defined as targets (the default implementation is HardCodedTarget), which allow the request to be dynamically discovered and decorated before it is executed.
For example, the following pattern allows each request to the authentication authority service to be decorated with the current URL and authentication token.
CloudDNS cloudDNS = Feign.builder().target(new CloudIdentityTarget(user, apiKey));
The sample
Feign includes sample implementations for both GitHub and Wikipedia clients. Similar projects also use Feign in practice. In particular, its example daemon.
Feign integrates multiple types of encoders
Feign can be integrated with other open source tools. You can integrate these open source tools into Feign. Some existing modules are as follows:
Gson
- Gson includes an encoder and a decoder, which can be used as an API for JSON format.
Add GsonEncoder and GsonDecoder to your ign.Builder as follows:
GsonCodec codec = new GsonCodec();
GitHub github = Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(GitHub.class, https://api.github.com);
Copy the code
Maven depends on:
<! -- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-gson</artifactId>
<version>8.18.0</version>
</dependency>
Copy the code
Jackson
- Jackson includes an encoder and a decoder that can be used as an API for JSON format.
Add JacksonEncoder and JacksonDecoder to your ign.Builder as follows:
UserService service = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(UserService.class, https://api.user.com);
Copy the code
Maven depends on:
<! -- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-jackson</artifactId>
<version>8.18.0</version>
</dependency>
Copy the code
Sax
- SaxDecoder is used to parse XML and is compatible with both regular JVMS and Android. Here is an example of configuring SAX to parse the response:
api = Feign.builder().decoder(SAXDecoder.builder()
.registerContentHandler(UserIdHandler.class)
.build())
.target(Api.class, https://apihost);
Copy the code
Maven depends on:
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-sax</artifactId>
<version>8.18.0</version>
</dependency>
Copy the code
JAXB
- JAXB includes an encoder and a decoder, which can be used as an API for XML formats.
Add JAXBEncoder and JAXBDecoder to your feign. Builder as follows:
api = Feign.builder()
.encoder(new JAXBEncoder())
.decoder(new JAXBDecoder())
.target(Api.class, https://apihost);
Copy the code
Maven depends on:
<! -- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-jaxb</artifactId>
<version>8.18.0</version>
</dependency>
Copy the code
JAX-RS
JAXRSContract overrides the default annotation handling with the JAX-RS specification.
Here is an example of using JAX-RS:
interface GitHub {
@GET @Path("/repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@PathParam("owner") String owner, @PathParam("repo") String repo);
}
The contract method configures the annotation handler, which defines which annotations and values can be applied to the interface
GitHub github = Feign.builder()
.contract(new JAXRSContract())
.target(GitHub.class, https://api.github.com);
Copy the code
Maven depends on:
<! -- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-jaxrs</artifactId>
<version>8.18.0</version>
</dependency>
Copy the code
OkHttp
OkHttpClient uses OkHttp to send Feign requests. OkHttp supports SPDY (SPDY is a TCP based transport layer protocol developed by Google to minimize network latency, improve network speed, and optimize the user’s network experience) and has better control over HTTP requests.
To get Feign to use OkHttp, you need to add OkHttp to your environment variable and then configure Feign to use OkHttpClient, as follows:
GitHub github = Feign.builder()
.client(new OkHttpClient())
.target(GitHub.class, "https://api.github.com");
Copy the code
Maven depends on:
<! -- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>8.18. 0</version>
</dependency>
Copy the code
Ribbon
**RibbonClient rewrites the Feign client’s HANDLING of URLS, adding intelligent routing and some of the other elastic features provided by the Ribbon.
Integrating the Ribbon requires you to pass the name of the client in the Ribbon as the host part of the URL, as follows: **
// myAppProd is your ribbon client name
MyService api = Feign.builder().client(RibbonClient.create()).target(MyService.class, "https://myAppProd");
Copy the code
Maven depends on:
<! -- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-ribbon</artifactId>
<version>8.18.0</version>
</dependency>
Copy the code
Hystrix
HystrixFeign is configured with the circuit breaker provided by Hystrix.
To use Hystrix in Feign, you need to add the Hystrix module to your environment variable, and then use HystrixFeign to construct your API:
MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd");
Copy the code
Maven depends on:
<! -- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-hystrix</artifactId>
<version>8.18.0</version>
</dependency>
Copy the code
SLF4J
The SLF4JModule allows you to use SLF4J as the Feign logging module so that you can easily log using Logback, Log4J, etc.
To use SLF4J in Feign, you need to add the SLF4J module and the corresponding logging implementation module (such as Log4J) to your environment variables, and then configure Feign to use Slf4jLogger:
GitHub github = Feign.builder()
.logger(new Slf4jLogger())
.target(GitHub.class, "https://api.github.com");
Copy the code
Maven depends on:
<! -- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>8.18.0</version>
</dependency>
Copy the code
Feign composition
Decoders
Feign.builder() allows you to customize some additional configurations, such as how to decode a response. If an interface method returns a message that is not of type Response, String, byte[], or void, then you need to configure a non-default decoder.
- Here is an example of configuring a JSON decoder (using the feign-gson extension) :
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, https://api.github.com);
Copy the code
If you want to do some extra processing before passing the response to the decoder for processing, you can use the mapAndDecode method. One use case is when using a JSONP service:
// mapAndDecode is not available in 1.8.0...
JsonpApi jsonpApi = Feign.builder()
.mapAndDecode((response, type) -> jsopUnwrap(response, type), new GsonDecoder())
.target(JsonpApi.class, https://some-jsonp-api.com);
Copy the code
Encoders
The easiest way to send a Post request is to pass a String or byte[] parameter. You might also want to add a content-type header like this:
interface LoginClient {
@RequestLine("POST /")
@Headers("Content-Type: application/json")
void login(String content);
}
client.login("{\"user_name\": \"denominator\", \"password\": \"secret\"}");
Copy the code
By configuring a decoder, you can send a secure request body. Here is an example using the Feign-gson extension:
static class Credentials {
final String user_name;
final String password;
Credentials(String user_name, String password) {
this.user_name = user_name;
this.password = password; }}interface LoginClient {
@RequestLine("POST /")
void login(Credentials creds); }... LoginClient client = Feign.builder() .encoder(new GsonEncoder())
.target(LoginClient.class, "https://foo.com");
client.login(new Credentials("denominator"."secret"));
Copy the code
@Body templates
The @body annotation declares a request Body template, which can take parameters and methods@ParamTo match the parameters of the annotation declaration, use as follows:
interface LoginClient {
@RequestLine("POST /")
@Headers("Content-Type: application/xml")
@Body("<login \"user_name\"=\"{user_name}\" \"password\"=\"{password}\"/>")
void xml(@Param("user_name") String user, @Param("password") String password);
@RequestLine("POST /")
@Headers("Content-Type: application/json")
// json curly braces must be escaped!
// The JSON format requires curly braces to be transcoded.
@Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
void json(@Param("user_name") String user, @Param("password") String password); }... client.xml("denominator"."secret"); // <login "user_name"="denominator" "password"="secret"/>
client.json("denominator"."secret"); // {"user_name": "denominator", "password": "secret"}
Copy the code
Headers
- Feign supports setting the request header for either the requesting API or the requesting client, as follows:
- Set the request header for the API
Use @headers to set the static request header
// Set Accept request headers for all methods in BaseApi
@Headers("Accept: application/json")
interface BaseApi<V> {
// Set content-type request header for the put method alone
@Headers("Content-Type: application/json")
@RequestLine("PUT /api/{key}")
void put(@Param("key") String, V value);
}
Copy the code
Set the dynamic value of the request header
@RequestLine("POST /")
@Headers("X-Ping: {token}")
void post(@Param("token") String token);
Copy the code
-
Set key and value as dynamic request headers
-
Some apis need to dynamically determine the use of different request headers depending on the call (e.g. custom metadata header fields such as “x-amz-meta-” or “x-goog-meta-“),
You can use the @headerMap annotation as follows:
// The @headerMap annotation takes precedence over any other set of headers
@RequestLine("POST /")
void post(@HeaderMap Map<String, Object> headerMap);
Copy the code
Set the request header to Target
Sometimes we need to pass different headers to different endpoints in an API implementation, so we can use a custom RequestInterceptor or Target to do this.
This is done by a custom RequestInterceptor
An example of setting the security Header for each Target with a custom Target:
static class DynamicAuthTokenTarget<T> implements Target<T> {
public DynamicAuthTokenTarget(Class
clazz, UrlAndTokenProvider provider, ThreadLocal
requestIdProvider)
; .@Override
public Request apply(RequestTemplate input) {
TokenIdAndPublicURL urlAndToken = provider.get();
if (input.url().indexOf("http") != 0) {
input.insert(0, urlAndToken.publicURL);
}
input.header("X-Auth-Token", urlAndToken.tokenId);
input.header("X-Request-ID", requestIdProvider.get());
returninput.request(); }}... Bank bank = Feign.builder() .target(new DynamicAuthTokenTarget(Bank.class, provider, requestIdProvider));
Copy the code
-
The implementation of this method depends on the custom RequestInterceptor or Target set for the Feign client. Can be used to set request headers for all API requests from a client. For example, it is used to set identity verification information in the header. These methods are executed when the thread executes the API request, so they allow headers to be set dynamically at runtime based on context.
-
For example, you can set different request headers for different threads based on thread-local storage.