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源码】流程分析相关推荐

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

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

  2. 技术宝典 | WebRTC ADM 源码流程分析

    导读: 本文主要基于 WebRTC release-72 源码及云信音视频团队积累的相关经验而成,主要分析以下问题: ADM(Audio Device Manager)的架构如何?ADM(Audio ...

  3. WebRTC ADM 源码流程分析

    导读: 本文主要基于 WebRTC release-72 源码及云信音视频团队积累的相关经验而成,主要分析以下问题: ADM(Audio Device Manager)的架构如何?ADM(Audio ...

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

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

  5. AQS 源码流程分析

    导读: 我们日常开发中,经常会碰到并发的场景,在 Java 中语言体系里,我们会想到 ReentrantLock.CountDownLatch.Semaphore 等工具,但你是否清楚它们内部的实现原 ...

  6. Android 9.0系统恢复出场设置源码流程分析

    前言 作为Framework层的开发人员,如果我们想让系统恢复出厂设置,一般有一下三种方式: 1.在[系统设置页面]进入[恢复出厂设置页面],点击[恢复出厂设置]按钮. 2.直接通过adb发送恢复出厂 ...

  7. Fabric源码流程分析之Orderer篇

    导言: 本文使用fabric1.1版本,此时有小朋友会问了,fabric都出1.4.2了你怎么还在看1.1呢!首先fabric自1.0以后大的架构基本没有变化,小版本升级只是功能性上更加丰满了,当然最 ...

  8. eureka源码流程分析

    这是euraka官网的架构图 从上面图中可以看到eureka的功能 服务注册 服务续约 服务同步 服务下线 远程调用 一.服务注册 这个服务提供者需需要把自己的实例注册到注册中心中(就相当于相亲时把自 ...

  9. MyBatis源码流程分析

    mybatis核心流程三大阶段 Mybatis的初始化  建造者模式 建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象.这种类型的设计模式属于创建型模式,它提 ...

  10. 【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

最新文章

  1. 藏在1.85亿人体内的隐形致癌病毒,有人确诊即是晚期
  2. 解决VMware—MAC冲突问题
  3. 【运筹学】运输规划 ( 运输规划问题模型及变化 | 表上作业法引入 )
  4. mdi 子窗体 菜单 不合并。
  5. 使用 CodeIgniter 框架快速开发 PHP 应用(五)
  6. BITPOS key bit [start] [end]
  7. 第三章 Selenide测试框架(三)
  8. linux/windows上STM32固件烧写工具的开发demo
  9. js与html编码不同,js与html中unicode编码的使用
  10. Tensorflow报错_np_qint8 = np.dtype([(“qint8“, np.int8, 1)])
  11. Django入门-helloworld
  12. 移动应用图标生成工具,一键生成所有尺寸的应用图标
  13. 小说网站服务器架构图,搭建小说网站用什么程序?搭建小说网站图文教程_好特教程...
  14. 可视化优化:百度地图内网访问(通过nginx代理)
  15. 【高级篇 / System】(7.0) ❀ 04. 高可用性 HA 配置 ❀ FortiGate 防火墙
  16. 解决D3DCompiler_47.dll文件丢失找不到问题
  17. 正则校验必须由数字 字母 和 特殊符号组成的正则
  18. 告别公共网盘,用闲置电脑或个人工作PC建立私有云超级网盘!
  19. MySQL是如何解决幻读
  20. php笔试面试题大全

热门文章

  1. java 日历选择天,如何使用Java日历从date中减去X天?
  2. 线性代数 : 矩阵消元
  3. 容器技术Docker K8s 7 容器服务ACK集群
  4. 阿里云云计算 13 OSS的优势和使用场景
  5. 极客大学架构师训练营 系统架构 分布式缓存 一致性哈希 Hash 第9课 听课总结
  6. AWS亚马逊ssh登录失败 Permissions 0644 for .pem are too open
  7. SwiftUI 生命周期onAppear,onDisappear
  8. 利用Python连接MySQL将表单转化为DataFrame
  9. java jar包 资源_一个小坑:java如何访问依赖jar包中的资源文件
  10. 2021-09-1427. 移除元素