說明
在上篇博文《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