Spring-cloud Feign 的深入理解
时间:2019-02-12
这篇文章主要介绍了Spring-cloud Feign 的深入理解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
feign的调用流程
读取注解信息:EnableFeignClients-->FeignClientsRegistrar-->FeignClientFactoryBean
feigh流程:ReflectiveFeign-->Contract-->SynchronousMethodHandler
相关configuration:FeignClientsConfiguration,FeignAutoConfiguration,DefaultFeignLoadBalancedConfiguration,FeignRibbonClientAutoConfiguration(Ribbon)
在FeignClientsRegistrar中:
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //注册feign配置信息 registerDefaultConfiguration(metadata, registry); //注册feign client registerFeignClients(metadata, registry); } private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); //准备注入FeignClientFactoryBean BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); ... }
查看FeignClientFactoryBean:
@Override public Object getObject() throws Exception { FeignContext context = applicationContext.getBean(FeignContext.class); //构建Feign.Builder Feign.Builder builder = feign(context); //如果注解没有指定URL if (!StringUtils.hasText(this.url)) { String url; if (!this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } url += cleanPath(); return loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } //如果指定了URL if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // 因为指定了URL且classpath下有Ribbon,获取client的delegate(unwrap) // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); return targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); } protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) { //获取Feign Client实例 Client client = getOptional(context, Client.class); if (client != null) { builder.client(client); //DefaultTargeter或者HystrixTargeter Targeter targeter = get(context, Targeter.class); //调用builder的target,其中就调用了Feign的newInstance return targeter.target(this, builder, context, target); } throw new IllegalStateException( "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?"); }
在FeignClientsConfiguration配置了Feign.Builder,prototype类型:
@Bean @Scope("prototype") @ConditionalOnMissingBean public Feign.Builder feignBuilder(Retryer retryer) { return Feign.builder().retryer(retryer); }
Feign的Builder.build返回了一个ReflectiveFeign:
public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, errorDecoder, synchronousMethodHandlerFactory); //ReflectiveFeign构造参数 //ParseHandlersByName作用是通过传入的target返回代理接口下的方法的各种信息(MethodHandler) //Contract:解析接口的方法注解规则,生成MethodMetadata //Options:Request超时配置 //Encoder:请求编码器 //Decoder:返回解码器 //ErrorDecoder:错误解码器 //SynchronousMethodHandler.Factory是构建SynchronousMethodHandler的工厂 //Client:代表真正执行HTTP的组件 //Retryer:该组决定了在http请求失败时是否需要重试 //RequestInterceptor:请求前的拦截器 //Logger:记录日志组件,包含各个阶段记录日志的方法和留给用户自己实现的log方法 //Logger.Level:日志级别 //decode404:处理404的策略,返回空还是报错 //synchronousMethodHandlerFactory通过所有的信息去包装一个synchronousMethodHandler,在调用invoke方法的时候执行HTTP return new ReflectiveFeign(handlersByName, invocationHandlerFactory); }
在调用Feign.Builder的target的时候,调用了ReflectiveFeign.newInstance:
/** * creates an api binding to the {@code target}. As this invokes reflection, care should be taken * to cache the result. */ @SuppressWarnings("unchecked") @Override //接收Target参数(包含feign代理接口的类型class,名称,http URL) public <T> T newInstance(Target<T> target) { //首先通过**ParseHandlersByName**解析出接口中包含的方法,包装RequestTemplate,组装成<name, MethodHandler> Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); //接口default方法List 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))); } } //InvocationHandlerFactory.Default()返回了一个ReflectiveFeign.FeignInvocationHandler对象,通过传入的methodHandler map 调用目标对象的对应方法 InvocationHandler handler = factory.create(target, methodToHandler); //生成JDK代理对象 T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler); //绑定接口的默认方法到代理对象 for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }
生成Feign代理对象的基本流程图:
当调用接口方法时,实际上就是调用代理对象invoke方法:
@Override public Object invoke(Object[] argv) throws Throwable { //工厂创建请求模版 RequestTemplate template = buildTemplateFromArgs.create(argv); //每次克隆一个新的Retryer Retryer retryer = this.retryer.clone(); while (true) { try { //这里调用实际的Feign client execute return executeAndDecode(template); } catch (RetryableException e) { //失败重试 retryer.continueOrPropagate(e); if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } }
在DefaultFeignLoadBalancedConfiguration里实例化了LoadBalancerFeignClient
@Override public Response execute(Request request, Request.Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); //delegate这里是Client.Default实例,底层调用的是java.net原生网络访问 FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); //executeWithLoadBalancer会根据ribbon的负载均衡算法构建url,这里不展开 return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } catch (ClientException e) { IOException io = findIOException(e); if (io != null) { throw io; } throw new RuntimeException(e); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
- 左手用R右手Python系列——循环中的错误异常规避
- SpringBoot2.x开发案例之整合Quartz任务管理系统
- 给出一组非负整数,重新排序组成最大的数
- [机智的机器在学习] TensorFlow实现Kmeans聚类
- [机智的机器在学习] 利用TensorFlow实现多元线性回归分类器
- [数据结构和算法]《算法导论》动态规划笔记(1)
- [数据结构和算法]《算法导论》动态规划笔记(2)
- [算法与数据结构] 《算法导论》堆排序笔记
- [数据结构与算法] 链表的其他类型
- [数据结构与算法] 链接表总结
- [数据结构与算法] 线性表总结
- [数据结构与算法] Python实现二分查找
- [机智的机器在学习] 机器学习中的归一化和正则化问题
- [情人节] jieba分词介绍
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 以攻击者角度学习某风控设备指纹产品
- 高并发系统三大利器之缓存
- 前端测试题:(解析)js中关于类(class)的继承的说法,下面错误的是?
- 程序员深夜惨遭老婆鄙视,原因竟是CAS原理太简单?| 每一张图都力求精美
- MySQL数据延迟跳动的问题分析
- Python GUI项目实战(八)修改密码功能的实现
- Prometheus监控神器-Alertmanager篇(3)
- Prometheus监控神器-Alertmanager篇(4)
- 71-STM32+ESP8266+AIR202基本控制篇-移植使用-移植微信小程序MQTT底层包到自己的工程项目
- 目标检测 | Anchor free之CornerNet网络深度解析
- 手把手教你 3 分钟搞定个人网站 http 免费升级到 https
- 设计模式(四):通过做蛋糕理解构建模式及Android中的变种
- 如何入门使用腾讯云物联网开发平台 IoT Explorer,开发一盏智慧城市的智能灯?
- 知道吗?容器镜像也可以延迟拉取!
- ansible超详细讲解,值得收藏