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

本系列程式碼地址://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,基址為 //api.github.com
                        .target(GitHub.class, "//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, "//api.github.com");指定定代理類為 GitHub,基址為 //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