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;}@Overridepublic 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;}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void run() {connectionManager.closeExpiredConnections();}}, 30000, httpClientProperties.getConnectionTimerRepeat());return connectionManager;}
}

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

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

如何装配组件?

组件装配到哪里?

答案: SynchronousMethodHandler

public class ReflectiveFeign extends Feign {// .../** 创建JDK动态代理对象 */@Overridepublic <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动态代理的APIT proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}return proxy;}// jdk动态代理的第三个参数InvocationHandlerstatic class FeignInvocationHandler implements InvocationHandler {private final Target target;private final Map<Method, MethodHandler> dispatch; // 每个方法封装到MethodHandlerFeignInvocationHandler(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();}// 通过方法, 找到对应的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);// ...}/** 真正地调用每个方法 */  @Overridepublic 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注解的顺序), 父子容器

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

优思学院|六西格玛:中国企业迈向国际舞台的必经之路

六西格玛质量管理在中国的企业中是实用和广泛运用的。六西格玛管理是一种系统的过程改进方法&#xff0c;通过对现有过程进行界定、测量、分析、改进和控制的流程来提高质量、降低成本。中国的一些企业已经取得了显著的经济效益&#xff0c;并且至少有25&#xff05;以上的世界…

【JavaEE初阶】 HTTP协议和使用Fiddler抓包

文章目录 &#x1f38d;HTTP协议是什么&#xff1f;&#x1f340;应用层协议&#xff08;HTTP&#xff09;存在的意义&#x1f384;HTTP 协议的工作过程&#x1f334;HTTP 协议格式&#x1f333;Fiddler抓包工具的使用&#x1f6a9;如何抓HTTPS的包&#xff1f; &#x1f38b;抓…

操作系统校招知识点总结

文章目录 前言1. 操作系统概述1.1 操作系统的四大特征&#xff08;并共虚异&#xff09;1.2 操作系统的主要功能&#xff1f;1.3 动态链接库和静态链接库的区别&#xff1f;1.4 并发和共享之间的关系&#xff1f;1.5 中断和异常的概念&#xff1f; 2. 进程与线程2.1 进程和线程…

SELinux零知识学习三十七、SELinux策略语言之约束(1)

接前一篇文章:SELinux零知识学习三十六、SELinux策略语言之角色和用户(7) 四、SELinux策略语言之约束 SELinux对策略允许的访问提供了更严格的约束机制,不管策略的allow规则如何。 1. 近距离查看访问决定算法 为了理解约束的用途,先来看一下SELinux Linux安全模块(Lin…

Linux端口流量统计

Ubuntu sudo apt-get install wiresharkCentOS sudo yum install wiresharkUDP端口统计 sudo tshark -i <interface> -f "udp port <port_number>" -a duration:60 -q -z conv,udp请将 替换为你的网络接口&#xff0c;<port_number> 替换为要监…

简易版扫雷+代码分析

前言&#xff1a; 实验一个简易版的扫雷&#xff0c;也要两百来行的代码&#xff0c;因此为了代码整洁&#xff0c;维护起来方便&#xff0c;这里我们和前期实现的三子棋一样&#xff0c;也弄一个游戏的头文件game.h用来装各种头文件以及函数的声明以及宏定义、预处理信息&…

电力智能化系统(智能电力综合监控系统)

电力智能化系统是一个综合性的系统&#xff0c;它利用物联网、云计算、大数据、人工智能等技术&#xff0c;依托电易云-智慧电力物联网&#xff0c;采用智能采集终端和物联网关&#xff0c;将电力设备、用电负荷、电力市场等各个环节有机地联系起来&#xff0c;实现了对电力配送…

计算机网络(超详解!) 第一节计算机网络的性能指标

1.速率 比特&#xff08;bit&#xff09;是计算机中数据量的单位&#xff0c;也是信息论中使用的信息量的单位。 比特&#xff08;bit&#xff09;来源于 binary digit&#xff0c;意思是一个“二进制数字”&#xff0c;因此一个比特就是二进制数字中的一个 1 或 0。 速率是…

VUE语法--img图片不显示/img的src动态赋值图片显示

1、问题概述 常见情景1&#xff1a;在VUE中使用img显示图片的时候&#xff0c;通过传参的方式传入图片的路径和名称&#xff0c;VUE不加载本地资源而是通过http://localhost:8080/...的地址去加载网络资源&#xff0c;从而出现了图片无法显示的情况。 常见情景2&#xff1a;针…

【教学类-06-12】20231126 (二)三位数 如何让加减乘除题目从小到大排序(以0-110之间加法为例,做正序排列用)

结果展示 背景需求&#xff1a; 二位数&#xff1a;去0 三位数&#xff08;需要排除很多0&#xff09; 解决思路 一、把数字改成三位数 二、对数组内的题目&#xff0c;8种可能性进行去“0”处理 1、十位数&#xff08;去百位数0&#xff09;十位数&#xff08;去百位数0&am…

GDOUCTF2023-Reverse WP

文章目录 [GDOUCTF 2023]Check_Your_Luck[GDOUCTF 2023]Tea[GDOUCTF 2023]easy_pyc[GDOUCTF 2023]doublegame[GDOUCTF 2023]L&#xff01;s&#xff01;[GDOUCTF 2023]润&#xff01;附 [GDOUCTF 2023]Check_Your_Luck 根据 if 使用z3约束求解器。 EXP&#xff1a; from z3 i…

盘点最新的十大地推拉新app推广接单平台,一手接单渠道分享

小编主推&#xff1a;聚量推客 一手官签直营 随着网络的发展&#xff0c;越来越多的app运应而生社交类、购物类、娱乐类、资讯类、教育类、健康类、金融类为了将这些应用程序推广给更多的人使用&#xff0c;地推拉新app推广接单平台应运而生。本文将介绍十大地推拉新app推广接…