Feign的内部组件

在说feign的整个初始化过程之前,我们先来看看feign的内部组件有哪些

我们直接来到Feign这个类,里面有一个内部类Builder,不难猜到创建一个Feign对象应该就是使用构造者模式

public static class Builder {private final List<RequestInterceptor> requestInterceptors =new ArrayList<RequestInterceptor>();private Logger.Level logLevel = Logger.Level.NONE;private Contract contract = new Contract.Default();private Client client = new Client.Default(null, null);private Retryer retryer = new Retryer.Default();private Logger logger = new NoOpLogger();private Encoder encoder = new Encoder.Default();private Decoder decoder = new Decoder.Default();private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();private ErrorDecoder errorDecoder = new ErrorDecoder.Default();private Options options = new Options();private InvocationHandlerFactory invocationHandlerFactory =new InvocationHandlerFactory.Default();private boolean decode404;private boolean closeAfterDecode = true;private ExceptionPropagationPolicy propagationPolicy = NONE;// 省略上面各组件的构建方法public <T> T target(Class<T> apiType, String url) {return target(new HardCodedTarget<T>(apiType, url));}public <T> T target(Target<T> target) {return build().newInstance(target);}public Feign build() {SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,logLevel, decode404, closeAfterDecode, propagationPolicy);ParseHandlersByName handlersByName =new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,errorDecoder, synchronousMethodHandlerFactory);return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);}
}

从上面我们可以看到Feign里面的组件很多,其中比较重要的就是Encoder,Decoder编解码组件,Contract约定组件(用来解析feign的请求注解以及路径,与springcloud整合之后具体用的是SpringMVCContract,它能够识别解析SpringMVC的注解),Options请求连接超时时间,响应超时时间等等,最后有一个特别重要的组件就是Client组件,这个组件估计不难猜到就是用来发起客户端请求的组件,因为客户端请求框架又很多,feign原生默认用的是jdk自带的HttpURLConnection去进行客户端的网络请求,但是与springcloud整合之后,就能够选择HttpClient或者OkHttp进行整合了

Feign的调用原理

为什么feign能够通过一个没有实现类的接口直接去调用其方法?

答案很简单就是动态代理,我们下面来看下feign是怎么对接口生成代理对象的

 public <T> T target(Target<T> target) {return build().newInstance(target);}public Feign build() {SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,logLevel, decode404, closeAfterDecode, propagationPolicy);ParseHandlersByName handlersByName =new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,errorDecoder, synchronousMethodHandlerFactory);return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);}

我们来看Build构造类的build方法,这个方法返回了一个ReflectiveFeign对象,我们来看这个类

public <T> T newInstance(Target<T> target) {// 返回把接口类里面的接口方法的名字(类名#方法名)以及这个方法对应的MethodHandler(SynchronousMethodHandler)// 具体的发起请求的逻辑就在MethodHandler中Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);// method对象->MethodHandler对象Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();for (Method method : target.type().getMethods()) {if (method.getDeclaringClass() == Object.class) {continue;} else if (Util.isDefault(method)) {DefaultMethodHandler handler = new DefaultMethodHandler(method);defaultMethodHandlers.add(handler);methodToHandler.put(method, handler);} else {methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));}}// jdk动态代理InvocationHandler handler = factory.create(target, methodToHandler);T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}return proxy;
}

可以看到这个类有一个newInstance方法,这个方法里面使用了jdk的动态代理并且返回代理对象,那么当我们调用接口的方法时候,代理对象又是如何代理方法的呢?我们直接看它的代理逻辑类InvocationHandler(FeignInvocationHandler)

static class FeignInvocationHandler implements InvocationHandler {private final Target target;private final Map<Method, MethodHandler> dispatch;FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {this.target = checkNotNull(target, "target");this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("equals".equals(method.getName())) {try {Object otherHandler =args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;return equals(otherHandler);} catch (IllegalArgumentException e) {return false;}} else if ("hashCode".equals(method.getName())) {return hashCode();} else if ("toString".equals(method.getName())) {return toString();}// 直接根据请求的方法得到对应的MethodHandlerreturn dispatch.get(method).invoke(args);}@Overridepublic boolean equals(Object obj) {if (obj instanceof FeignInvocationHandler) {FeignInvocationHandler other = (FeignInvocationHandler) obj;return target.equals(other.target);}return false;}@Overridepublic int hashCode() {return target.hashCode();}@Overridepublic String toString() {return target.toString();}
}

上面的那个dispatch属性就是之前的methodToHandler,通过要调用的method得到MethodHandler并且执行它的invoke方法,invoke方法里面就是发起服务请求

feign.SynchronousMethodHandler#invoke

@Override
public Object invoke(Object[] argv) throws Throwable {// 根据方法参数构造出请求模板对象RequestTemplate template = buildTemplateFromArgs.create(argv);// 可以在方法参数上面指定请求配置对象OptionsOptions options = findOptions(argv);Retryer retryer = this.retryer.clone();while (true) {try {// 开始请求return executeAndDecode(template, options);} catch (RetryableException e) {try {// 抛异常,重试retryer.continueOrPropagate(e);} catch (RetryableException th) {Throwable cause = th.getCause();if (propagationPolicy == UNWRAP && cause != null) {throw cause;} else {throw th;}}if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);}continue;}}
}

feign.SynchronousMethodHandler#executeAndDecode

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {// 根据请求模板对象得到请求request对象Request request = targetRequest(template);if (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);}Response response;long start = System.nanoTime();try {// 通过具体的client组件去进行网络请求response = client.execute(request, options);} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));}throw errorExecuting(request, e);}// 请求时间统计long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);boolean shouldClose = true;try {if (logLevel != Logger.Level.NONE) {response =logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);}if (Response.class == metadata.returnType()) {if (response.body() == null) {return response;}if (response.body().length() == null ||response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {shouldClose = false;return response;}// Ensure the response body is disconnectedbyte[] bodyData = Util.toByteArray(response.body().asInputStream());return response.toBuilder().body(bodyData).build();}if (response.status() >= 200 && response.status() < 300) {if (void.class == metadata.returnType()) {return null;} else {Object result = decode(response);shouldClose = closeAfterDecode;return result;}} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {Object result = decode(response);shouldClose = closeAfterDecode;return result;} else {throw errorDecoder.decode(metadata.configKey(), response);}} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);}throw errorReading(request, response, e);} finally {if (shouldClose) {ensureClosed(response.body());}}
}

可以看到response = client.execute(request, options)这句代码,这里就是通过具体的client组件去发起网络请求了,而这个具体的client是哪个呢?当我们引入了springcloud-feign之后,springcloud会帮我们自动地把ribbon给整合进来了,所以这里的client就是LoaderBalancerClient,这个client组件整合了ribbon的负载均衡功能

org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute

public Response execute(Request request, Request.Options options) throws IOException {try {// 得到uri对象URI asUri = URI.create(request.url());// 得到host,也就是我们的服务名String clientName = asUri.getHost();// 把url上面的服务名去掉URI uriWithoutHost = cleanUrl(request.url(), clientName);// 把client,request对象以及url封装成ribbon的一个ClientRequest请求对象// 因为下面调用的是ribbon的原生api,所以这里封装成ClientRequest请求对象是为了适配ribbon的apiFeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);IClientConfig requestConfig = getClientConfig(options, clientName);// 获取FeignLoadBalancer对象,该对象继承ribbon原生的扩展抽象类AbstractLoadBalancerAwareClientreturn lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();}catch (ClientException e) {IOException io = findIOException(e);if (io != null) {throw io;}throw new RuntimeException(e);}
}

com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {// 构造LoadBalancerCommand对象LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);try {return command.submit(new ServerOperation<T>() {@Overridepublic Observable<T> call(Server server) {// 重写请求的urlURI finalUri = reconstructURIWithServer(server, request.getUri());S requestForServer = (S) request.replaceUri(finalUri);try {// 调用真正的client发起请求return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));} catch (Exception e) {return Observable.error(e);}}}).toBlocking().single();} catch (Exception e) {Throwable t = e.getCause();if (t instanceof ClientException) {throw (ClientException) t;} else {throw new ClientException(e);}}}

这里的关键代码在LoadBalancerCommand的submit方法上

com.netflix.loadbalancer.reactive.LoadBalancerCommand#submit

public Observable<T> submit(final ServerOperation<T> operation) {// ......Observable<T> o = // 选择目标服务实例(server == null ? selectServer() : Observable.just(server)).concatMap(new Func1<Server, Observable<T>>() {@Override// Called for each server being selectedpublic Observable<T> call(Server server) {context.setServer(server);final ServerStats stats = loadBalancerContext.getServerStats(server);// Called for each attempt and retryObservable<T> o = Observable.just(server).concatMap(new Func1<Server, Observable<T>>() {@Overridepublic Observable<T> call(final Server server) {// ......// 根据目标服务实例信息构造真实请求的urlreturn operation.call(server).doOnEach(new Observer<T>() {// ......// ......
}

这段代码很长,我们只看重点的那些,其中一开始的时候会调用selectServer方法去选择目标服务实例,那么是如何选择目标服务实例的呢?这里我们猜想应该就是使用ribbon的LoadBlancer组件去进行目标服务实例的筛选

private Observable<Server> selectServer() {return Observable.create(new OnSubscribe<Server>() {@Overridepublic void call(Subscriber<? super Server> next) {try {Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   next.onNext(server);next.onCompleted();} catch (Exception e) {next.onError(e);}}});
}
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {// ......ILoadBalancer lb = getLoadBalancer();if (host == null) {if (lb != null){Server svc = lb.chooseServer(loadBalancerKey);// ......
}

果然,最终就是调用了LoadBalancer组件的chooseServer方法,里面就是我们熟悉的ribbon选择目标服务实例的逻辑了

org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#reconstructURIWithServer

public URI reconstructURIWithServer(Server server, URI original) {URI uri = updateToSecureConnectionIfNeeded(original, this.clientConfig,this.serverIntrospector, server);return super.reconstructURIWithServer(server, uri);
}

当选择返回了具体的某一个目标服务实例的时候,就需要对这个实例的请求url进行重写了,updateToSecureConnectionIfNeeded方法中作用就是处理请求协议的,会把http更改为https,然后再执行父类的实现

public URI reconstructURIWithServer(Server server, URI original) {String host = server.getHost();int port = server.getPort();String scheme = server.getScheme();if (host.equals(original.getHost()) && port == original.getPort()&& scheme == original.getScheme()) {return original;}if (scheme == null) {scheme = original.getScheme();}if (scheme == null) {scheme = deriveSchemeAndPortFromPartialUri(original).first();}try {StringBuilder sb = new StringBuilder();sb.append(scheme).append("://");if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {sb.append(original.getRawUserInfo()).append("@");}sb.append(host);if (port >= 0) {sb.append(":").append(port);}sb.append(original.getRawPath());if (!Strings.isNullOrEmpty(original.getRawQuery())) {sb.append("?").append(original.getRawQuery());}if (!Strings.isNullOrEmpty(original.getRawFragment())) {sb.append("#").append(original.getRawFragment());}URI newURI = new URI(sb.toString());return newURI;            } catch (URISyntaxException e) {throw new RuntimeException(e);}
}

在父类的实现中就是进行了一番字符串的拼接操作,把url的host替换为选择的服务实例的host

org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute

得到重写的请求url之后,就要真正地发起请求了

public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)throws IOException {Request.Options options;if (configOverride != null) {// 如果ribbon的配置不为空,就用ribbon的配置进行覆盖连接超时配置和响应超时配置RibbonProperties override = RibbonProperties.from(configOverride);options = new Request.Options(override.connectTimeout(this.connectTimeout),override.readTimeout(this.readTimeout));}else {options = new Request.Options(this.connectTimeout, this.readTimeout);}// 这里就是使用原始的client去发起请求,jdk自带,httpclient,okhttpResponse response = request.client().execute(request.toRequest(), options);return new RibbonResponse(request.getUri(), response);
}

SpringCloud整合Feign的调用源码流程解析相关推荐

  1. springcloud ribbon @LoadBalance负载均衡源码流程分析

    一.编写示例 1.服务端 pom.xml <properties><java.version>1.8</java.version><spring-cloud. ...

  2. Android4.2Email源码流程解析

    Email代码结构 源码地址http://download.csdn.net/detail/wds1181977/5623927 入口处为com.android.email.activity.Welc ...

  3. django-admin的源码流程

    一.admin的源码流程 首先可以确定的是:路由关系一定对应一个视图函数 a.当点击运行的时候,会先找到每一个app中的admin.py文件,并执行 settings---->INSTALLED ...

  4. MediaPlayer源码流程简要分析

    涉及文件目录: \frameworks\base\media\java\android\media\MediaPlayer.java \frameworks\base\media\jni\androi ...

  5. Dubbo负载均衡的源码流程(2022.5.30)

    Dubbo负载均衡的源码流程 1.默认负载均衡策略:RandomLoadBalance(随机策略) 2.负载均衡策略存在以下五种: 2.1 RandomLoadBalance(随机) 2.2 Roun ...

  6. Spring源码深度解析(郝佳)-学习-源码解析-Spring整合MyBatis

    了解了MyBatis的单独使用过程之后,我们再来看看它也Spring整合的使用方式,比对之前的示例来找出Spring究竟为我们做了什么操作,哪些操作简化了程序开发. 准备spring71.xml &l ...

  7. java springcloud微服务航班管理系统源码+课程报告

    下载地址:https://download.csdn.net/download/qq_31293575/10728702 项目介绍 java springcloud微服务航班管理系统源码+课程报告 主 ...

  8. android 虚拟按键源码流程分析

    android 虚拟按键流程分析 今天来说说android 的虚拟按键的源码流程.大家都知道,android 系统的状态栏,虚拟按键,下拉菜单,以及通知显示,keyguard 锁屏都是在framewo ...

  9. 分布式定时任务—xxl-job学习(四)——调度中心web页面端api调用源码分析

    分布式定时任务-xxl-job学习(四)--调度中心web页面端api调用源码分析 前言 一.controller目录下非controller类 1.1 PermissionLimit自定义注解 1. ...

  10. SpringSecurity详细介绍RememberMe源码流程

      本文我们来详细看看rememberMe的源码流程 rememberMe源码分析   首先我们要搞清楚rememberMe功能应该是在认证成功后才能具有的,所以我们应该从Usernamepasswo ...

最新文章

  1. Windows下R语言环境安装
  2. 两列高度自适应(转)
  3. 回头再看N层架构(图解)
  4. java里面赋值运算符解释_java复合赋值运算符和赋值运算符
  5. 北京内推 | ​美团搜索与NLP部招聘自然语言处理算法实习生
  6. grunt的学习和使用
  7. Nginx全局块的工作进程的两个指令
  8. unknown error mysql_mysql执行sql文件报错Error: Unknown storage engine‘InnoDB’的解决方法...
  9. 给你1分钟,回答下RabbitMQ如何保证消息不丢?
  10. python制作桌面小程序_微信小程序在线制作:快速生成一个餐饮小程序
  11. #C++初学记录(算法测试2019/5/5)(深度搜索)
  12. 电脑对眼睛的伤害,护眼的七大误区
  13. 《电子元器件的可靠性》——3.6节恒定应力加速寿命试验
  14. GPS NMEA协议,0183 定位数据格式 双模定位:GNXXX GPS+BD 完整版
  15. 极化SAR图像特征提取与分类方法研究
  16. 1.22.FLINK Watermark\Flink窗口(Window)\watermark有什么用?\如何使用Watermarks处理乱序的数据流?\机制及实例详解\生成方式\代码实例
  17. BUG生命周期和管理
  18. python可视化迷宫求解_如何用 Python 制作一个迷宫游戏
  19. 迪文串口屏的安装方法之卡扣结构带外壳智能屏
  20. C语言程序实现道格拉斯—普克算法(Douglas--Peucker)

热门文章

  1. 深度学习图解 - 具备高中数学知识就能从入门到精通的神书 Andrew W· Trask
  2. 算法:Majority Element(求众数)
  3. 决策树 结构_如何快速简单的理解决策树的概念?
  4. 《统计学习方法》小结
  5. 简述环状引用问题及其解决方案
  6. matlab之find函数
  7. 无法启动excel服务器以导入_无法启动 web 服务器unable to start embedded tomcat
  8. 顺序容器和关联容器添加新元素方法详解
  9. Raki的读paper小记:MEMORY REPLAY WITH DATA COMPRESSION FOR CONTINUAL LEARNING
  10. IM消息重试机制Java实现_消息的重试机制的设计思路