Feign原理分析

背景

​ 近几年来随着微服务的流行,微服务治理成为了大家越来越关注的重点。目前市面上有很多成熟的方案,其中Spring Cloud Netflix 因为Netflix成功落地实现而流行起来。本文主要介绍Spring Cloud Netfix中服务间调用框架Feign具体如何实现的,通过阅读本文后,希望能让你对Feign是什么以及如何实现的有一个清楚的认识。

Feign是一种声明式、模板化的HTTP客户端,可以让HTTP的远程调用就像调用本地方法一样,无感知发送远程HTTP请求。Feign通过为目标服务接口生成动态代理对象,将HTTP请求逻辑封装到代理对象中,从而达到简化服务间调用的Java代码的目的。

​ 接下来会详细Feign是如何创建代理对象,代理对象方法执行的完整流程了解Feign的工作原理,同时也会简单说明FeignHystrixEureka如何协同工作的。

Feign

​ 首先我们需要一个interface,这个interface定义了我们需要调用的目标服务中的方法,这里我们用Feign官方文档中调用GitHub服务的例子来举例:

1
2
3
4
5
6
7
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

@RequestLine("POST /repos/{owner}/{repo}/issues")
void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);
}
创建流程

了解以上信息之后就进入使用案例,案例通过Feign生成了一个GitHub的代理对象:

1
2
3
4
5
6
7
public class MyApp {
public static void main(String... args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
}
}

Feign对象构建使用Builder模式,将复杂的构建过程封装在Feign.Builider类中。我们追踪Feign.builder().target()方法,可以看到:

1
2
3
public <T> T target(Target<T> target) {
return build().newInstance(target);
}

首先会调用到 Feign.builder.build()方法:

1
2
3
4
5
6
7
8
9
10
public Feign build() {
...
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

代码中我们只保留了代码主体,可以看到build()方法最终会返回一个Feign 的实现类ReflectiveFeign的对象。接下来会调用ReflectiveFeign.newInstance(Target<T> target)方法,这里我们可以看到非常熟悉的动态代理的代码逻辑:

1
2
3
4
5
6
7
8
9
10
public <T> T newInstance(Target<T> target) {
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
...
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
...
return proxy;
}

至此代理对象生成就完成了,总体流程逻辑非常简单,下图为生成代理对象的时序图:

接下来我们详细分析下各个类的作用。

FeignInvocationHandler

代理对象生成的核心对象是InvocationHandler ,在里边封装了方法调用时的处理逻辑。代码中的InvocationHandlerFactory默认实现会创建FeignInvocationHandler对象。下面我们仔细研究下FeignInvocationHandler是如何实现的:

1
2
3
4
5
6
7
8
9
10
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
return dispatch.get(method).invoke(args);
}
}

可以看到FeignInvocationHandler里边逻辑很简单,内部包含一个method到具体执行逻辑对象MehothHandler的一个map,然后根据method获取到MethodHandler后就可以执行对象。具体的http调用实现是在MethodHandler中实现。

SynchronousMethodHandler

​ 在Feign.Builider.build()方法中,我们知道MethodHandler使用的是同步实现类SynchronousMethodHandler,采用同步的方式进行http调用。我们看下这个类中的方法调用的主要逻辑:

1
2
3
4
5
6
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
return executeAndDecode(template, options);
}

成员变量buildTemplateFromArgs是一个RequestTemplate.Factory对象,该对象的作用是根据方法的参数 argv生成对应的RequestTemplate对象,用于底层发送HTTP请求。接下来会会执行executeAndDecode方法进行HTTP的调用。

那么SynchronousMethodHandler是怎么生成的呢?

ParseHandlersByName

Feign.Builder.build()方法中,我们可以看到一个对象ParseHandlersByName,他负责生成对应的SynchronousMethodHandler对象:

1
2
3
4
5
6
7
8
9
10
11
public Map<String, MethodHandler> apply(Target target) {
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
...
result.put(md.configKey(),
factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
...
}
return result;
}

这个类的核心功能就是根据输入的Target接口中的为每个方法生成对应的SynchronousMethodHandler。从上面代码可以看到,首先需要获取接口中每个方法上的元数据信息,接口中的每个方法上的注解表示的元信息通过Contract规范来解析,比如Feign 默认规范中@RequestLine注解用来指定HTTP访问方法以及访问的资源地址。

Contract

Contract规定了如何通过接口上的annotation来表示接口要访问的http地址,使用的http方法,http头信息等。Contract接口定义如下:

1
2
3
4
5
6
7
8
public interface Contract {
/**
* Called to parse the methods in the class that are linked to HTTP requests.
*
* @param targetType {@link feign.Target#type() type} of the Feign interface.
*/
List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType);
}

通过接口定义可以看到,Contract会根据输入的接口类生成对应的MethodMetadata,在这个类中记录了关于如何根据输入参数生成对应的RequestTemplate的元数据信息。

Feign的规范主要包含@RequestLine,@Param,@Headers,@QueryMap,@HeaderMap,@Body几个注解来表示请求的方法,url,参数,headers等信息。

当然除了默认规范外Feign还支持JAX-RS,SOAP等等,如果这些规范都不满足业务需求时,你也可以自定义自己的规范。

至此,Feign生成代理对象的总体流程就介绍完了。

执行流程

有了代理对象后,我们在来看下一个完整的方法调用的流程:

  • 访问GitHub接口的createIssue方法
  • 执行FeignInvocationHanlderinvoke方法,根据dispatch 获取方法createIssue对应的SynchronousMethodHandler对象。
  • 调用SynchronousMethodHandlerinvoke方法,方法执行时首先会调用RequestTemplate.Factory来根据访问参数获取HTTP访问的请求对象RequestTemplate,然后将请求对象交给底层的Client完成HTTP调用。

HystrixFeign

Feign可以和Hystrix结合,从而使方法调用增加熔断保护的功能。Hystrix 的使用方式是将需要的方法调用封装成一个HystrixCommand,经过上文的分析,我们知道所有的方法调用入口都是在FeignInvocationHandler中,所以很自然可以想到在FeignInvocationHanlder中将方法执行都封装成HystrixCommand就可以了,实际上HystrixFeign也是这样做的。

我们看下HystrixFeign的使用方式:

1
2
3
4
5
6
7
public class MyApp {
public static void main(String... args) {
GitHub github = HystrixFeign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
}
}

Feign代码中唯一的不同是HystrixFeign.Builder.build()方法:

1
2
3
4
5
6
7
8
9
10
11
12
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch, setterFactory,
nullableFallbackFactory);
}
});
super.contract(new HystrixDelegatingContract(contract));
return super.build();
}

可以看到最终方法调用了父类方法的build()方法,在这之前只是额外做了两件事情:

  • 设置InvocationHandlerFactory,最终生成的代理对象中的的InvocationHandler会是HystrixInvocationHandler
  • 设置Contract,将原始的ContractHystrixDelegatingContract包装了一下

第一点修改很好理解,因为我们需要将方法调用包装成HystrixCommand,但是为什么还需要修改Contract呢?

我们看下两个类中具体做了些什么:

HystrixDelegatingContract

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
List<MethodMetadata> metadatas = this.delegate.parseAndValidateMetadata(targetType);
for (MethodMetadata metadata : metadatas) {
Type type = metadata.returnType();
if (type instanceof ParameterizedType
&& ((ParameterizedType) type).getRawType().equals(HystrixCommand.class)) {
Type actualType = resolveLastTypeParameter(type, HystrixCommand.class);
metadata.returnType(actualType);
} else if (type instanceof ParameterizedType
&& ((ParameterizedType) type).getRawType().equals(Observable.class)) {
...
}
return metadatas;
}

HystrixInvocationHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Object invoke(final Object proxy, final Method method, final Object[] args)
HystrixCommand<Object> hystrixCommand =
new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
}
@Override
protected Object getFallback() {
...
}
};

if (Util.isDefault(method)) {
return hystrixCommand.execute();
} else if (isReturnsHystrixCommand(method)) {
return hystrixCommand;
} else if (isReturnsObservable(method)) {
...
}
return hystrixCommand.execute();
}

我们看到HystrixDelegatingContract只是把返回值为HystrixCommand,Observable,Signle,Completable,CompletableFuture,这几种异步执行对象的方法做了特殊处理,同样HystrixInvocationHandler除了将方法执行包装成HystrixCommand以外也对返回值为这几种类型的方法做了特殊处理。我们用一个例子来说明:

1
2
3
4
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
HystrixCommand<List<Contributor>> contributors(@Param("owner") String owner, @Param("repo") String repo);
}

这个方法返回一个HystrixCommand,它的目的并不是想要马上同步去执行HTTP访问,而是想获取一个HystrixCommand对象在业务需要的地方才去自行决定执行execute()方法来发起HTTP调用。

所以为了满足这种需求,所以在HystrixInvocationHandler中包装成的HystrixCommand就不能马上直接调用execute()方法而是直接返回HystrixCommand对象。

但是只是HystrixInvocationHandler 中做处理是不够的,因为经过Contract 解析后,方法执行的返回值也是HystrixCommand对象,但是实际底层发起HTTP请求后的返回值是HystrixCommand<List<Contributor>>中的泛型List<Contributor>,所以HystrixDelegatingContract需要对返回值为异步执行类型的方法修改方法返回值类型为它们内部对象的泛型类,比如上面contributors方法的返回值类型HystrixCommand<List<Contributor>>List<Contributor>类,这样在HystrixInvocationHandler返回时会重新返回HystrixCommand<List<Contributor>>类对象,达到获取一个异步执行对象的目的。

客户端负载均衡

在方法执行流程一节中我们可以看到,最底层发起HTTP请求是由Client来完成,ClientHttpClient的抽象接口,有多种具体实现类:OkHttpClient,JAXRSClient,RibbonClient等等。其中RibbonClient可以实现简单的客户端的负载均衡。

下面我们举个例子说明:

1
2
3
4
5
6
7
8
public class MyApp {
public static void main(String... args) {
ServiceA serviceA = Feign.builder()
.client(RibbonClient.builder().build())
.decoder(new GsonDecoder())
.target(ServiceA.class, "http://service-a");
}
}

这里使用方式上需要设置ClientRibbonClient,并且http://service-a不再是直接可以访问的实际地址,service-a作为服务A在客户端的一个clientName,clientName会映射到一个服务地址的列表。

我们来看下RibbonClient的实现:

1
2
3
4
5
public class RibbonClient implements Client {
private final Client delegate;
private final LBClientFactory lbClientFactory;
...
}

这里可以看到RibbonClient将发送HTTP请求的工作委托给其他类型的Client,自身只是做了额外的负载均衡的工作。

下面我们看下具体执行逻辑:

1
2
3
4
5
6
7
8
9
public Response execute(Request request, Request.Options options) throws IOException {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
LBClient.RibbonRequest ribbonRequest =
new LBClient.RibbonRequest(delegate, request, uriWithoutHost);
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
new FeignOptionsClientConfig(options)).toResponse();
}
  • Uri中解析出clientName,比如例子中的service-a
  • 生成service-a对应的LBClient 对象,然后执行executeWithLoadBalancer方法。

LBClient中包含成员对象ILoadBalancer,这个就是实现复杂均衡的核心类,方法调用时通过ILoadBalancer.choose()方法来选择一个服务地址发起HTTP 请求。

ILoadBalancer负责最终选取一个Server地址,ILoadBalancer中又会涉及到服务列表如何维护,选择Server规则这些方面的内容,这里我们只简单介绍RibbonClient中使用的相关类,更多详细设计可以参考项目Ribbon

我们追踪lbClient(clientName)最终会进入到LBClientFactory文件中:

1
2
3
4
5
6
7
8
9
10
public static final class Default implements LBClientFactory {
@Override
public LBClient create(String clientName) {
IClientConfig config =
ClientFactory.getNamedConfig(clientName, DisableAutoRetriesByDefaultClientConfig.class);
ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName);
// ZoneAwareLoadBalancer ,DisableAutoRetriesByDefaultClientConfig
return LBClient.create(lb, config);
}
}

这里我们会看到会得到一个ILoadBalancer对象,底层更多信息后可以知道,这里会根据DefaultClientConfigImpl配置类中获取到默认的ILoadBalancerZoneAwareLoadBalancer,挖掘它的子类可以获取几个关键的成员:

  • ServerList 定义了如何去获取服务列表。
  • ServerListFilter 更新服务列表时按需要过滤掉不符合需求的服务。
  • ServerListUpdater 更新ILoadBalancer中的服务列表的规则类。
  • IRule 方法调用时选择服务的规则。

我们可以通过自定义ServerList对象来达到控制服务列表的目的,与Eureka相结合也是通过ServerList来实现的。通过指定ServerListDiscoveryEnabledNIWSServerList类即可,DiscoveryEnabledNIWSServerList会从EurekaClient 中来获取相应的服务列表。

但是因为DiscoveryEnabledNIWSServerList这个类只是作为ZoneAwareLoadBalancer获取服务列表的方式,只有当ServerListUpdater发起服务列表更新时才会去重新到EurekaClient里获取一次服务列表,所以当Eureka中服务端服务列表有变更时,除了EurekaClient重新到服务端获取列表的延时外,还要加上ServerListUpdater更新ILoadBalancer频率的延时。

总结

​ 文章主要讲解了Feign的目标以及具体实现的原理以及和HystrixEureka是如何结合在一起的,希望能对日常使用Spring Cloud Netflix全家桶的小伙伴定位日常问题有所帮助。