前言
书接上文,feign接口是如何注册到容器想必已然清楚,现在我们着重关心一个问题,feign调用服务的时候是如何抉择的?上一篇主要是从读源码的角度入手,后续将会逐步从软件构架方面进行剖析。
一、ReflectiveFeign.FeignInvocationHandler
从上文知道feign接口调用实质上是调用的对应的动态代理接口的InvocationHandler,跟踪源码发现默认的InvocationHandler实现就是FeignInvocationHandler。现在我们看一下这个FeignInvocationHandler.invoke(...)方法。
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();
}
// dispath 是缓存的method 以及 method对应的MethodHandler
return dispatch.get(method).invoke(args);
}
从代码中可以看到他是直接从缓存中拿到对应的MethodHandler,然后调用的MethodHandler的invoke方法。我们看一下MethodHandler都有哪些实现:
可以看到就两个实现, DefaultMethodHandler处理的是feign接口中的Default修饰的方法。我们调用的远程接口用的是SynchronousMethodHandler实现。那么可以看到我们最终对feing接口的某个方法的调用实际上调用的是SynchronousMethodHandler.invoke(...)方法。跟踪代码发现,最终调用的是SynchronousMethodHandler持有的Client的实例的execute方法。那么我们看一下Client都有那些实现:
这里跟踪SynchronousMethodHandler的创建过程发现Client的创建是按照如下逻辑进行的(FeignClientFactoryBean.loadBalance):
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
从上述代码可以看到,他从context中获取到client,然后通过client获取执行。想必feign肯定是自动装备了一个Client,我们看一下他的默认配置:
显然配置必定是从FeignAutoConfiguration 或者 FeignRibbonClientAutoConfiguration进行配置的,查看这两个类最终发现Client是通过FeignRibbonClientAutoConfiguration进行注入的(通过@Import引入的DefaultFeignLoadBalancedConfiguration进行注入):
@Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
所以我们调用feign接口的某一个方法,最终调用的LoadBalancerFeignClient.execute()方法。那么负载均衡相关逻辑应该是在此接入的。
二、LoadBalancerFeignClient 做了些什么
先看核心代码,注意注释部分:
public Response execute(Request request, Request.Options options) throws IOException {
try {
// URL 处理
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 获取调用服务配置
IClientConfig requestConfig = getClientConfig(options, clientName);
// 创建负载均衡客户端,执行请求
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
从上面的代码可以看到,lbClient(clientName) 创建了一个负载均衡的客户端,它实际上就是生成的如下所述的类:
public class FeignLoadBalancer extends
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse>
熟悉ribbon的朋友应该知道AbstractLoadBalancerAwareClient 就是Ribbon负载均衡调用的父类。具体的负载均衡实现策略,下一章在详细描述。至此我们可以得出结论:feign集成负载均衡是通过将FeignLoadBalancer作为调用feign接口的实际执行者,从而达到负载均衡的效果。可以看到这里与Ribbon高度的解耦,相当于我们获取了服务名、调用地址、调用参数后,最终交由一个执行器去调用。执行器并不关心参数从何而来,这里基于Ribbon提供的执行器实现只是更具传递的服务名找到了一个正确的实例去调用而已。
三 、 小结
至此我们可以看到初步职责划分: 代理对象、请求与响应解析、执行器三个职能部门。
- 代理对象职责是: 将feign接口中方法的调用转接到对FeignInvocationHandler的invoke调用,在invoke函数中通过方法名称找到对应的SynchronousMethodHandler。
- 执行器: 负责根据请求的方法解析出调用信息(调用服务名、调用地址、调用参数)然后发起调用,他不关心参数是如何来的
一个系统的好坏、可扩展性高低很大程序上取决于系统中各职能部门划分是否清晰以及各个职能部分的权限是否越界。