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