一,入口 —— Feign的核心注解
Feign是我们在分布式开发中常用的RPC框架,关于Feign远程调用的秘密,我有很多想要探究的例如:
- Feign是如何收集FeignClient的?
- Feign是如何配置FeignClient的,让其拥有降级重试的能力?
- Feign整个远程调用的流程是怎么样的?
- Feign他是如何创建代理类的?
- Feign他是怎么去将一个Interface里面的接口方法作为请求发送的?
- Feign是如何兼容这么多HttpClient框架的?
- Feign他是怎么做到负载均衡的,如何和Ribbon结合的?
这么多繁杂的问题,往往扰人心智,不如我们从我们开发中最常见最常用的两个注解入手。
①,@EnableFeignClients
这个注解通常标注在启动类上,作用看起来和@SpringBootApplication
一样,我们姑且猜测他的职能也和@SpringBootApplication
一样是负责开启Feign功能以及扫描相关类,我们直接进入@EnableFeignClients
来探究源码。
@Import({
FeignClientsRegistrar.class})
public @interface EnableFeignClients {
// 根据包路径扫描其下的FeignClient,类似于basePackages的简写
String[] value() default {
};
// 根据包路径扫描其下的FeignClient
String[] basePackages() default {
};
// 指定标记的接口来扫描包
Class<?>[] basePackageClasses() default {
};
// Feign客户端默认的全局配置类
Class<?>[] defaultConfiguration() default {
};
// 指定@FeignClient注解的类,直接使用这几个@FeignClient,此时会ban掉类路径扫描
Class<?>[] clients() default {
};
}
可以通过源码和注释看出来,@EnableFeignClients
的作用是用来扫描@FeignClient
标注的类和指定全局配置类,值得注意的是在该注解最上方还有一个
@Import({FeignClientsRegistrar.class})
在我们使用@EnableFeignClients
时 FeignClientsRegistrar
也会被引入至SpringBean容器的上下文。从名称可以看出这是FeignClient的注册类,我们再大胆猜测一下,FeignClientsRegistrar
该类的职能应该是扫描和注册FeignClients,他肯定是接下来我们源码阅读的关接类之一,再次我们现在这里将其Mark一下,稍后回来。
②,@FeignClient
对于这个接口我们通常是标记在一个远程调用RPC的Interface类上面,但是其作用是用来标记还是用来声明呢,这个作用其实光从名称很难理解,我们可以看看作者留给我们的注释来熟悉一二:
注解用于声明某接口应创建为 REST 客户端(例如,用于自动注入到其他组件中)。如果 SC LoadBalancer 可用,它将用于对后端请求进行负载均衡,且负载均衡器可以使用与 Feign 客户端相同的名称(即
value
)进行配置。
看完注释我们再结合代码来进行理解:
public @interface FeignClient {
@AliasFor("name")
// 服务名称 (name的简化版)
String value() default "";
// 接口生成的动态代理的bean Id
String contextId() default "";
@AliasFor("value")
// 服务名称 (name的简化版)
String name() default "";
String[] qualifiers() default {
};
// 服务的url,对于开启了Loadbalance的服务无需使用url,如果没有开启则需要一个绝对的地址
String url() default "";
// 如果为false则404时默认抛出 FeignException异常,为true则抛出404异常
boolean decode404() default false;
// Feign客户端本身的配置类,可以对 feign.codec.Decoder, feign.codec.Encoder, feign.Contract.等等进行自定义
Class<?>[] configuration() default {
};
// 失败回调方法
Class<?> fallback() default void.class;
// 用于生成fallback类实例
Class<?> fallbackFactory() default void.class;
// 定义当前FeignClient的统一前缀
String path() default "";
boolean primary() default true;
}
看完源码和注释后,我们可以总结出FeignClient的功能:
① 用来声明FeignClient的一些基本属性,例如:bean name,url,服务名称,path等等
② 对FeignClient的一些核心功能组件进行配置,核心配置组件如下图所示(截图取自 bojiangzhou 大佬)
③ , Feign客户端配置和全局配置
看到上面两个主即我们可以发现,不管是@EnableFeignClients
和 @FeignClient
都有配置项选择,那对于配置项我们该怎么使用呢
1,yaml配置项
feign:
client:
config:
# 默认全局配置
default:
# 指定客户端名称
genius-client:
2,代码配置项
public class GeniusFeignConfiguration {
@Bean
public Retryer feignRetryer() {
return new Retryer.Default();
}
}
@FeignClient(value = "genius-client", configuration = GeniusFeignConfiguration.class)
public interface GeniusFeignClient{
}
可以看到对于客户端配置和全局配置的使用都很简单方便,但是值得注意的一点是这两个的配置项使用优先级是不同,这个我们后续再谈。
本章导图
二,FeignClientsRegister
我们重新回到之前mark的FeignClientsRegister
先来看看整个类的实现
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware{
private ResourceLoader resourceLoader;
private Environment environment;
}
可以看到FeignClientsRegister
实现了三个接口
ResourceLoaderAware
:注入资源加载器 ResourceLoaderEnvironmentAware
:注入环境 EnvironmentImportBeanDefinitionRegistrar
:注册并注入BeanDefinition
BeanDefinition 实际上是Spring bean的元数据,它保存了Bean的很多属性,我们通常注册BeanDefinition的方式有 @Component,@Bean等。而实现
ImportBeanDefinitionRegistrar
也是一种注册BeanDefinition 的方式,它通过registerBeanDefinitions
方法来实现BeanDefinition 的注册。
通过这三个实现的接口,我们也找到了核心函数 registerBeanDefinitions
,我们来看看他的代码:
/**
* @param metadata @EnableFeignClients 注解的元数据
* @param registry BeanDefinition 注册器
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册默认配置项
registerDefaultConfiguration(metadata, registry);
// 注册FeignClients
registerFeignClients(metadata, registry);
}
可以看出整个BeanDefinitions的注册分为两个步骤:注册默认配置项 和 注册FeignClients
① 注册默认配置项
这一步是将@EnableFeignClients上的defaultConfiguration内容,给注册为BeanDefinitions,如果不存在则不注册
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 获取注解上的属性信息
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
// defaultConfiguration不为空则注入自定义默认配置项
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
// 查看注解是不是标记在内部类上
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
// 注册自定义默认配置项
registerClientConfiguration(registry, name, defaultAttrs.get(