What is Feign?

Feign is a lightweight client framework for HTTP requests. HTTP request invocation is initiated through interface + annotation and interface oriented programming rather than encapsulation of HTTP request packets in Java. The service consumer takes the service provider’s interface and invokes it as if it were a local interface method, actually making a remote request. It makes it easier and more elegant to call the HTTP-based API, which is widely used in Spring Cloud solutions. Open source project address: Feign, official description:

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 invocation scenario, we invoke a lot of http-based services, If the service invocation only uses the HTTP Client framework that provides HTTP invocation services (e.g. Apache HttpComponnets, HttpURLConnection OkHttp, etc.), what issues should we pay attention to?

In contrast to these HTTP request frameworks, Feign encapsulates the flow of HTTP request invocation and forces the user to get into the habit of interface oriented programming (because Feign itself is interface oriented).

Demo

Native mode of use

Taking the GitHub open-source project of Feign as an example, the native ways use Feign in three steps (taking the projects using Gradle for dependency management as an example) : Step 1: Introduce related dependencies: Implementation ‘IO. Making. Openfeign: feign – core: 11.0’ in the project build. Gradle file depend on the statement of dependencies can add that depend on the statement.

The second step: Feign uses the Java interface and Feign’s native annotation @requestLine to declare the HTTP request interface. From here we can see that Feign encapsulates the DETAILS of HTTP calls to users, greatly reducing the complexity of HTTP calls. Just define the interface.

The last step is to configure the Feign client. This step is mainly to set the request address, Encoder, Decoder and so on.

Interface calls can be made by defining the interface and using annotations to describe information about the interface. The final request result is as follows:

Combine with Spring Cloud usage

Similarly, taking Feign’s GitHub open-source project as an example, there are three steps to use them in combination with Spring Cloud. Step 1: Introduce related starter dependencies: Org. Springframework. Cloud: spring – the cloud – starter – openfeign in the project build. Gradle file depend on the statement of dependencies can add that depend on the statement.

Step 2: Add the @EnableFeignClients annotation to the project’s startup class XXXApplication to enable Feign client functionality.

Step 3: Create the HTTP call interface and add the annotation declaring @FeignClient. The last step is to initialize the client. This step is mainly to set the request address (URL), Encoder (Decoder), Decoder (Decoder), etc. Different from the native use, now we configure the Feign client properties through @FeignClient annotation. The requested URL is also annotated using Spring MVC.

The test classes are as follows:

The running results are as follows:

As you can see, the interface you just defined is injected via @autoWired, and you can use it directly to make HTTP requests.

Dive into Feign

As can be seen from the first example of native use above, there is no specific implementation class for the interface, but you can directly call the interface method in the test class to complete the interface call. We know that in Java, the interface cannot be directly used. So it’s safe to assume that Feign is quietly generating the proxy implementation class for the interface, or to verify this, just debug the test class to see what implementation class the interface actually uses:

The debug result shows that the framework generates the object $Proxy14 of the interface’s proxy implementation class HardCodedTarget to complete the interface request call. Feign encapsulates the HTTP request invocation. Its overall architecture is as follows:

GitHub = feign.builder ().target(GitHub. Class, “api.github.com”); We use the Feign framework functionality, so we choose to dig into the source code from here, click into the Feign abstract class provided by the method, also we know that abstract classes can not be initialized, so there must be a subclass, if you have looked at the above debug code carefully, You can see that there is a ReflectiveFeign class, which is a subclass of the abstract Feign class. Abstract class feign.Feign:

public abstract class Feign {...public static Builder builder(a) {
    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<>();

    // Set the input print log level
    public Builder logLevel(Logger.Level logLevel) {
      this.logLevel = logLevel;
      return this;
    }

    // Set the interface method annotation handler (contract)
    public Builder contract(Contract contract) {
      this.contract = contract;
      return this;
    }

    // Set the Client to use (default: HttpURLConnection of the JDK)
    public Builder client(Client client) {
      this.client = client;
      return this;
    }

    // Set the retry
    public Builder retryer(Retryer retryer) {
      this.retryer = retryer;
      return this;
    }

    // Set the request encoder
    public Builder encoder(Encoder encoder) {
      this.encoder = encoder;
      return this;
    }

    // Set the response decoder
    public Builder decoder(Decoder decoder) {
      this.decoder = decoder;
      return this;
    }

    // Set the 404 return result decoder
    public Builder decode404(a) {
      this.decode404 = true;
      return this;
    }

    // Set error decoder
    public Builder errorDecoder(ErrorDecoder errorDecoder) {
      this.errorDecoder = errorDecoder;
      return this;
    }

    // Set request interceptor
    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) {
      returnbuild().newInstance(target); }}... }Copy the code

You can see that the HardCodedTarget object is created directly in the public T target(Class apiType, String URL) method. This object is also the object seen by debug above. Further, we come to feign.Feign’s newInstance(Target Target) method, which is an abstract method. In fact, in subclass ReflectiveFeign, this method is where interface proxy implementation is generated. Here is a look at the implementation logic through the source code:

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) {
    // < Class name # Method signature, MethodHandler>, the key is generated by feign.feign.configKey (Class targetType, Method Method)
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    // Convert Map
      
        to Map
       ,>
      ,>
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    // The default method handler
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      // Skip the methods specified by the Object class
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        // The default method (the default method declared by the interface) uses the default method handler
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        // ways to declare interfaces normally (e.g. GitHub. Listpolymorphism (String, String))methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); }}// Generate Feign wrapped InvocationHandler
    InvocationHandler handler = factory.create(target, methodToHandler);
    // JDK dynamic proxy generation interface based on the proxy class (e.g. Github interface)
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        newClass<? >[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    returnproxy; }... }Copy the code

The overall process is to generate a proxy object with FeignInvocationHandler in method T newInstance(Target Target), The FeignInvocationHandler object holds the Map<Method, MethodHandler> Map, and the proxy object calls FeignInvocationHandler#invoke, Get the corresponding MethodHandler based on the method called, and then the MethodHandler completes the method processing (handling HTTP requests, etc.).

Go deeper MethodHandler below to see how to complete the method HTTP request processing, MethodHandler is an interface defined in feign. InvocationHandlerFactory interface (p.. There are two implementation classes called DefaultMethodHandler and SynchronousMethodHandler. The first DefaultMethodHandler handles the interface’s default methods. The second is used to handle normal interface methods, which are normally handled by this class.


final class SynchronousMethodHandler implements MethodHandler {...@Override
  public Object invoke(Object[] argv) throws Throwable {
    // get RequestTemplate to encapsulate the request parameters as a RequestTemplate
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    // Request retries
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        // Execute the request and decode it and return
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          // If an exception occurs, try again
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if(propagationPolicy == UNWRAP && cause ! =null) {
            throw cause;
          } else {
            throwth; }}if(logLevel ! = Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); }continue; }}}Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // Construct the Request parameter object Request from the RequestTemplate
    Request request = targetRequest(template);

    if(logLevel ! = Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response;long start = System.nanoTime();
    try {
      // Execute HTTP request calls through the client (Apache HttpComponnets, HttpURLConnection OkHttp, etc.). The default is 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)
      // Decode the returned result
      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;
      throwe; }}... }Copy the code

At this point, the core of Feign implementation process introduction, look from the code Feign. SynchronousMethodHandler operation is relatively simple, mainly by the client to complete the request, the response to decode and exception handling operation, the whole process is as follows:

Summary

Feign generates a proxy object of type HardCodedTarget for our defined target interface (such as GitHub in our example), implemented by JDK dynamic proxies, This Map is held by the InvocationHandler. When the interface Method is called, Enter the invoke method of InvocationHandler (why here? JDK dynamic proxy basics).

We then get the corresponding MethodHandler from Map

based on the Method called, and use MethodHandler to handle the corresponding process based on the specified client. The implementation class in MethodHandler, DefaultMethodHandler, handles requests for default methods (the default methods of the interface), and the SynchronousMethodHandler implementation class implements HTTP requests for other methods, This is the core process of Feign, and the source code has been uploaded to Github. This is the core process for implementing the Feign framework. How does Spring Cloud integrate Feign? Please look forward to the next post.
,>


Thanks for your likes, favorites and comments, and we’ll see you next week! This article continues to update, wechat search “mghio” to follow this “Java porter & lifelong learner”, together awesome ~.