SpringCloud-Feign底层源码分析

Feign能实现什么:

  • 远程调用
  • 负载均衡

在Feign之前,我们使用RestTemplate + Ribbon 来处理服务的远程调用和负载均衡,那为什么要重新开发一套呢?

主要是使用RestTemplate进行远程调用需要进行字符串的拼接,拼接http请求的url,而在微服务架构中,我们期望的方式是能够像调用本地方法一样调用远程服务提供的接口方法,Feign让这个需求得到了实现。

具体的用法之前文章已经讲过了,本文着重分析Feign的底层实现原理。

思考Feign要做的事情

  • feginClient参数的解析和装载
  • 针对指定的client,生成动态代理
  • 调用client中的方法时解析方法,组装出一个request对象发起请求,解析过程中包含负载均衡

注解@EnableFeignClients

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {...}

当我们在启动类上注解了@EnableFeignClients,启动时会导入@Import(FeignClientsRegistrar.class),这是Bean动态装载的用法,FeignClientsRegistrar就是开启feign的入口类。

FeignClientsRegistrar

1
2
3
4
5
6
7
//FeignClientsRegistrar
@Override // springboot启动时会调用这个方法
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
  • registerDefaultConfiguration() 方法从 SpringBoot 启动类上检查是否有 @EnableFeignClients,有该注解的话, 则完成 Feign 框架相关的一些配置内容注册
  • registerFeignClients() 方法从 classpath 中, 扫描获得注解了 @FeignClient 的类, 将类的内容解析为 BeanDefinition ,最终通过调用 Spring 框架中的 BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClient BeanDeifinition 添加到 spring 容器中。

重点在第二个方法,registerFeignClients()最终会在一个for循环中调用registerFeignClient方法,注册每一个feginClient,

1
2
3
4
5
6
7
8
9
10
11
//FeignClientsRegistrar
public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
...
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
...
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}

registerFeignClient方法,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器。

1
2
3
4
5
6
7
8
9
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
... //
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

此处关注一下,BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过 genericBeanDefinition 来构建的,并且传入了一个FeignClientFactoryBean的类对象。

所以每个@FeginClient注解的类最终都会注册一个FactoryBean到容器中,简单来说,当我们代码自动注入feignClient时,会通过对应的FactoryBean的getObject方法获得一个实例。

FeignClientFactoryBean.getObject()

getObject调用的是getTarget方法,它从applicationContext取出FeignContext,FeignContext继承了 NamedContextFactory,它是用来统一维护feign中各个feign客户端相互隔离的上下文。

FeignContext注册到容器是在FeignAutoConfiguration上完成的

1
2
3
4
5
6
7
8
9
// FeignAutoConfiguration
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}

在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations的来源就是在前面registerFeignClients方法中将@FeignClient的配置configuration,每个feginClient对应了一个FeignClientSpecification,内含serviceid和配置类的类对象引用。

贴一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//FeignClientFactoryBean
@Override
public Object getObject() throws Exception {
return getTarget();
}
// T泛型在这里表示的应该是FeignClient的代理类型
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
// 一般使用中都是使用服务名称调用服务,所以通常会执行这段代码
if (!StringUtils.hasText(this.url)) { // 如果url为空,则走负载均衡,生成能负载均衡的代理类
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
...// 如果制定了url,则生成默认的代理类,不常用省略不分析
}

接着,构建feign.builder,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。 FeignContext在上篇中已经提到会为每个Feign客户端分配了一个容器,它们的父容器就是spring容器

配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。实际上他们最终调用的是Targeter.target()方法。

loadBalance

该方法生成具备负载均衡能力的feign客户端

1
2
3
4
5
6
7
8
9
10
11
12
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
// 从上下文中获取一个Client,默认是LoadBalanceFeignClient
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
// 先根据服务名称获取对应上下文,再在上下文中getbean(Targeter.class)
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
...
}

LoadBalanceFeignClient在FeignRibbonClientAutoConfiguration这个自动装配类中通过Import注解进行装配的。

1
2
3
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
DefaultTarget.target
1
2
3
4
5
6
// DefaultTarget
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
return feign.target(target);
}

直接执行Feign.Builder的target方法

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

build()方法构建出一个ReflectiveFeign,再执行newInstance(target),参数Target<T> target表示即将被代理的目标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@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 handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
// jdk动态代理
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);

for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}

这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规 则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。

从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接 口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的 InvocationHandler。

targetToHandlersByName.apply(target)根据FeignClient接口的描述解析出对应的请求数据,根据Contract协议规则,解析接口类的注解信息,解析成内部表现:会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个<method,MethodHandler> 的map,放入InvocationHandler接口的实现FeignInvocationHandler中。

OpenFeign的调用过程

在前面的分析中,我们知道OpenFeign最终返回的是一个ReflectiveFeign.FeignInvocationHandler的对象。

那么当客户端发起请求时,会进入到FeignInvocationHandler.invoke方法中,这个大家都知道,它是 一个动态代理的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override //FeignInvocationHandler
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();
}

return dispatch.get(method).invoke(args);
}

在invoke方法中,会调用 this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。dispatch是分发的意思,这里的dispatch就是解析阶段生成的Map<Method, MethodHandler>,根据调用的方法,执行指定方法的SynchronousMethodHandler.invoke方法。

这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override  //SynchronousMethodHandler
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); // 执行到这里还没有进行负载均衡
} 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;
}
}
}