Column series: SpringCloud column series

Series of articles:

SpringCloud source series (1) – registry initialization for Eureka

SpringCloud source code series (2) – Registry Eureka service registration, renewal

SpringCloud source code series (3) – Registry Eureka crawl registry

SpringCloud source code series (4) – Registry Eureka service offline, failure, self-protection mechanism

SpringCloud source series (5) – Registry EurekaServer cluster for Eureka

SpringCloud source Code Series (6) – Summary of the Registry Eureka

SpringCloud source series (7) – load balancing Ribbon RestTemplate

SpringCloud source series (8) – load balancing Ribbon core principles

SpringCloud source series (9) – load balancing Ribbon core components and configuration

SpringCloud source Series (10) – HTTP client component of load balancing Ribbon

SpringCloud Source Series (11) – Retries and summaries of the Load Balancing Ribbon

SpringCloud source Code Series (12) – Basic usage of Service invocation Feign

SpringCloud source Code Series (13) – Service invocation of Feign’s scanning @FeignClient annotation interface

SpringCloud source code series (14) – Service calls to Feign build @FeignClient interface dynamic proxy

As analyzed in the previous article, ReflectiveFeign was eventually built in the build() method of Feign.Builder and then used ReflectiveFeign’s newInstance method to create dynamic proxies. This dynamic proxy agent object is ReflectiveFeign FeignInvocationHandler. Eventually clients must be used for load-balancing requests. This section takes a look at how Feign uses dynamic proxies to initiate HTTP requests.

FeignClient Dynamic proxy request

Use FeignClient interface, injection is actually a dynamic proxy objects, call interface method would be the actuator ReflectiveFeign. FeignInvocationHandler, As you can see from the Invoke method of FeignInvocationHandler, you get the method handler to execute based on method, and then execute the method. The actual type of MethodHandler is SynchronousMethodHandler.

static class FeignInvocationHandler implements InvocationHandler {
    private final Target target;
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        / /...
        // Get a MethodHandler based on method and execute the method
        returndispatch.get(method).invoke(args); }}Copy the code

Looking at the SynchronousMethodHandler’s Invoke method, the core logic is in two steps:

  • Start by building a request template based on the request parametersRequestTemplateIs used to process URI templates and parameters, such as replacing placeholders in URIs, concatenating parameters, and so on.
  • And then calledexecuteAndDecodeThe request is executed and the corresponding result is decoded back.
public Object invoke(Object[] argv) throws Throwable {
    // Build request templates, such as URL parameters, request parameters, etc
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        // Execute and decode
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        // Retry. The default is never retry
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if(propagationPolicy == UNWRAP && cause ! =null) {
            throw cause;
          } else {
            throwth; }}continue; }}}Copy the code

As you can see, after processing, the placeholders on the URI are replaced by parameters and the request parameters are concatenated.

Perform requests and decodes

Moving on to executeAndDecode, there are three main steps:

  • First calltargetRequestThe method, basically, is traversalRequestInterceptorRequestTemplate is customized and then calledHardCodedTargettargetMethod to convert the RequestTemplate toRequestRequest object, Request encapsulates the Request address, Request header, body and other information.
  • The request is then executed using the client, which isLoadBalancerFeignClient, this is the load balancing request.
  • And finally the decoderdecoderTo parse the response result and convert the result to the return type of the interface.
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // Get the Request object Request
    Request request = targetRequest(template);

    Response response;
    try {
      // Call client to execute the request, client => LoadBalancerFeignClient
      response = client.execute(request, options);
      // Build Response
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
      / /...
    }

    if(decoder ! =null) {
      // Convert the returned data to the return type of the interface using a decoder
      return decoder.decode(response, metadata.returnType());
    }

    //....
}
// Apply the interceptor to the RequestTemplate and use target to get the Request from the RequestTemplate
Request targetRequest(RequestTemplate template) {
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    // target => HardCodedTarget
    return target.apply(template);
}
Copy the code

HardCodedTarget is hardcoded and there is no way to customize it. Look at the Apply method, which handles the address of the RequestTemplate template and generates the completed request address. Finally, the Request Request object is returned.

public Request apply(RequestTemplate input) {
  if (input.url().indexOf("http") != 0) {
    // url() => http://demo-producer
    // Input. target Processes the request template
    input.target(url());
  }
  return input.request();
}
Copy the code

You can see that after applying the HardCodedTarget, the URL prefix is concatenated.

LoadBalancerFeignClient Load balancing

LoadBalancerFeignClient is Feign’s core load balancing component. It is the default implementation of Feign’s network request component Client. LoadBalancerFeignClient is the final load balancing request using FeignLoadBalancer.

The Execute method of LoadBalancerFeignClient executes a load balancing request from here to the end of the Ribbon.

  • As you can see, the request is also wrapped to ClientRequest first, and the implementation class isFeignLoadBalancer.RibbonRequest. Note that the first RibbonRequest Client parameter is the proxy object of the LoadBalancerFeignClient, when Apache HttpClient is enabledApacheHttpClient.
  • Then get the client configuration, that isThe Ribbon client configuration also works with Feign.
  • Finally, the load balancer is obtainedFeignLoadBalancer, and then performs the load balancing request.
public Response execute(Request request, Request.Options options) throws IOException {
    try {
        URI asUri = URI.create(request.url());
        // Client name: demo-producer
        String clientName = asUri.getHost();
        URI uriWithoutHost = cleanUrl(request.url(), clientName);
        / / encapsulation ClientRequest = > FeignLoadBalancer RibbonRequest
        FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                this.delegate, request, uriWithoutHost);
        // Client load balancing ribbon. Demo-producer.*
        IClientConfig requestConfig = getClientConfig(options, clientName);
        // lbClient => FeignLoadBalancer to execute the load balancing request
        return lbClient(clientName)
                .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
    }
    catch (ClientException e) {
        / /...}}private FeignLoadBalancer lbClient(String clientName) {
    return this.lbClientFactory.create(clientName);
}
Copy the code

Enter the executeWithLoadBalancer method, which is the same as the Ribbon source code, and verify that Feign does load balancing requests based on the Ribbon.

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    // The load balancer runs the command
    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

    try {
        return command.submit(
            new ServerOperation<T>() {
                @Override
                public Observable<T> call(Server server) {
                    // Reconstruct the URI address with Server information
                    URI finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                        // Actually calls the execute method of LoadBalancerFeignClient
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    }
                    catch (Exception e) {
                        return Observable.error(e);
                    }
                }
            })
            .toBlocking()
            .single();
    } catch (Exception e) {
        //....}}Copy the code

After refactoring the URI, the FeignLoadBalancer’s execute method is actually called to perform the final HTTP call. Take a look at the FeignLoadBalancer’s execute method, which ultimately uses the proxy’s HTTP client to execute the request.

By Default, this is client. Default, which uses HttpURLConnection to perform HTTP requests. ApacheHttpClient is enabled. Okhttp is enabled, which is OkHttpClient.

Note that FeignClient allows you to set the Ribbon timeout, but when you execute FeignLoadBalancer, the Ribbon timeout overwrites the Feign timeout. The Ribbon’s timeout is final.

public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException {
    Request.Options options;
    if(configOverride ! =null) {
        // The Ribbon timeout overrides the feign timeout
        RibbonProperties override = RibbonProperties.from(configOverride);
        options = new Request.Options(override.connectTimeout(this.connectTimeout),
                override.readTimeout(this.readTimeout));
    }
    else {
        options = new Request.Options(this.connectTimeout, this.readTimeout);
    }
    // request.client() HTTP client object
    Response response = request.client().execute(request.toRequest(), options);
    return new RibbonResponse(request.getUri(), response);
}
Copy the code

A diagram summarizes Feign load balancing requests

For the Ribbon source code analysis, see the previous article about Ribbon. How Ribbon obtains the Server from the Eureka registry is not analyzed.

The following diagram summarizes the flow of Feign load balancing requests:

  • First the service is scanned and parsed when started@FeignClientAnnotated interface and generated proxy classes to inject into the container. This is the proxy class that we injected into the @FeignClient interface.
  • When an interface method is called, it is intercepted by a proxy object and enteredReflectiveFeign.FeignInvocationHandlerinvokeMethod to perform the request.
  • FeignInvocationHandler retrieves the built method handler based on the interface method being calledSynchronousMethodHandlerAnd then call itsinvokeMethod to perform the request.
  • In the SynchronousMethodHandler’s Invoke method, the request template is first built based on the request parametersRequestTemplate, which handles placeholders in parameters, concatenates request parameters, processes parameters in body, and so on.
  • Then convert the RequestTemplate toRequestIn the process of transformation:
    • First useRequestInterceptorHandles request templates, so we can customize the RequestTemplate by customizing interceptors.
    • After useTarget (HardCodedTarget)Address to process the request, concatenated with the service name prefix.
    • The last call to RequestTemplaterequestMethod to get the Request object.
  • Once we get the Request, we call the LoadBalancerFeignClientexecuteMethod to execute the request and get the result of the requestResponse:
    • ClientRequest is constructed and the load balancer is obtainedFeignLoadBalancerThe load balancing request is then executed.
    • The load balancing request finally entersAbstractLoadBalancerAwareClient, executeWithLoadBalancerMethod, a LoadBalancerCommand is built and a ServerOperation is submitted.
    • LoadBalancerCommand obtains a Server based on the service name through LoadBalancerContext.
    • In ServerOperation, the URI is refactored based on the Server information, replacing the service name with a specific IP address, and then the actual HTTP call can be made.
    • By default, the underlying component used for HTTP calls is HttpURLConnection; Enable okhttp, which is okhttp’s OkHttpClient; Httpclient is enabled, which is apache’s HttpClient.
    • The most popular is to use the HTTP client component to execute the request and get the responseResponse.
  • Once you get the Response, the decoder is usedDecoderParses the response result and returns the return type defined by the interface method.

The core component of LoadBalancerClient is LoadBalancerClient. For detailed source code analysis, see the two source code analysis articles in the Ribbon. The principle of LoadBalancerClient load balancing can be seen in the following figure.