What is Feign?
Feign is a lightweight client framework for HTTP requests. Initiate HTTP request calls through interface + annotations, interface-oriented programming, rather than direct calls by encapsulating HTTP request messages in Java. The service consumer gets the service provider's interface, and then invokes it like a local interface method. The actual request is a remote request. Let us more conveniently and elegantly call the HTTP-based API, which is widely used in Spring Cloud solutions. Open source project address: Feign , the official description is as follows:
Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.
Why Feign?
Feign's primary goal is to reduce the complexity of HTTP calls. In the microservice call scenario, we call services based on the HTTP protocol many times. If the service call only uses the HTTP Client framework that provides HTTP call services (eg Apache HttpComponnets, HttpURLConnection OkHttp, etc.), what issues should we pay attention to?
Compared with these HTTP request frameworks, Feign encapsulates the process of HTTP request invocation, and will force users to develop the habit of interface-oriented programming (because Feign itself is interface-oriented).
Demo
Native usage
Taking the Contributors of Feign's GitHub open source project as an example, there are three steps to use Feign natively (here we take a project that uses Gradle for dependency management as an example):
first step: introduces related dependencies: implementation'io.github.openfeign:feign-core:11.0'
Just add the dependency declaration in the dependencies declaration of the project's build.gradle file.
Step 2: declares the HTTP request interface
Use the Java interface and Feign's native annotation @RequestLine to declare the HTTP request interface. From here, you can see that Feign encapsulates the HTTP call details for the user, which greatly reduces the complexity of HTTP calls. Just define the interface.
Step 3: configuration initialization Feign client
The last step is to configure and initialize the client. This step is mainly to set the request address, encoding (Encoder), decoding (Decoder), etc.
By defining the interface and using annotations to describe the information of the interface, you can initiate an interface call. The final request result is as follows:
Combined with Spring Cloud usage
Also take the Contributors of Feign's GitHub open source project as an example. The use of Spring Cloud in combination with the following three steps:
first step: introduces related starter dependency: org.springframework.cloud:spring-cloud-starter-openfeign
Just add the dependency declaration in the dependencies declaration of the project's build.gradle file.
Step 2: Add the @EnableFeignClients annotation to the startup class XXXApplication of the project to enable Feign client functions.
Step 3: creates an HTTP call interface and adds the statement @FeignClient annotation.
The last step is to configure and initialize the client. This step is mainly to set the request address (url), encoding (Encoder), decoding (Decoder), etc., which is different from the original usage method, now we are configuring Feign client properties through the @FeignClient annotation At the same time, the requested URL is also an annotation provided by Spring MVC.
The test class is as follows:
The results of the operation are as follows:
You can see here that the interface just defined is injected through @Autowired, and then you can directly use it to initiate HTTP requests. Is it convenient and concise to use?
Dive into Feign
As can be seen from the first example of native use above, the interface is only defined and there is no specific implementation class, but the method of the interface can be directly called in the test class to complete the interface call. We know that the interface cannot be used in Java. It is used directly, so it can be boldly guessed that Feign silently generated the proxy implementation class of the interface behind it. You can also verify it. Just debug in the test class just now to see what implementation class is actually used by the interface:
From the debug results, we can see that the framework generates the object $Proxy14 of the interface proxy implementation class HardCodedTarget to complete the interface request call, which is consistent with the guess just now. Feign mainly encapsulates HTTP request calls, and its overall architecture is as follows:
The test code is only in GitHub github = Feign.builder().target(GitHub.class, "https://api.github.com"); The Feign framework functions are used, so we choose to go deep into the source code from here. Click to enter and find that it is the method provided by the Feign abstract class. Similarly, we know that the abstract class cannot be initialized, so there must be subclasses. If you have just carefully observed the debug code above, you can find that there is a ReflectiveFeign class, this The class is a subclass of the abstract class Feign. Part of the source code of the abstract class feign.Feign is as follows:
public abstract class Feign {
...
public static Builder builder() {
return new Builder();
}
public abstract <T> T newInstance(Target<T> target);
public static class Builder {
...
private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>();
private Logger.Level logLevel = Logger.Level.NONE;
private Contract contract = new Contract.Default();
private Client client = new Client.Default(null, null);
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
private Encoder encoder = new Encoder.Default();
private Decoder decoder = new Decoder.Default();
private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
private boolean forceDecoding = false;
private List<Capability> capabilities = new ArrayList<>();
// 设置输入打印日志级别
public Builder logLevel(Logger.Level logLevel) {
this.logLevel = logLevel;
return this;
}
// 设置接口方法注解处理器(契约)
public Builder contract(Contract contract) {
this.contract = contract;
return this;
}
// 设置使用的 Client(默认使用 JDK 的 HttpURLConnection)
public Builder client(Client client) {
this.client = client;
return this;
}
// 设置重试器
public Builder retryer(Retryer retryer) {
this.retryer = retryer;
return this;
}
// 设置请求编码器
public Builder encoder(Encoder encoder) {
this.encoder = encoder;
return this;
}
// 设置响应解码器
public Builder decoder(Decoder decoder) {
this.decoder = decoder;
return this;
}
// 设置 404 返回结果解码器
public Builder decode404() {
this.decode404 = true;
return this;
}
// 设置错误解码器
public Builder errorDecoder(ErrorDecoder errorDecoder) {
this.errorDecoder = errorDecoder;
return this;
}
// 设置请求拦截器
public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
this.requestInterceptors.clear();
for (RequestInterceptor requestInterceptor : requestInterceptors) {
this.requestInterceptors.add(requestInterceptor);
}
return this;
}
public <T> T target(Class<T> apiType, String url) {
return target(new HardCodedTarget<T>(apiType, url));
}
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
}
...
}
You can see that the HardCodedTarget object is directly created in the method public <T> T target(Class<T> apiType, String url), which is also the object seen by debug above. Going deeper, you come to the newInstance(Target<T> target) method of feign.Feign, which is an abstract method. In fact, in the subclass ReflectiveFeign, this method is where the interface proxy is generated. Let’s take a look at the source code. What is the implementation logic:
public class ReflectiveFeign extends Feign {
...
private final ParseHandlersByName targetToHandlersByName;
private final InvocationHandlerFactory factory;
private final QueryMapEncoder queryMapEncoder;
ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
QueryMapEncoder queryMapEncoder) {
this.targetToHandlersByName = targetToHandlersByName;
this.factory = factory;
this.queryMapEncoder = queryMapEncoder;
}
@SuppressWarnings("unchecked")
@Override
public <T> T newInstance(Target<T> target) {
// <类名#方法签名, MethodHandler>,key 是通过 feign.Feign.configKey(Class targetType, Method method) 生成的
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
// 将 Map<String, MethodHandler> 转换为 Map<Method, MethodHandler> 方便调用
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
// 默认方法处理器
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
// 跳过 Object 类定于的方法
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
// 默认方法(接口声明的默认方法)使用默认的方法处理器
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
// 接口正常声明的方法(e.g. GitHub.listContributors(String, String))
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 生成 Feign 封装的 InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
// 基于 JDK 动态代理生成接口的代理类(e.g. Github 接口)
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
...
}
The overall process is to generate a proxy object containing FeignInvocationHandler in the method <T> T newInstance(Target<T> target), the FeignInvocationHandler object will hold the Map<Method, MethodHandler> map, and enter the FeignInvocationHandler#invoke method when the proxy object is called. Call the method to obtain the corresponding MethodHandler, and then the MethodHandler completes the processing of the method (processing HTTP requests, etc.).
Let's go deeper into MethodHandler to see how to complete the processing of method HTTP requests. MethodHandler is an interface defined in the feign.InvocationHandlerFactory interface (PS basic knowledge points, the interface can define internal interfaces internally), there are two implementations The classes are DefaultMethodHandler and SynchronousMethodHandler respectively. The first DefaultMethodHandler is used to handle the default methods of the interface, and the second is used to handle the normal interface methods, which are generally handled by this class.
final class SynchronousMethodHandler implements MethodHandler {
...
@Override
public Object invoke(Object[] argv) throws Throwable {
// 获取 RequestTemplate 将请求参数封装成请求模板
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
// 请求重试器
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 执行请求并解码后返回
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
// 发生重试异常则进行重试处理
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 从请求模板 RequestTemplate 构造请求参数对象 Request
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 通过 client(Apache HttpComponnets、HttpURLConnection OkHttp 等)执行 HTTP 请求调用,默认是 HttpURLConnection
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null)
// 对返回结果进行解码操作
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
...
}
So far, Feign's core implementation process is introduced. From the code point of view, the operation of feign.SynchronousMethodHandler is relatively simple, mainly through the client to complete the request, decode the response, and handle exceptions. The overall process is as follows:
Summary
Feign generates a HardCodedTarget type proxy object by giving us the defined target interface (such as GitHub in the example), which is implemented by the JDK dynamic proxy. When generating the proxy, it will generate a corresponding Map<Method, MethodHandler>, this Map according to the annotations. When held by InvocationHandler, when the interface method is called, enter the invoke method of InvocationHandler (why did you enter here? Basic knowledge of JDK dynamic proxy).
Then obtain the corresponding MethodHandler from Map<Method, MethodHandler> according to the called method, and then complete the corresponding processing according to the specified client through MethodHandler. The implementation class DefaultMethodHandler in MethodHandler handles the request processing of the default method (the default method of the interface), SynchronousMethodHandler The implementation class is the implementation of HTTP requests for other methods. This is the main core process of . The Github 160d8452ccaf39. The above is an introduction to the core process of the Feign framework. How does Spring Cloud integrate Feign? Please see the next blog post, so stay tuned.
Thank you all for your likes, favorites and comments. See you next week! The article is continuously updated, and a search on WeChat "mghio" pays attention to this "Java porter & lifelong learner", let's be awesome~.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。