SpringCloud升級之路2020.0.x版-27.OpenFeign的生命週期-建立代理

乾貨滿滿張雜湊發表於2021-10-16

本系列程式碼地址:https://github.com/JoJoTec/spring-cloud-parent

接下來,我們開始分析 OpenFeign 的生命週期,結合 OpenFeign 本身的原始碼。首先是從介面定義建立 OpenFeign 代理開始。我們這裡只關心同步客戶端,因為非同步客戶端目前還在實現中,並且在我們的專案中,非同步響應式的客戶端不用 OpenFeign,而是用的官方的 WebClient

建立 OpenFeign 代理

建立 OpenFeign 代理,主要分為以下幾步:

  1. 使用 Contract 解析介面的每一個方法,生成每一個方法的後設資料列表:List<MethodMetadata> metadata
  2. 根據每一個 MethodMetadata,生成對應的請求模板工廠 RequestTemplate.Factory,用於生成後面的請求。同時,使用這個模板工廠以及其他配置生成對應的方法處理器 MethodHandler,對於同步的 OpenFeign,MethodHandler 實現為 SynchronousMethodHandler。將介面方法與 MethodHandler 一一對應建立對映,結果為 Map<Method, MethodHandler> methodToHandler。對於 Java 8 引入的 interface default 方法,需要用不同 MethodHandler,即 DefaultMethodHandler ,因為這種方法不用代理,不用生成對應的 http 呼叫,其實現為直接呼叫對應的 default 方法程式碼。
  3. 使用 InvocationHandlerFactory 這個工廠,建立 InvocationHandler 用於代理呼叫。
  4. 呼叫 JDK 動態代理生成類方法使用 InvocationHandler 建立代理類。

建立 OpenFeign 代理,主要基於 JDK 的動態代理實現。我們先舉一個簡單的例子,建立一個 JDK 動態代理,用來類比。

JDK 動態代理

使用 JDK 動態代理,需要如下幾個步驟:

1. 編寫介面以及對應的代理類。我們這裡編寫一個簡單的介面和對應的實現類:

public interface TestService {
    void test();
}
public class TestServiceImpl implements TestService {
    @Override
    public void test() {
        System.out.println("TestServiceImpl#test is called");
    }
}

2.建立代理類實現java.lang.reflect.InvocationHandler,並且,在核心方法中,呼叫實際的物件,這裡即我們上面 TestService 的實現類 TestServiceImpl 的物件。

JDK 中有內建的動態代理 API,其核心是 java.lang.reflect.InvocationHandler。我們先來建立一個簡單的 InvocationHandler 實現類:

public class SimplePrintMethodInvocationHandler implements InvocationHandler {
    private final TestService testService;
    public SimplePrintMethodInvocationHandler(TestService testService) {
        this.testService = testService;
    }
    @Override
    public Object invoke(
            //代理物件
            Object proxy,
            //呼叫的方法
            Method method,
            //使用的引數
            Object[] args)
            throws Throwable {
        System.out.println("Invoked method: " + method.getName());
        //進行實際的呼叫
        return method.invoke(testService, args);
    }
}

3.建立代理物件,並使用代理物件呼叫。一般通過 Proxy 的靜態方法去建立,例如:

//首先,建立要代理的物件
TestServiceImpl testServiceImpl = new TestServiceImpl();
//然後使用要代理的物件建立對應的 InvocationHandler
SimplePrintMethodInvocationHandler simplePrintMethodInvocationHandler = new SimplePrintMethodInvocationHandler(testServiceImpl);
//建立代理類,因為一個類可能實現多個介面,所以這裡返回的是 Object,使用者根據自己需要強制轉換成要用的介面
Object proxyInstance = Proxy.newProxyInstance(
        TestService.class.getClassLoader(),
        testServiceImpl.getClass().getInterfaces(),
        simplePrintMethodInvocationHandler
);
//強制轉換
TestService proxied = (TestService) proxyInstance;
//使用代理物件進行呼叫
proxied.test();

這樣,我們就使用了 JDK 的內建動態代理機制實現了一個簡單的動態代理。在 OpenFeign 的使用中,和我們的示例有一點區別。首先,我們只需要定義要代理的介面,不用定義實現類。因為所有的 OpenFeign 介面要做的事情其實都是 HTTP 呼叫,其資訊可以自動從介面定義中生成,我們可以使用統一的物件根據介面定義,承載 OpenFeign 介面定義的請求。在 OpenFeign 中,這個等同於實現物件的,就是根據介面生成的 MethodHandler,在同步的 OpenFeign 中,即 feign.SynchronousMethodHandler。之後,OpenFeign 建立的 InvocationHandler,其實就是將呼叫轉發到對應的 SynchronousMethodHandler 的對應方法。

建立 OpenFeign 代理物件的流程詳解

我們使用前面的例子,來看一下建立代理的流程:

interface GitHub {
    /**
     * 定義get方法,包括路徑引數,響應返回序列化類
     * @param owner
     * @param repository
     * @return
     */
    @RequestLine("GET /repos/{owner}/{repo}/contributors")
    List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repository);

    /**
     * 響應體結構類
     */
    class Contributor {
        String login;
        int contributions;

        public Contributor() {
        }

        public String getLogin() {
            return login;
        }

        public void setLogin(String login) {
            this.login = login;
        }

        public int getContributions() {
            return contributions;
        }

        public void setContributions(int contributions) {
            this.contributions = contributions;
        }
    }
}

/**
 * 基於 FastJson 的反序列化解碼器
 */
static class FastJsonDecoder implements Decoder {
    @Override
    public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
        //讀取 body
        byte[] body = response.body().asInputStream().readAllBytes();
        return JSON.parseObject(body, type);
    }
}
public static void main(String[] args) {
    //建立 Feign 代理的 HTTP 呼叫介面實現
    GitHub github = Feign.builder()
                        //指定解碼器為 FastJsonDecoder
                        .decoder(new FastJsonDecoder())
                        //指定代理類為 GitHub,基址為 https://api.github.com
                        .target(GitHub.class, "https://api.github.com");
    List<GitHub.Contributor> contributors = github.contributors("OpenFeign", "feign");
}

我們這裡關心的其實就是建立 Feign 代理的 HTTP 呼叫介面實現這一步的內部流程。首先我們來看 Feign 的 Builder 的結構,當我們初始化一個 Feign 的 Builder 也就是呼叫 Feign.builder() 時,會建立如下元件(同時也說明以下元件都是可以配置的,如果一些配置之前沒有提到,則可以):

//請求攔截器列表,預設為空
private final List<RequestInterceptor> requestInterceptors = new ArrayList();
//日誌級別,預設不列印任何日誌
private Level logLevel = Level.NONE;
//負責解析類後設資料的 Contract,預設是支援 OpenFeign 內建註解的預設 Contract 
private Contract contract = new Contract.Default();
//承載 HTTP 請求的 Client,預設是基於 Java HttpURLConnection 的 Default Client
private Client client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
//重試器,預設也是 Default
private Retryer retryer = new feign.Retryer.Default();
//預設的日誌 Logger,預設不記錄任何日誌
private Logger logger = new NoOpLogger();
//編碼器解碼器也是預設的
private Encoder encoder = new feign.codec.Encoder.Default();
private Decoder decoder = new feign.codec.Decoder.Default();
//查詢引數編碼,這個我們一般不會修改
private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
//錯誤編碼器,預設為 Default
private ErrorDecoder errorDecoder = new feign.codec.ErrorDecoder.Default();
//各種超時的 Options 走的預設配置
private Options options = new Options();
//用來生成 InvocationHandler 的 Factory 也是預設的
private InvocationHandlerFactory invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
//是否特殊解析 404 錯誤,因為針對 404 我們可能不想丟擲異常,預設是 false
private boolean decode404 = false;
//是否在解碼後立刻關閉 Response,預設為是
private boolean closeAfterDecode = true;
//異常傳播規則,預設是不傳播
private ExceptionPropagationPolicy propagationPolicy = ExceptionPropagationPolicy.NONE;
//是否強制解碼,這個主要為了相容非同步 Feign 引入的配置,我們直接忽略,認為他就是 false 即可
private boolean forceDecoding = false;
private List<Capability> capabilities = new ArrayList();

我們的程式碼中指定了指定解碼器為 FastJsonDecoder,所以 Decoder 就是 FastJsonDecoder 了。最後通過 target(GitHub.class, "https://api.github.com");指定定代理類為 GitHub,基址為 https://api.github.com,這時候就會生成 Feign 代理類,其步驟是:

Feign

public <T> T target(Class<T> apiType, String url) {
  //使用代理介面型別,以及基址建立 HardCodedTarget,他的意思其實就是硬編碼的 Target
  return target(new HardCodedTarget<T>(apiType, url));
}

public <T> T target(Target<T> target) {
  return build().newInstance(target);
}

public Feign build() {
  //將所有元件經過所有的 Capability,從這裡我們可以看出,我們可以實現 Capability 介面來在建立 Feign 代理的時候動態修改元件
  Client client = Capability.enrich(this.client, capabilities);
  Retryer retryer = Capability.enrich(this.retryer, capabilities);
  List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
      .map(ri -> Capability.enrich(ri, capabilities))
      .collect(Collectors.toList());
  Logger logger = Capability.enrich(this.logger, capabilities);
  Contract contract = Capability.enrich(this.contract, capabilities);
  Options options = Capability.enrich(this.options, capabilities);
  Encoder encoder = Capability.enrich(this.encoder, capabilities);
  Decoder decoder = Capability.enrich(this.decoder, capabilities);
  InvocationHandlerFactory invocationHandlerFactory =
      Capability.enrich(this.invocationHandlerFactory, capabilities);
  QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);

  //建立 SynchronousMethodHandler 的 Factory,用於生成 SynchronousMethodHandler,SynchronousMethodHandler 是實際承載 Feign 代理請求的實現類
  SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
      new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
          logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
  //通過方法名稱來區分不同介面方法的後設資料解析,用於生成並路由到對應的代理方法
  ParseHandlersByName handlersByName =
      new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
          errorDecoder, synchronousMethodHandlerFactory);
  //建立 ReflectiveFeign
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}

建立 ReflectiveFeign 之後,會呼叫其中的 newInstance 方法:

ReflectiveFeign

public <T> T newInstance(Target<T> target) {
    //使用前面提到的 ParseHandlersByName 解析後設資料並生成所有需要代理的方法的 MethodHandler,我們這裡分析的是同步 Feign,所以是 SynchronousMethodHandler
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    //將方法與對應的 MethodHandler 一一對應
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        //對於 Object 的方法,直接跳過
        continue;
      } else if (Util.isDefault(method)) {
        //如果是 java 8 中介面的預設方法,就使用 DefaultMethodHandler 處理
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    //使用前面 Builder 中的 InvocationHandlerFactory 建立 InvocationHandler
    InvocationHandler handler = factory.create(target, methodToHandler);
    //使用 InvocationHandler 建立 Proxy
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);
    //將代理與 DefaultMethodHandler 關聯
    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  } 

對於使用前面提到的 ParseHandlersByName 解析後設資料並生成所有需要代理的方法的 MethodHandler 這一步,主要就涉及到了使用 Contract 解析出方法的後設資料,然後將這些後設資料用對應的編碼器繫結用於之後呼叫的編碼:

ReflectiveFeign

public Map<String, MethodHandler> apply(Target target) {
      // 使用 Contract 解析出所有方法的後設資料
      List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
      Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
      // 對於每個解析出的方法後設資料
      for (MethodMetadata md : metadata) {
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
          //有表單的情況
          buildTemplate =
              new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else if (md.bodyIndex() != null) {
          //有 body 的情況
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else {
          //其他情況
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
        }
        if (md.isIgnored()) {
          result.put(md.configKey(), args -> {
            throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
          });
        } else {
          // 使用 SynchronousMethodHandler 的 Factory 生成 SynchronousMethodHandler 
          result.put(md.configKey(),
              factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
        }
      }
      return result;
    }
  }

預設的 InvocationHandlerFactory 生成的 InvocationHandler 是 ReflectiveFeign.FeignInvocationHandler:

static final class Default implements InvocationHandlerFactory {
    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
}

其中的內容是:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // 對於 equals,hashCode,toString 方法直接呼叫
  if ("equals".equals(method.getName())) {
    try {
      Object otherHandler =
          args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
      return equals(otherHandler);
    } catch (IllegalArgumentException e) {
      return false;
    }
  } else if ("hashCode".equals(method.getName())) {
    return hashCode();
  } else if ("toString".equals(method.getName())) {
    return toString();
  }
  //對於其他方法,呼叫對應的 SynchronousMethodHandler 進行處理
  return dispatch.get(method).invoke(args);
}

從這裡我們就可以看出,我們生成的 Proxy,其實就是將請求代理到了 SynchronousMethodHandler 上。

我們這一節詳細介紹了 OpenFeign 建立代理的詳細流程,可以看出,對於同步 Feign 生成的 Proxy,其實就是將介面 HTTP 請求定義的方法請求代理到了 SynchronousMethodHandler 上。下一節我們會詳細分析 SynchronousMethodHandler 做實際 HTTP 呼叫的流程,來搞清楚 Feign 所有元件是如何協調工作的。

微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer

相關文章