本文共 9194 字,大约阅读时间需要 30 分钟。
在上篇博文中,我对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和代理对象的创建后,我们来认识了解方法的请求执行过程。
在调用代理对象的方法时,通过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进行处理。
在获取到方法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方法中请求的执行主要分为两步:
接下来,我们再对每步的执行原理进行学习。
再回忆下之前的通过ParseHandlersByName的apply方法得到接口中方法对应的MethodHandler的处理过程,其中先通过Contract得到方法的元数据MethodMetadata,然后根据MethodMetadata来创建不同方法的RequestTemplate.Factory对象。
分别有BuildFormEncodedTemplateFromArgs,BuildEncodedTemplateFromArgs,BuildTemplateByResolvingArgs。前两个类都继承自BuildTemplateByResolvingArgs。关于三种类型的创建,请看这篇博文。
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])); } MapvarBuilder = 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 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, Mapvariables) { return mutable.resolve(variables);}
在创建完RequestTemplate对象后,通过配置的重试控制器Retryer的clone()方法,为每个请求创建一个Retryer实例。在之前的博文中介绍其使用方法使提到,自实现Retryer类必须实现clone()方法。
接下来,进入循环请求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 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发送请求
这里我使用的是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官网上已经介绍实现了异步处理请求的方式,详见
接下来,我将继续通过源码对OpenFeign后续的请求结果处理和请求重试的工作原理进行介绍学习。
参考资料:
https://github.com/OpenFeign/feign https://www.jianshu.com/p/64e8e296aa44 https://www.jianshu.com/p/8c7b92b4396c转载地址:http://hdeii.baihongyu.com/