天天看點

OpenFeign學習(四):OpenFeign的方法同步請求執行說明正文

說明

在上篇博文《OpenFeign學習(三):OpenFeign配置生成代理對象》中,我對OpenFeign的整體工作流程做了簡單的介紹,并且通過源碼,介紹學習了OpenFeign配置建立代理對象的原理。在本篇博文中,我将繼續通過源碼對OpenFeign的方法請求工作流程的原理進行介紹學習。

正文

在閱讀請求部分的源碼前,我們先回顧下上篇博文的内容,包括OpenFeign的整體工作流程圖和配置建立代理對象。

在建立代理對象時,通過源碼可知,OpenFeign是通過抽象類Feign的内部構造器Builder進行代理對象的參數配置和建立。

其中在建立JDK動态代理的InvocationHandler對象時,使用的是自實作的InvocationHandlerFactory接口的實作類Default,該類建立了FeignInvocationHandler對象。

FeignInvocationHandler類實作了InvocationHandler接口,該類的構造函數的參數為 代理接口基本資訊Target 和 接口方法對象對應的MethodHandler字典 Map<Method, MethodHandler> dispatch。

通過代理對象的配置建立過程的源碼,我們可以知道,接口中方法對應的Handler建立,是先通過配置的協定Contract對接口中使用的注解進行解析處理得到設定的資訊MethodMetadata後,再通過SynchronousMethodHandler的工廠對象synchronousMethodHandlerFactory建立方法對應的MethodHandler。

在解析得到方法對應的MethodHandler後,OpenFeign使用JDK動态代理的方式Proxy.newProxyInstance建立了接口的代理對象。

在回顧了OpenFeign的InvocationHandler, MethodHandler和代理對象的建立後,我們來認識了解方法的請求執行過程。

方法請求執行

FeignInvocationHandler.invoke

在調用代理對象的方法時,通過InvocationHandler将不同方法dispatch到不同MethodHandler進行處理。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (!"equals".equals(method.getName())) {
        if ("hashCode".equals(method.getName())) {
            return this.hashCode();
        } else {
            return "toString".equals(method.getName()) ? this.toString() : ((MethodHandler)this.dispatch.get(method)).invoke(args);
        }
    } else {
        try {
            Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
            return this.equals(otherHandler);
        } catch (IllegalArgumentException var5) {
            return false;
        }
    }
}
           

通過源碼可以看到,除了equals, hashCode, toString方法,其他方法的執行都是通過字典Map<Method, MethodHandler> dispatch來找到對應的SynchronousMethodHandler進行處理。

SynchronousMethodHandler.invoke

在擷取到方法Method對應的MethodHandler後,調用其invoke方法,進入到方法的執行過程。我們先通過源碼了解下在該方法中是如何進行請求執行的。

public Object invoke(Object[] argv) throws Throwable {
    // 通過在建立MehtodHandler時設定RequestTemplate的factory來建立請求對應的RequestTemplate對象
    RequestTemplate template = this.buildTemplateFromArgs.create(argv);
    Options options = this.findOptions(argv);
    // 針對每次請求都會建立新的Retryper執行個體
    Retryer retryer = this.retryer.clone();

    // 循環執行直到請求成功或者重試失敗抛出異常
    while(true) {
        try {
            return this.executeAndDecode(template, options);
        } catch (RetryableException var9) {
            RetryableException e = var9;

            try {
                retryer.continueOrPropagate(e);
            } catch (RetryableException var8) {
                Throwable cause = var8.getCause();
                if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                    throw cause;
                }

                throw var8;
            }

            if (this.logLevel != Level.NONE) {
                this.logger.logRetry(this.metadata.configKey(), this.logLevel);
            }
        }
    }
}
           

通過源碼可以看到,在invoke方法中請求的執行主要分為兩步:

  • 不同方法請求對應的RequestTemplate對象的建立
  • 循環執行請求,直到請求成功或重試失敗抛出異常

接下來,我們再對每步的執行原理進行學習。

RequestTemplate.Factory.create

再回憶下之前的通過ParseHandlersByName的apply方法得到接口中方法對應的MethodHandler的處理過程,其中先通過Contract得到方法的中繼資料MethodMetadata,然後根據MethodMetadata來建立不同方法的RequestTemplate.Factory對象。

分别有BuildFormEncodedTemplateFromArgs,BuildEncodedTemplateFromArgs,BuildTemplateByResolvingArgs。前兩個類都繼承自BuildTemplateByResolvingArgs。關于三種類型的建立,請看這篇博文《Feign原了解析(三)參數的處理》。

public RequestTemplate create(Object[] argv) {
    RequestTemplate mutable = RequestTemplate.from(this.metadata.template());
    mutable.feignTarget(this.target);
    if (this.metadata.urlIndex() != null) {
        int urlIndex = this.metadata.urlIndex();
        Util.checkArgument(argv[urlIndex] != null, "URI parameter %s was null", new Object[]{urlIndex});
        mutable.target(String.valueOf(argv[urlIndex]));
    }

    Map<String, Object> varBuilder = new LinkedHashMap();
    Iterator var4 = this.metadata.indexToName().entrySet().iterator();

    while(true) {
        Entry entry;
        int i;
        Object value;
        do {
            if (!var4.hasNext()) {
                RequestTemplate template = this.resolve(argv, mutable, varBuilder);
                if (this.metadata.queryMapIndex() != null) {
                    Object value = argv[this.metadata.queryMapIndex()];
                    Map<String, Object> queryMap = this.toQueryMap(value);
                    template = this.addQueryMapQueryParameters(queryMap, template);
                }

                if (this.metadata.headerMapIndex() != null) {
                    template = this.addHeaderMapHeaders((Map)argv[this.metadata.headerMapIndex()], template);
                }

                return template;
            }

            entry = (Entry)var4.next();
            i = (Integer)entry.getKey();
            value = argv[(Integer)entry.getKey()];
        } while(value == null);

        if (this.indexToExpander.containsKey(i)) {
            value = this.expandElements((Expander)this.indexToExpander.get(i), value);
        }

        Iterator var8 = ((Collection)entry.getValue()).iterator();

        while(var8.hasNext()) {
            String name = (String)var8.next();
            varBuilder.put(name, value);
        }
    }
}
           

在該方法中,我們可以看到有嵌套了兩層循環。在第一層循環中将請求的參數對應放入Map集合 varBuilder中。全部處理完畢後,在第二層循環中,通過 RequestTemplate template = this.resolve(argv, mutable, varBuilder);來解析建立RequestTemplate對象。

這裡是我們要重點注意的地方。根據實際建立的RequestTemplate對參數進行編碼。

上面提到BuildFormEncodedTemplateFromArgs,BuildEncodedTemplateFromArgs這兩個類都繼承自BuildTemplateByResolvingArgs類,他們都重寫了resolve()方法,根據配置的encoder對表單參數或請求體進行編碼。

BuildFormEncodedTemplateFromArgs

this.encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, mutable);

           

BuildEncodedTemplateFromArgs

this.encoder.encode(body, this.metadata.bodyType(), mutable);

           

之後都調用了父類BuildTemplateByResolvingArgs的resolve方法

protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map<String, Object> variables) {
    return mutable.resolve(variables);
}
           

在建立完RequestTemplate對象後,通過配置的重試控制器Retryer的clone()方法,為每個請求建立一個Retryer執行個體。在之前的博文中介紹其使用方法使提到,自實作Retryer類必須實作clone()方法。

接下來,進入循環請求executeAndDecode。

executeAndDecode

該方法的源碼有些長,我們将它分為兩部分進行介紹,分别為請求的執行和結果的處理。這裡先介紹請求執行部分:

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // 根據RequestTemplate來建立統一的實際請求Request對象
    Request request = this.targetRequest(template);
    if (this.logLevel != Level.NONE) {
        this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
    }

    long start = System.nanoTime();

    Response response;
    try {
        // 通過配置的Client執行請求
        response = this.client.execute(request, options);
        // 根據請求結果構造統一的響應Response對象
        response = response.toBuilder().request(request).requestTemplate(template).build();
    } catch (IOException var16) {
        if (this.logLevel != Level.NONE) {
            this.logger.logIOException(this.metadata.configKey(), this.logLevel, var16, this.elapsedTime(start));
        }

        throw FeignException.errorExecuting(request, var16);
    }
}
           

通過源碼可以看到,在請求執行部分中也分為了兩步:

  • 統一請求Request對象的建立
  • 根據配置Client發送請求

接下來再對這兩步的執行原理進行了解:

Request建立

Request targetRequest(RequestTemplate template) {
    Iterator var2 = this.requestInterceptors.iterator();

    while(var2.hasNext()) {
        RequestInterceptor interceptor = (RequestInterceptor)var2.next();
        interceptor.apply(template);
    }

    return this.target.apply(template);
}
           

可以看到先通過循環的方式執行了配置的請求攔截器。再通過Target的apply方法建立Request,之前我們提到在構造器Builder的target方法中,先通過接口class和url建立了HardCodedTarget對象。

public Request apply(RequestTemplate input) {
    if (input.url().indexOf("http") != 0) {
        input.target(this.url());
    }

    return input.request();
}
           

先設定了url,再調用了RequestTemplate的request()方法。

public Request request() {
    if (!this.resolved) {
        throw new IllegalStateException("template has not been resolved.");
    } else {
        return Request.create(this.method, this.url(), this.headers(), this.body, this);
    }
}
           

根據請求方法method,url,頭部參數headers,請求體body來建立Request對象。

最後通過Client發送請求

Client.execute

這裡我使用的是OkHttpClient,通過源碼簡單了解下如何請求:

public Response execute(feign.Request input, Options options) throws IOException {
    okhttp3.OkHttpClient requestScoped;
    if (this.delegate.connectTimeoutMillis() == options.connectTimeoutMillis() && this.delegate.readTimeoutMillis() == options.readTimeoutMillis() && this.delegate.followRedirects() == options.isFollowRedirects()) {
        requestScoped = this.delegate;
    } else {
        requestScoped = this.delegate.newBuilder().connectTimeout((long)options.connectTimeoutMillis(), TimeUnit.MILLISECONDS).readTimeout((long)options.readTimeoutMillis(), TimeUnit.MILLISECONDS).followRedirects(options.isFollowRedirects()).build();
    }

    Request request = toOkHttpRequest(input);
    okhttp3.Response response = requestScoped.newCall(request).execute();
    return toFeignResponse(response, input).toBuilder().request(input).build();
}
           

先根據如果配置了client參數Options時,針對該請求建立對應的OkHttpClient對象,否則使用預設配置時建立的Client對象,之後送出請求,再對請求結果進行封裝。

OkHttpClient的使用請看我之前的博文。

至此,OpenFeign的方法請求執行流程介紹完畢。我們可以知道:

OpenFeign通過SynchronousMethodHandler來同步處理方法請求,在建立RequestTemplate對象時,對表單參數或者請求體進行了編碼,之後再建立統一的請求對象Request,通過配置的Client送出請求,再對結果進行了統一封裝。

現在OpenFeign官網上已經介紹實作了異步處理請求的方式,詳見 Async execution via CompletableFuture

接下來,我将繼續通過源碼對OpenFeign後續的請求結果處理和請求重試的工作原理進行介紹學習。

參考資料:

https://github.com/OpenFeign/feign

https://www.jianshu.com/p/64e8e296aa44

https://www.jianshu.com/p/8c7b92b4396c

繼續閱讀