Open Feign 源码解析(二) --- 如何发送http请求

Open Feign 源码解析二 如何发送http请求?

如何组件化?

定义接口

public interface Client {
	Response execute(Request request, Options options) throws IOException;
}

是否存在已有的方案?

1)rest template

  1. http client

  2. ok http 。。。

如何整合已有的方案?

在这里插入图片描述

/** http client的适配器 */
public final class ApacheHttpClient implements Client {
 
  private final HttpClient client;

  public ApacheHttpClient(HttpClient client) {
    this.client = client;
  }

  @Override
  public Response execute(Request request, Request.Options options) throws IOException {
    HttpUriRequest httpUriRequest;
    try {
      httpUriRequest = toHttpUriRequest(request, options);
    } catch (URISyntaxException e) {
      throw new IOException("URL '" + request.url() + "' couldn't be parsed into a URI", e);
    }
    HttpResponse httpResponse = client.execute(httpUriRequest);
    return toFeignResponse(httpResponse, request);
  }
}

/** ok http 的适配器 */
public final class OkHttpClient implements Client {
    
  private final okhttp3.OkHttpClient delegate;

  public OkHttpClient(okhttp3.OkHttpClient delegate) {
    this.delegate = delegate;
  }

 @Override
  public feign.Response execute(feign.Request input, feign.Request.Options options)
      throws IOException {
    okhttp3.OkHttpClient requestScoped;
    if (delegate.connectTimeoutMillis() != options.connectTimeoutMillis()
        || delegate.readTimeoutMillis() != options.readTimeoutMillis()) {
      requestScoped = delegate.newBuilder()
          .connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS)
          .readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS)
          .followRedirects(options.isFollowRedirects())
          .build();
    } else {
      requestScoped = delegate;
    }
    Request request = toOkHttpRequest(input);
    Response response = requestScoped.newCall(request).execute();
    return toFeignResponse(response, input).toBuilder().request(input).build();
  }
}

适配器需要引入的包(Pom.xml)

<dependency>
	<groupId>io.github.openfeign</groupId>
	<artifactId>feign-httpclient</artifactId>
</dependency>

<dependency>
	<groupId>io.github.openfeign</groupId>
	<artifactId>feign-okhttp</artifactId>
</dependency>

如何动态选择实现方案?

插拔式:

提供几种思路:

1)JAVA SPI -> 无法提供依赖注入,无法动态地选择实现类

2)Dubbo SPI -> 额外添加dubbo依赖,Dubbo SPI 与其业务模型耦合

3)springboot的自动装配 -> open feign 作为spirngcloud组件之一直接依托于springboot

技巧:如何快速找到自动装配类?

1)Ctrl+G -> find Usages 功能 寻找new Instance

2)通过名字去猜 autoconfiguration结尾, 其中带有feign开头

3)直接通过 spring.factories 文件去搜索

spring-cloud-openfeign-core-2.2.1.RELEASE.jar -> META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
feign 的带负载均衡的自动配置类
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
	// ...
}
HttpClient适配器的配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancedConfiguration {

	@Bean
	@ConditionalOnMissingBean(Client.class)
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory, HttpClient httpClient) {
		ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
		return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
	}
}
HttpClient的配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CloseableHttpClient.class)
public class HttpClientFeignConfiguration {

	private final Timer connectionManagerTimer = new Timer(
			"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);

	private CloseableHttpClient httpClient;

	@Autowired(required = false)
	private RegistryBuilder registryBuilder;

	@Bean
	@ConditionalOnMissingBean(HttpClientConnectionManager.class)
	public HttpClientConnectionManager connectionManager(
			ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
			FeignHttpClientProperties httpClientProperties) {
		final HttpClientConnectionManager connectionManager = connectionManagerFactory
				.newConnectionManager(httpClientProperties.isDisableSslValidation(),
						httpClientProperties.getMaxConnections(),
						httpClientProperties.getMaxConnectionsPerRoute(),
						httpClientProperties.getTimeToLive(),
						httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);
		this.connectionManagerTimer.schedule(new TimerTask() {
			@Override
			public void run() {
				connectionManager.closeExpiredConnections();
			}
		}, 30000, httpClientProperties.getConnectionTimerRepeat());
		return connectionManager;
	}

    // ...

	@Bean
	@ConditionalOnProperty(value = "feign.compression.response.enabled",
			havingValue = "false", matchIfMissing = true)
	public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
			HttpClientConnectionManager httpClientConnectionManager,
			FeignHttpClientProperties httpClientProperties) {
		this.httpClient = createClient(httpClientFactory.createBuilder(),
				httpClientConnectionManager, httpClientProperties);
		return this.httpClient;
	}

	private CloseableHttpClient createClient(HttpClientBuilder builder,
			HttpClientConnectionManager httpClientConnectionManager,
			FeignHttpClientProperties httpClientProperties) {
		RequestConfig defaultRequestConfig = RequestConfig.custom()
				.setConnectTimeout(httpClientProperties.getConnectionTimeout())
				.setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();
		CloseableHttpClient httpClient = builder
				.setDefaultRequestConfig(defaultRequestConfig)
				.setConnectionManager(httpClientConnectionManager).build();
		return httpClient;
	}
}

如果同时依赖了http client和ok http?

// 依照import的顺序 http client -> ok http -> jdk
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
	// ...
}

如何修改配置参数?

方法一:通过配置文件修改FeignHttpClientProperties的参数(属性绑定)

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CloseableHttpClient.class)
public class HttpClientFeignConfiguration { // http client的配置类
    @Bean
	@ConditionalOnMissingBean(HttpClientConnectionManager.class)
	public HttpClientConnectionManager connectionManager(
			ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
			FeignHttpClientProperties httpClientProperties) {
		final HttpClientConnectionManager connectionManager = connectionManagerFactory
				.newConnectionManager(httpClientProperties.isDisableSslValidation(),
						httpClientProperties.getMaxConnections(),
						httpClientProperties.getMaxConnectionsPerRoute(),
						httpClientProperties.getTimeToLive(),
						httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);
		this.connectionManagerTimer.schedule(new TimerTask() {
			@Override
			public void run() {
				connectionManager.closeExpiredConnections();
			}
		}, 30000, httpClientProperties.getConnectionTimerRepeat());
		return connectionManager;
	}
}

方法二:修改配置类(@Bean) 替换源码中的配置

@Configuration
public class DefaultConfiguration {

    @Bean
    public HttpClientBuilder apacheHttpClientBuilder() {
        // 修改builder参数
        return HttpClientBuilder.create().setMaxConnTotal(1000);
    }
}

如何装配组件?

组件装配到哪里?

答案: SynchronousMethodHandler

public class ReflectiveFeign extends Feign {
    // ...
    
    /** 创建JDK动态代理对象 */
    @Override
    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 
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
          } else {
            // SynchronousMethodHandler (我们正常使用的情况下, 通常MethodHandler都是SynchronousMethodHandler)
            // SynchronousMethodHandler他里面有个属性client, 把组件(就是对应http请求的组件), 就放在了这个类的client属性里面
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
          }
        }
        // 通过工厂创建FeignInvocationHandler对象并把methodToHandler封装进去
        // target: 里面三个属性(type: 对应的feign接口class加载器, name:服务名称, url:http://服务名称)
        // methodToHandler: 对应的方法事件
        InvocationHandler handler = factory.create(target, methodToHandler);
        // JDK动态代理的API
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
            new Class<?>[] {target.type()}, handler);

        for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
          defaultMethodHandler.bindTo(proxy);
        }
        return proxy;
    }
    
    // jdk动态代理的第三个参数InvocationHandler
    static class FeignInvocationHandler implements InvocationHandler {
        private final Target target;
        private final Map<Method, MethodHandler> dispatch; // 每个方法封装到MethodHandler

        FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
          this.target = checkNotNull(target, "target");
          this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
        }

        @Override
        public 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();
          }

          // 通过方法, 找到对应的MethodHandler去调用它的invoke方法把对应的参数传递进去
          return dispatch.get(method).invoke(args);
        }
    }
}
final class SynchronousMethodHandler implements MethodHandler {

  private final MethodMetadata metadata;
  private final Target<?> target;
  private final Client client; // http 请求客户端
  private final Retryer retryer;
  private final List<RequestInterceptor> requestInterceptors;
  private final Logger logger;
  private final Logger.Level logLevel;
  private final RequestTemplate.Factory buildTemplateFromArgs;
  private final Options options;
  // ...
    
  private SynchronousMethodHandler(Target<?> target, Client client, Retryer retryer,
      List<RequestInterceptor> requestInterceptors, Logger logger,
      Logger.Level logLevel, MethodMetadata metadata,
      RequestTemplate.Factory buildTemplateFromArgs, Options options,
      Decoder decoder, ErrorDecoder errorDecoder, boolean decode404,
      boolean closeAfterDecode, ExceptionPropagationPolicy propagationPolicy) {
    // ...
    this.client = checkNotNull(client, "client for %s", target);
    // ...
  }
    
  /** 真正地调用每个方法 */  
  @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template, options); // 调用client
      } 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;
      }
    }
  }
    
    
  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      response = client.execute(request, options); // 调用client组件的execute方法
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    // ...
  }  
}

如何获取组件?

  1. Autowired 自动装配

  2. 获取BeanFactory或ApplicationContext,再从里面获取

在这里插入图片描述

如何传递组件?

通过 Feign.Builder 传给 SynchronousMethodHandler.Factory ->SynchronousMethodHandler

总结:

设计:组件化思维

技术点:适配器模式,springboot自动装配(@Conditional注解的解读,@Import注解的顺序), 父子容器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/194398.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《微信小程序开发从入门到实战》学习三十三

第四章 云开发 本章云开发技术的功能与使用&#xff0c;包括以下几点&#xff1a; 1.学习使用云开发控制台 2.学习云开发JSON数据库功能 3.学习云开文件存储功能 4.学习云函数功能 5.使用云开发技术实现投票小程序的服务端功能 投票小程序大部分已经实现。需要实现&#…

【算法萌新闯力扣】:合并两个有序链表

力扣题目&#xff1a;合并两个有序链表 开篇 今天是备战蓝桥杯的第24天及算法村开营第2天。根据算法村的讲义&#xff0c;来刷链表的相关题目。今天要分享的是合并两个有序链表。 题目链接: 21.合并两个有序链表 题目描述 代码思路 通过创建一个新链表&#xff0c;然后遍历…

振南技术干货集:znFAT 硬刚日本的 FATFS 历险记(7)

注解目录 1、znFAT 的起源 1.1 源于论坛 &#xff08;那是一个论坛文化兴盛的年代。网友 DIY SDMP3 播放器激起了我的兴趣。&#xff09; 1.2 硬盘 MP3 推了我一把 &#xff08;“坤哥”的硬盘 MP3 播放器&#xff0c;让我深陷 FAT 文件系统不能自拔。&#xff09; 1.3 我…

Android Bitmap 模糊效果实现 (二)

文章目录 Android Bitmap 模糊效果实现 (二)使用 Vukan 模糊使用 RenderEffect 模糊使用 GLSL 模糊RS、Vukan、RenderEffect、GLSL 效率对比 Android Bitmap 模糊效果实现 (二) 本文首发地址 https://blog.csdn.net/CSqingchen/article/details/134656140 最新更新地址 https:/…

将用户的session改为分布式共享session

将用户的session改为分布式session 分布式session理解 使用分布式session的原因&#xff1a; 后台服务器是分布式的&#xff08;比如要负载均衡&#xff09;&#xff0c;在A服务器请求的的信息&#xff08;如用户登录信息&#xff09;存在A的session中&#xff0c;B服务器并不…

C++之哈希

unordered系列容器的效率之所以比较高(尤其是查找),是因为它底层使用了哈希结构,即哈希表. 哈希概念 前言: 顺序结构以及平衡树中, 元素关键码与其存储位置之间没有对应的关系, 因此在查找一个元素 时, 必须要经过关键码的多次比较. 顺序查找时间复杂度为O(N), 平衡树中为树的…

香港科技大学广州|智能制造学域博士招生宣讲会—天津大学专场

时间&#xff1a;2023年12月07日&#xff08;星期四&#xff09;15:30 地点&#xff1a;天津大学卫津路校区26楼B112 报名链接&#xff1a;https://www.wjx.top/vm/mmukLPC.aspx# 宣讲嘉宾&#xff1a; 汤凯教授 学域主任 https://facultyprofiles.hkust-gz.edu.cn/faculty-p…

解决:AttributeError: module ‘os’ has no attribute ‘mknod’

解决&#xff1a;AttributeError: module ‘os’ has no attribute ‘mknod’ 文章目录 解决&#xff1a;AttributeError: module os has no attribute mknod背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使用之前的代码时&#xff0c;报错…

借助arthas 性能调优全过程

使用 arthas 的trace 命令分析方法耗时瓶颈&#xff1a; 可以看出 bindReloadZoneTimeLimite 耗时最久&#xff0c; 通过分析Bind 底层&#xff0c;将业务粒度进行拆分&#xff0c;加入并发执行 再次使用arthas 追踪单个方法耗时时间&#xff1a; 核心耗时方法&#xff0c…

使用Postman如何在接口测试前将请求的参数进行自定义处理

1、前言 当我们使用 Postman 进行接口测试时&#xff0c;对于简单的不需要处理的接口&#xff0c;直接请求即可&#xff0c;但是对于需要处理的接口&#xff0c;如需要转码、替换值等&#xff0c;则就麻烦一些&#xff0c;一般我们都是先手动把修改好的值拷贝到请求里再进行请…

使用Arthas排查性能问题

Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类加载信…

unity学习笔记10

一、生命周期函数 1.Awake() 调用时间&#xff1a;对象被激活或创建时。 用途&#xff1a;通常用于初始化对象的状态&#xff0c;获取组件引用或执行其他在脚本生命周期早期需要完成的任务。 2.OnEnable(): 调用时间&#xff1a;对象激活时&#xff0c;包括对象被创建和Se…

每天五分钟计算机视觉:经典架构的力量与启示

在深度学习和计算机视觉领域,卷积神经网络(Convolutional Neural Networks,简称CNN)无疑是最为经典的架构之一。近年来,随着研究的不断深入和新架构的不断涌现,许多初学者可能会忽视这些经典架构的重要性。然而,理解并学习这些经典架构,对于我们深入理解卷积神经网络的…

操作系统 选择题 期末试题 考研真题 + 参考答案

1.&#xff08;考研真题&#xff0c;单项选择题&#xff09;单道批处理系统的主要缺点是&#xff08; &#xff09;。 A. CPU利用率不高 B.失去了交互性 C.不具备并行性 D.以上都不是 【参考答案】A 【解析】单道批处理系统的内存中只有一道程序&#xff0c;当该程序…

苍穹外卖项目笔记(5)——Redis

1 入门 1.1 Redis 简介 Redis 是一个基于内存的 key-value 结构数据库&#xff0c;官网链接&#xff08;中文&#xff09;&#xff1a;https://www.redis.net.cn 特点&#xff1a; 基于内存存储&#xff0c;读写性能高适合存储热点数据&#xff08;热点商品、资讯、新闻&am…

vue3(二)-基础入门之列表循环、数组变动检测、filter模糊查询、事件修饰符

一、列表循环 of 和 in 都是一样的效果 html代码&#xff1a; <div id"app"><ul><li v-for"item of datalist">{{ item }}</li></ul><ul><li v-for"item in dataobj">{{ item }}</li></u…

3D点云目标检测:CT3D解读(未完)

CT3D 一、RPN for 3D Proposal Generation二、Proposal-to-point Encoding Module2.1、Proposal-to-point Embedding2.2、Self-attention Encoding 三、Channel-wise Decoding Module3.1、Standard Decoding3.2、Channel-wise Re-weighting3.3、Channel-wise Decoding Module 四…

数据库之索引的底层数据逻辑及应用

索引&#xff08;index&#xff09;是帮助数据库高效获取数据的数据结构。 索引的数据结构 堆存储 使用二叉树存储 极端情况下的单链形式 大数据量下&#xff0c;层级越深&#xff0c;查询效率越低。 平衡二叉树 多路平衡查找树 B树的结构 所有的数据都存储在叶结点中…

redis Redis::geoAdd 无效,phpstudy 如何升级redis版本

redis 查看当前版本命令 INFO SERVERwindows 版redis 进入下载 geoadd 功能在3.2之后才有的&#xff0c;但是phpstudy提供的最新的版本也是在3.0&#xff0c;所以需要升级下 所以想出一个 挂狗头&#xff0c;卖羊肉的方法&#xff0c;下载windows 的程序&#xff0c;直接替…

Cache学习(3):Cache地址映射(直接映射缓存组相连缓存全相连缓存)

1 Cache的与存储地址的映射 以一个Cache Size 为 128 Bytes 并且Cache Line是 16 Bytes的Cache为例。首先把这个Cache想象成一个数组&#xff0c;数组总共8个元素&#xff0c;每个元素大小是 16 Bytes&#xff0c;如下图&#xff1a; 现在考虑一个问题&#xff0c;CPU从0x0654…