一,入口 —— 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("defaultConfiguration"));
}
}
② 注册FeignClient
registerFeignClients方法
分为两大部分 扫描包 和 注册FeignClient 我们接下来进入源码一一介绍:
1,扫描包找到所有FeignClient,放入候选队列
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
// 获取EnableFeignClients注解元数据信息
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 查看注解中的 Clients项是否存在
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
// 不存在则启用扫描器,扫描包下的FeignClient
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// 设置扫描注解类型为 FeignClient.class
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
// 获取注解中所有待扫描的包路径,value,basePackages,basePackageClasses,都不存在则添加EnableFeignClients标记对象所在的包路径
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
// 使用扫描器扫描并放入候选队列
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
for (Class<?> clazz : clients) {
// 将clients项放入候选队列
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
从上述我们可以明白Feigjn是如何获取项目中的所有FeignClient:
- clients值不存在时,扫描包路径:value,basePackages,basePackageClasses 或者 (前面配置项都不存在时)EnableFeignClients标记对象所在的包路径
- clients值存在时,直接使用clients中的值
2,校验并注册候选队列中的所有FeignClient标记的接口
for (BeanDefinition candidateComponent : candidateComponents) {
// 对象是否是注解Bean
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 注解的对象必须是接口
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
// 获取注解上的所有属性信息
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
// 获取Client配置名称,优先级如下:contextId, valueId, name, serviceId
String name = getClientName(attributes);
// 注册Client服务配置,名称为Client配置名称
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册FeignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
}
经过简单的验证和服务配置注册后我们来到核心方法registerFeignClient(registry, annotationMetadata, attributes)
3,registerFeignClient——注册FeignClient核心方法
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
// 获取名称 优先级 serviceId(弃用),name,value,并检验 http://+name是否合法
String name = getName(attributes);
// 构建FeignClientFactoryBean 工厂,职责是Feign创建代理类的工厂
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
// 设置构建Url,Path,404,fallback和fallbackFactory并返回代理类的supplier,clazz为注解的接口对象
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
return factoryBean.getObject();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// 设置懒加载
definition.setLazyInit(true);
// 校验fallback和fallbackFactory是否争取(放在这里校验是否有点,早期版本是放在前面的,不太明白为什么设置写完了supplier才校验)
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// 设置优先级
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
// 设置bean别名
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
// 将信息放入BeanDefinitionHolder
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
// 注册Bean
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
registerOptionsBeanDefinition(registry, contextId);
}
可以看到这里有个很重要的类 FeignClientFactoryBean
,它实际上是实现了FactoryBean 接口,这个接口是Spring提供的一个工厂接口
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
}
它里面提供了一个方法,用于获取工厂中所提供的Bean,当你实现并注册这个类时,获取该Bean名称获取的实际上是调用了 factory.getObject() 来获取其中的类对象。
而 FeignClientFactoryBean
它是创建FeignClient 代理类的工厂,他在后续的代理类生成中至关重要,我们在后面一章中来详细介绍他。
本章导图
三,FeignClientFactoryBean
在上一节我们知道FeignClientFactoryBean实现了FactoryBean,通过getObject()方法生成FeignClient,我们来详细看看他的方法实现
@Override
public Object getObject() {
return getTarget();
}
<T> T getTarget() {
//1,配置Feign.Builder
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
//2.1,未设置url,则创建loadBalance Feign
if (!StringUtils.hasText(url)) {
if (url != null && LOG.isWarnEnabled()) {
LOG.warn("The provided URL is empty. Will try picking an instance via load-balancing.");
}
else if (LOG.isDebugEnabled()) {
LOG.debug("URL not provided. Will use LoadBalancer.");
}
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
//返回具备负载均衡能力的Target
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
//2.2,设置了url,则创建具体的 FeignClient
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof FeignBlockingLoadBalancerClient) {
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
// 创建动态代理对象Target
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
我们可以看到FeignClientFactoryBean创建FeignClient对象分为两个步骤:构建Builder和创建不同的代理对象
1,Feign.Builder构建和设置配置项
在构建Feign.Builder前,他会先获取整个Feign配置的上下文环境,也就是FeignContext
。之后他便会定制Encoder,Dcoder,Contract等组件的Feign Builder模板,通过一些配置代码和文件进行Builder的设置。
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// 定制Encoder,Dcoder,Contract等组件的Feign Builder模板
Feign.Builder builder = get(context, Feign.Builder.class)
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// 通过配置代码和配置项初始化Feign.Builder
configureFeign(context, builder);
applyBuildCustomizers(context, builder);
return builder;
}
通过下面我们可以知道整个FeignClient的一个配置优先级如下:
自定义服务配置项 > 默认配置项 > 代码配置Config配置
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
: applicationContext.getBean(FeignClientProperties.class);
FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
if (properties != null && inheritParentContext) {
if (properties.isDefaultToProperties()) {
// 低优先级,代码配置Config配置
configureUsingConfiguration(context, builder);
// 中优先级,默认配置项 feign.client.default
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
// 高优先级,自定义服务配置项 feign.client.<clientName>
configureUsingProperties(properties.getConfig().get(contextId), builder);
}
//.......
}
else {
//通过开关优先使用代码配置项
configureUsingConfiguration(context, builder);
}
}
2.1,创建loadBalance Feign
在了解LoadBalance时我们先了解一下Client
接口,Client接口它有许多HttpClient实现类,他的功能就是将request发出然后将返回值封装进入Response中
public interface Client {
Response execute(Request var1, Request.Options var2) throws IOException;
...
}
他的实现类如下,他会通过一些自动配置类(10.12移除了自动配置类,需要自行构建Bean),来使用其他框架的HttpClient,例如OkHttp,ApacheHttp等等,他有一个默认实现的即Client.Default
可以看到Client.Default
中包含了一些SSL和域名校验的配置,可以通过自行配置Default来构造Feign Https链路。
public static class Default implements Client {
private final SSLSocketFactory sslContextFactory;
private final HostnameVerifier hostnameVerifier;
private final boolean disableRequestBuffering;
public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
this.sslContextFactory = sslContextFactory;
this.hostnameVerifier = hostnameVerifier;
this.disableRequestBuffering = true;
}
}
再回到loadbalance方法我们可以发现,Client或根据当前使用HttpClient框架选择注入不同的RibbonClient,并将client放入Fegin.Builder
中来生成代理对象Targeter
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
// 获取Client
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-loadbalancer?");
}
四,生成代理对象
我们可以看到DefaultTargeter
会使用 FeignBuilder的target()方法生成代理对象
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}
在进行Feign的一系列组件配置后,创建ReflectiveFeign来进行动态代理Feign创建
public <T> T target(Target<T> target) {
return this.build().newInstance(target);
}
public Feign build() {
// Feign Http调用客户端,默认为 Client.Default
Client client = (Client)Capability.enrich(this.client, this.capabilities);
// 重试器
Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
// Feign 请求拦截器
List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {
return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
}).collect(Collectors.toList());
// 日志组件,默认slf4j
Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
// 接口协议
Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
// 配置类
Request.Options options = (Request.Options)Capability.enrich(this.options, this.capabilities);
// 编码器
Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
// 解码器
Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
// 创建 InvocationHandler 的工厂类
InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
// 接口方法处理器工厂
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
// 解析 springmvc 注解
ReflectiveFeign.ParseHandlersByName handlersByName = new ReflectiveFeign.ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
// ✨ 动态创建Feign对象
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
ReflectiveFeign.newInstance()
会通过InvocationHandlerFactory.create()
来进行动态代理创建InvocationHandler
public <T> T newInstance(Target<T> target) {
//通过contract(即SpringMvcContract)将FeignClient接口转换成MethodHandler
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);
// 用 Proxy 创建动态代理,动态代理对象就是 SynchronousMethodHandler
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
他的默认实现是InvocationHandlerFactory.Default
然后创建一个ReflectiveFeign.FeignInvocationHandler
public static final class Default implements InvocationHandlerFactory {
public Default() {
}
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
五,一图流总结
🎉特别感谢( bojiangzhou 大佬)🎉