【feign源码】流程分析
Feign的接口和Mybatis的接口相似,都是通过FactoryBean实现了spring整合的,那么扫包的就不再分析了,直接看FactoryBean–FeignClientFactoryBean
@Overridepublic Object getObject() throws Exception {return getTarget();}
<T> T getTarget() {FeignContext context = this.applicationContext.getBean(FeignContext.class);// 配置FeginFeign.Builder builder = feign(context);if (!StringUtils.hasText(this.url)) {if (!this.name.startsWith("http")) {this.url = "http://" + this.name;}else {this.url = this.name;}this.url += cleanPath();return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.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) {// not load balancing because we have a url,// but ribbon is on the classpath, so unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}// 返回实例;Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));}
本方法才是精髓,用于配置Feign;这里有一个特殊的地方,就是每个Feign客户端是多例的,这样就可以实现定制客户端。那么怎么区分配置呢?这就用到了FeignContext ,每个客户端,有各自的配置容器。这方面的就不展开了。自己分析吧。
protected Feign.Builder feign(FeignContext context) {FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);Logger logger = loggerFactory.create(this.type);// 从容器中找到类型,配置FeignFeign.Builder builder = get(context, Feign.Builder.class)// required values.logger(logger).encoder(get(context, Encoder.class)).decoder(get(context, Decoder.class)).contract(get(context, Contract.class));// @formatter://配置FeignconfigureFeign(context, builder);return builder;}
开发者可以通过配置文件,定制每个客户端的每个组件。格式是:
feign.client.客户端的名称,即访问服务的名称.FeignClientConfiguration中的属性。例如:
feign.client.springcloud-demo-provier.contract=com.test.MyContract
protected void configureFeign(FeignContext context, Feign.Builder builder) {FeignClientProperties properties = this.applicationContext.getBean(FeignClientProperties.class);if (properties != null) {if (properties.isDefaultToProperties()) {configureUsingConfiguration(context, builder);configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);configureUsingProperties(properties.getConfig().get(this.contextId),builder);}else {configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);configureUsingProperties(properties.getConfig().get(this.contextId),builder);configureUsingConfiguration(context, builder);}}else {configureUsingConfiguration(context, builder);}}
定制服务有两种方法,一种是配置文件,第二种是通过注解FeignClient中的configuration属性,配置自己的。例如:FeignClientsConfiguration,把自己的组件,以配置的形式编写。
创建实例
在getObejct()方法的最后是创建实例的逻辑。其实调用的是builder中的target方法。
Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));
feign.Feign.Builder#target(feign.Target)
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);}
public <T> T newInstance(Target<T> target) {// 这块已经给分析了一大半了,其实就是将接口类解析出来,之后创建方法处理对象。Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);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)));}}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;}
InvocationHandler 很简单,有个方法和方法处理器的映射,家用那个方法,直接找对应的方法处理器。
public Object invoke(Object[] argv) throws Throwable {// 这里是使用真正的值替换占位符;得到RequestTemplate RequestTemplate template = buildTemplateFromArgs.create(argv);Options options = findOptions(argv);Retryer retryer = this.retryer.clone();while (true) {try {// client的发送逻辑了 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;}}}
替换占位符
public RequestTemplate create(Object[] argv) {//克隆对象 RequestTemplate mutable = RequestTemplate.from(metadata.template());if (metadata.urlIndex() != null) {int urlIndex = metadata.urlIndex();checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);mutable.target(String.valueOf(argv[urlIndex]));}Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();// 得到占位符和真正值的对应关系for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {int i = entry.getKey();Object value = argv[entry.getKey()];if (value != null) { // Null values are skipped.if (indexToExpander.containsKey(i)) {value = expandElements(indexToExpander.get(i), value);}for (String name : entry.getValue()) {varBuilder.put(name, value);}}}// 替换占位符,这里有不同了,子类的实现同,涉及到解析请求体,请求参数的不同RequestTemplate template = resolve(argv, mutable, varBuilder);if (metadata.queryMapIndex() != null) {// add query map parameters after initial resolve so that they take// precedence over any predefined valuesObject value = argv[metadata.queryMapIndex()];Map<String, Object> queryMap = toQueryMap(value);template = addQueryMapQueryParameters(queryMap, template);}if (metadata.headerMapIndex() != null) {template =addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);}return template;}
【feign源码】流程分析相关推荐
- android 虚拟按键源码流程分析
android 虚拟按键流程分析 今天来说说android 的虚拟按键的源码流程.大家都知道,android 系统的状态栏,虚拟按键,下拉菜单,以及通知显示,keyguard 锁屏都是在framewo ...
- 技术宝典 | WebRTC ADM 源码流程分析
导读: 本文主要基于 WebRTC release-72 源码及云信音视频团队积累的相关经验而成,主要分析以下问题: ADM(Audio Device Manager)的架构如何?ADM(Audio ...
- WebRTC ADM 源码流程分析
导读: 本文主要基于 WebRTC release-72 源码及云信音视频团队积累的相关经验而成,主要分析以下问题: ADM(Audio Device Manager)的架构如何?ADM(Audio ...
- springcloud ribbon @LoadBalance负载均衡源码流程分析
一.编写示例 1.服务端 pom.xml <properties><java.version>1.8</java.version><spring-cloud. ...
- AQS 源码流程分析
导读: 我们日常开发中,经常会碰到并发的场景,在 Java 中语言体系里,我们会想到 ReentrantLock.CountDownLatch.Semaphore 等工具,但你是否清楚它们内部的实现原 ...
- Android 9.0系统恢复出场设置源码流程分析
前言 作为Framework层的开发人员,如果我们想让系统恢复出厂设置,一般有一下三种方式: 1.在[系统设置页面]进入[恢复出厂设置页面],点击[恢复出厂设置]按钮. 2.直接通过adb发送恢复出厂 ...
- Fabric源码流程分析之Orderer篇
导言: 本文使用fabric1.1版本,此时有小朋友会问了,fabric都出1.4.2了你怎么还在看1.1呢!首先fabric自1.0以后大的架构基本没有变化,小版本升级只是功能性上更加丰满了,当然最 ...
- eureka源码流程分析
这是euraka官网的架构图 从上面图中可以看到eureka的功能 服务注册 服务续约 服务同步 服务下线 远程调用 一.服务注册 这个服务提供者需需要把自己的实例注册到注册中心中(就相当于相亲时把自 ...
- MyBatis源码流程分析
mybatis核心流程三大阶段 Mybatis的初始化 建造者模式 建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象.这种类型的设计模式属于创建型模式,它提 ...
- 【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )
Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...
最新文章
- 藏在1.85亿人体内的隐形致癌病毒,有人确诊即是晚期
- 解决VMware—MAC冲突问题
- 【运筹学】运输规划 ( 运输规划问题模型及变化 | 表上作业法引入 )
- mdi 子窗体 菜单 不合并。
- 使用 CodeIgniter 框架快速开发 PHP 应用(五)
- BITPOS key bit [start] [end]
- 第三章	Selenide测试框架(三)
- linux/windows上STM32固件烧写工具的开发demo
- js与html编码不同,js与html中unicode编码的使用
- Tensorflow报错_np_qint8 = np.dtype([(“qint8“, np.int8, 1)])
- Django入门-helloworld
- 移动应用图标生成工具,一键生成所有尺寸的应用图标
- 小说网站服务器架构图,搭建小说网站用什么程序?搭建小说网站图文教程_好特教程...
- 可视化优化:百度地图内网访问(通过nginx代理)
- 【高级篇 / System】(7.0) ❀ 04. 高可用性 HA 配置 ❀ FortiGate 防火墙
- 解决D3DCompiler_47.dll文件丢失找不到问题
- 正则校验必须由数字 字母 和 特殊符号组成的正则
- 告别公共网盘,用闲置电脑或个人工作PC建立私有云超级网盘!
- MySQL是如何解决幻读
- php笔试面试题大全
热门文章
- java 日历选择天,如何使用Java日历从date中减去X天?
- 线性代数 : 矩阵消元
- 容器技术Docker K8s 7 容器服务ACK集群
- 阿里云云计算 13 OSS的优势和使用场景
- 极客大学架构师训练营 系统架构 分布式缓存 一致性哈希 Hash 第9课 听课总结
- AWS亚马逊ssh登录失败 Permissions 0644 for .pem are too open
- SwiftUI 生命周期onAppear,onDisappear
- 利用Python连接MySQL将表单转化为DataFrame
- java jar包 资源_一个小坑:java如何访问依赖jar包中的资源文件
- 2021-09-1427. 移除元素