博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OpenFeign学习(四):OpenFeign的方法同步请求执行
阅读量:4096 次
发布时间:2019-05-25

本文共 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和代理对象的创建后,我们来认识了解方法的请求执行过程。

方法请求执行

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。关于三种类型的创建,请看这篇博文。

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
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
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
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官网上已经介绍实现了异步处理请求的方式,详见

接下来,我将继续通过源码对OpenFeign后续的请求结果处理和请求重试的工作原理进行介绍学习。


参考资料:

https://github.com/OpenFeign/feign
https://www.jianshu.com/p/64e8e296aa44
https://www.jianshu.com/p/8c7b92b4396c

转载地址:http://hdeii.baihongyu.com/

你可能感兴趣的文章
Pandas 踩坑 dataframe.index.values
查看>>
在 PyPI 上发布 python 包
查看>>
DRF项目工程基础包
查看>>
inception安装使用
查看>>
Django Rest Swagger生成api文档
查看>>
docker-compose 环境变量问题
查看>>
vue 配置后台接口方式
查看>>
drf jwt 认证
查看>>
Vue 初体验
查看>>
记一次sentry部署过程
查看>>
python 开发环境配置
查看>>
Docker 部署Django项目
查看>>
Docker部署Vue 工程包
查看>>
CentOS7使用Docker Overlay存储驱动并更改日志驱动
查看>>
CentOS7修改Docker镜像默认存储位置
查看>>
python 变量进阶(理解)
查看>>
scrapy 框架入门
查看>>
scrapy爬取伯乐在线文章
查看>>
Scrapy Item Loaders使用方法
查看>>
Yum自动下载RPM包及其所有依赖的包
查看>>