专栏系列文章:SpringCloud系列专栏java
系列文章:web
SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化apache
SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约markdown
SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表网络
SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制app
SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群负载均衡
SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇ide
SpringCloud 源码系列(7)— 负载均衡Ribbon 之 RestTemplate源码分析
SpringCloud 源码系列(8)— 负载均衡Ribbon 之 核心原理post
SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置
SpringCloud 源码系列(10)— 负载均衡Ribbon 之 HTTP客户端组件
SpringCloud 源码系列(11)— 负载均衡Ribbon 之 重试与总结篇
SpringCloud 源码系列(12)— 服务调用Feign 之 基础使用篇
SpringCloud 源码系列(13)— 服务调用Feign 之 扫描@FeignClient注解接口
SpringCloud 源码系列(14)— 服务调用Feign 之 构建@FeignClient接口动态代理
前一篇文章已经分析出,最终在 Feign.Builder
的 build()
方法构建了 ReflectiveFeign
,而后利用 ReflectiveFeign 的 newInstance
方法建立了动态代理。这个动态代理的代理对象是 ReflectiveFeign.FeignInvocationHandler
。最终来讲确定就会利用 Client
进行负载均衡的请求。这节就来看看 Feign 若是利用动态代理发起HTTP请求的。
使用 FeignClient 接口时,注入的实际上是动态代理对象,调用接口方法时就会进入执行器 ReflectiveFeign.FeignInvocationHandler
,从 FeignInvocationHandler 的 invoke
方法能够看出,就是根据 method 获取要执行的方法处理器 MethodHandler
,而后执行方法。MethodHandler 的实际类型就是 SynchronousMethodHandler
。
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
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 {
//...
// 根据 method 获取 MethodHandler,而后执行方法
return dispatch.get(method).invoke(args);
}
}
复制代码
接着看 SynchronousMethodHandler 的 invoke 方法,核心逻辑就两步:
RequestTemplate
,就是处理 URI 模板、参数,好比替换掉 uri 中的占位符、拼接参数等。executeAndDecode
执行请求,并将相应结果解码返回。public Object invoke(Object[] argv) throws Throwable {
// 构建请求模板,例若有 url 参数,请求参数之类的
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 执行并解码
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;
}
}
continue;
}
}
}
复制代码
能够看到,通过处理后,URI 上的占位符就被参数替换了,而且拼接了请求参数。
接着看 executeAndDecode
,主要有三步:
targetRequest
方法,主要就是遍历 RequestInterceptor
对请求模板 RequestTemplate 定制化,而后调用 HardCodedTarget
的 target
方法将 RequestTemplate 转换成 Request
请求对象,Request 封装了请求地址、请求头、body 等信息。LoadBalancerFeignClient
,这里就进入了负载均衡请求了。decoder
来解析响应结果,将结果转换成接口的返回类型。Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 处理RequestTemplate,获得请求对象 Request
Request request = targetRequest(template);
Response response;
try {
// 调用 client 执行请求,client => LoadBalancerFeignClient
response = client.execute(request, options);
// 构建响应 Response
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
//...
}
if (decoder != null) {
// 使用解码器解码,将返回数据转换成接口的返回类型
return decoder.decode(response, metadata.returnType());
}
//....
}
// 应用拦截器处理 RequestTemplate,最后使用 target 从 RequestTemplate 中获得 Request
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
// target => HardCodedTarget
return target.apply(template);
}
复制代码
HardCodedTarget
是硬编码写死的,咱们没有办法定制化,看下它的 apply
方法,主要就是处理 RequestTemplate 模板的地址,生成完成的请求地址。最后返回 Request 请求对象。
public Request apply(RequestTemplate input) {
if (input.url().indexOf("http") != 0) {
// url() => http://demo-producer
// input.target 处理请求模板
input.target(url());
}
return input.request();
}
复制代码
能够看到通过 HardCodedTarget 的 apply 方法以后,就拼接上了 url 前缀了。
LoadBalancerFeignClient
是 Feign 实现负载均衡核心的组件,是 Feign 网络请求组件 Client
的默认实现,LoadBalancerFeignClient 最后是使用 FeignLoadBalancer
来进行负载均衡的请求。
看 LoadBalancerFeignClient 的 execute
方法,从这里到后面执行负载均衡请求,其实跟分析 Ribbon 源码中 RestTemplate 的负载均衡请求都是相似的了。
FeignLoadBalancer.RibbonRequest
。注意 RibbonRequest 第一个参数 Client 就是设置的 LoadBalancerFeignClient 的代理对象,启用 apache httpclient 时,就是 ApacheHttpClient
。Ribbon 的客户端配置对 Feign 一样生效
。FeignLoadBalancer
,而后执行负载均衡请求。public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
// 客户端名称:demo-producer
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
// 封装 ClientRequest => FeignLoadBalancer.RibbonRequest
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 客户端负载均衡配置 ribbon.demo-producer.*
IClientConfig requestConfig = getClientConfig(options, clientName);
// lbClient => 负载均衡器 FeignLoadBalancer,执行负载均衡请求
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
//...
}
}
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
复制代码
进入 executeWithLoadBalancer
方法,这就跟 Ribbon 源码中分析的是同样的了,最终就验证了 Feign 基于 Ribbon 来作负载均衡请求。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
// 负载均衡器执行命令
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
// 用Server的信息重构URI地址
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
// 实际调用 LoadBalancerFeignClient 的 execute 方法
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
//....
}
}
复制代码
重构URI后,实际是调用 FeignLoadBalancer 的 execute
方法来执行最终的HTTP调用的。看下 FeignLoadBalancer 的 execute 方法,最终来讲,就是使用代理的HTTP客户端来执行请求。
默认状况下,就是 Client.Default
,用 HttpURLConnection 执行HTTP请求;启用了 httpclient 后,就是 ApacheHttpClient;启用了 okhttp,就是 OkHttpClient。
这里有一点须要注意的是,FeignClient 虽然能够配置超时时间,但进入 FeignLoadBalancer 的 execute 方法后,能够看到会用 Ribbon 的超时时间覆盖 Feign 配置的超时时间
,最终以 Ribbon 的超时时间为准。
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException {
Request.Options options;
if (configOverride != null) {
// 用 Ribbon 的超时时间覆盖了feign配置的超时时间
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
// request.client() HTTP客户端对象
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
复制代码
关于Ribbon的源码分析请看前面 Ribbon 相关的文章,Ribbon 如何从 eureka 注册中心获取 Server 就再也不分析了。
下面这张图总结了 Feign 负载均衡请求的流程:
@FeignClient
注解的接口,并生成代理类注入到容器中。咱们注入 @FeignClient 接口时其实就是注入的这个代理类。ReflectiveFeign.FeignInvocationHandler
的 invoke
方法执行请求。SynchronousMethodHandler
,而后调用它的 invoke
方法执行请求。RequestTemplate
,这个时候会处理参数中的占位符、拼接请求参数、处理body中的参数等等。Request
,在转换的过程当中:
RequestInterceptor
处理请求模板,所以咱们能够自定义拦截器来定制化 RequestTemplate。Target(HardCodedTarget)
处理请求地址,拼接上服务名前缀。request
方法获取到 Request 对象。execute
方法来执行请求并获得请求结果 Response
:
FeignLoadBalancer
,而后就执行负载均衡请求。AbstractLoadBalancerAwareClient,executeWithLoadBalancer
方法中,会先构建一个 LoadBalancerCommand,而后提交一个 ServerOperation。Response
。Decoder
解析响应结果,返回接口方法定义的返回类型。负载均衡获取Server的核心组件是 LoadBalancerClient
,具体的源码分析能够参考 Ribbon 源码分析的两篇文章。LoadBalancerClient 负载均衡的原理能够看下面这张图。