前言

前情回顾

上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是

将EnableFeignClients注解对应的配置属性注入,将FeignClient注解对应的属性注入。

最后是生成FeignClient对应的bean,注入到Spring 的IOC容器。

本讲目录

目录如下:

  1. registerFeignClient()回顾
  2. FeignClientFactoryBean.getObject()解析
  3. Feign.builder()及client()构建逻辑
  4. 创建Feign动态代理实现细节

说明

原创不易,如若转载 请标明来源!

博客地址:一枝花算不算浪漫

微信公众号:壹枝花算不算浪漫

源码分析

registerFeignClient()回顾

回顾下之前的代码:

private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); String alias = name + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
} BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

registerFeignClient()方法中构造了一个BeanDefinitionBuilder对象,BeanDefinitionBuilder的主要作用就是构建一个AbstractBeanDefinition,AbstractBeanDefinition类最终被构建成一个BeanDefinitionHolder 然后注册到Spring中。

beanDefinition类为FeignClientFactoryBean,故在Spring获取类的时候实际返回的是FeignClientFactoryBean类。

FeignClientFactoryBean作为一个实现了FactoryBean的工厂类,那么每次在Spring Context 创建实体类的时候会调用它的getObject()方法。

FeignClientFactoryBean.getObject()解析

这里直接分析FeignClientFactoryBean.getObject()方法,这里包含着Feign动态代理的原理。

先看下代码:

@Override
public Object getObject() throws Exception {
// 可以类比于ribbon中的SpringClientFactory,每个服务都对应一个独立的spring容器
FeignContext context = applicationContext.getBean(FeignContext.class);
// builder中包含contract、logLevel、encoder、decoder、options等信息
Feign.Builder builder = feign(context); // 如果@FeignClient注解上没有指定url,说明是要用ribbon的负载均衡
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
// 这里构建的url类似于:http://serviceA
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not lod balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
} public <T> T getInstance(String name, Class<T> type) {
// getContext是从SpringClientContext中获取,之前讲ribbon源码时讲过
// 一个serviceName都会有自己的一个SpringClientContext上下文信息
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
// 这里是获取到LoadBalancerFeignClient
return context.getBean(type);
}
return null;
}

首先是FeignContext ,我们可以类比下ribbon中的SpringClientFactory, 每个服务的调用,都有一个独立的ILoadBalancer、IRule、IPing等等,每个服务都对应一个独立的spring容器,从那个独立的容器中,可以取出这个服务关联的属于自己的LoadBalancer之类的东西。

如果我们调用一个服务的话,比如ServiceA,那么这个服务就会关联一个spring容器,FeignContext就代表一个独立的容器,关联着自己独立的一些组件,例如Logger组件、Decoder组件、Encoder组件等等。

我们可以看下FeignAutoConfiguration中:

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
// configurations是一个Map结构
context.setConfigurations(this.configurations);
return context;
}
} public class FeignContext extends NamedContextFactory<FeignClientSpecification> { public FeignContext() {
// FeignClientsConfiguration中会加载Encoder、Decoder、Logger等组件
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}

这里可以知道FeignContext的结构,里面其实就是封装了一个服务实例(ServiceA)对应的各种组件,其中FeignClientsConfiguration是加载默认的组件信息配置类。

接下来还是回到FeignClientFactoryBean.getObject()中,接着看feign()方法:

protected Feign.Builder feign(FeignContext context) {
// 从context中获取到默认Logger组件:Slf4jLogger
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type); // 从context中找type:Feign.Builder.class 对应的组件信息
// 然后往builder中放入各种组件信息
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on configureFeign(context, builder); return builder;
} protected <T> T get(FeignContext context, Class<T> type) {
// context中转载的有Logger组件信息,这里默认的是Slf4jLogger
T instance = context.getInstance(this.name, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for "
+ this.name);
}
return instance;
}

这里是构造一个Feign.builder()对象,里面还是封装了各种组件信息。其中Feign.builder在FeignClientsConfiguration被初始化,一般使用的是HystrixFeign.builder()

@Configuration
public class FeignClientsConfiguration {
// 一般环境都会配置feign.hystrix.enabled = true,这里直接看HystrixFeign.builder();
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
}

接着看configureFeign() 方法,这个方法是读取application.properties中的配置信息。这里有个很有趣的配置:

configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);

如果我们配置feign,先指定一个全局配置,在指定针对于某个服务的配置,那么某个服务配置的优先级会覆盖全局的配置。

一张图总结下Feign.builder()构建的过程:

Feign.builder()及client()构建逻辑

还是接着上面getObject() 方法去分析,上面分析完了Feign.builder()的构建,下面接着看看剩下的代码。

loadBalance(builder, context, new HardCodedTarget<>(this.type,this.name, url));

这里形式构造了一个HardCodeTarget对象,这个对象包含了接口类型(com.barrywang.service.feign.ServiceAFeignClient)、服务名称(ServiceA)、url地址(http://ServiceA),跟Feign.Builder、FeignContext,一起,传入了loadBalance()方法里去。

接着查看loadBalance() 方法:

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
// 这里还是从context中获取feignClient数据
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?");
} protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(this.name, type);
}

这里还是从context中获取Client.class对应的数据,我们继续查看FeignAutoConfiguration 类,但是并没有发现Feign.client相关的数据,查看FeignAutoConfiguration的依赖,可以找到FeignRibbonClientAutoConfiguration ,代码如下:

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
// 这里会import三个FeignLoadBalance配置
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration { @Bean
@Primary
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory cachingLBClientFactory(
SpringClientFactory factory) {
return new CachingSpringLoadBalancerFactory(factory);
} @Bean
@Primary
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory,
LoadBalancedRetryPolicyFactory retryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);
} // Options是超时相关的配置
@Bean
@ConditionalOnMissingBean
public Request.Options feignRequestOptions() {
return LoadBalancerFeignClient.DEFAULT_OPTIONS;
}
} @Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}

到了这里就知道了,这里Feign.client默认应该就是LoadBalancerFeignClient了。

到这继续用一张图总结下:

创建Feign动态代理实现细节

接着上面代码,默认Feign.client()为LoadBalancerFeignClient, 然后将client加入到builder中。接着继续跟进targer相关:

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
// 这里又是通过Targer然后再context中获取默认配置
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?");
} protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.name, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for "
+ this.name);
}
return instance;
}

可以看到,这里又是通过Targeter.class从context中获取对应默认Targter。我们继续通过FeignAutoConfiguration中进行查找:

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration { @Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>(); @Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
} // 如果配置了feign.hystrix.HystrixFeign 则创建HystrixTargeter
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
} // 如果没有配置feign.hystrix.HystrixFeign 则创建DefaultTargeter
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
}

在默认情况下,feign是和hystrix整合的,feign.hystrix.HystrixFeign会有配置,所以这里默认Targeter使用的是HystrixTargeter, 在loadBalance()方法中执行的targeter.target()方法就是执行HystrixTargeter.target()方法:

class HystrixTargeter implements Targeter {
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
// 判断Feign.builder()类型
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
} // 最终都会执行feign.target()方法
return feign.target(target);
} public abstract class Feign { public static Builder builder() {
return new Builder();
} /**
* Returns a new instance of an HTTP API, defined by annotations in the {@link Feign Contract},
* for the specified {@code target}. You should cache this result.
*/
public abstract <T> T newInstance(Target<T> target); public static class Builder { // 省略部分代码 public <T> T target(Target<T> target) {
return build().newInstance(target);
} public Feign build() {
// 构建一个SynchronousMethodHandler工厂
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404); // 构建
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
}
}

这里主要是build方法,构造了一个ReflectieFein对象,接着看它里面的newInstance()方法:

@Override
public <T> T newInstance(Target<T> target) {
// nameToHandler是@FeignClient中的方法名对应的MethodHandler对象
Map<String, InvocationHandlerFactory.MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = new LinkedHashMap<Method, InvocationHandlerFactory.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 {
// 将具体的method作为map的key值
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
} // JDK动态代理 返回类似于:ReflectiveFeign$FeignInvocationHandler@7642
// methodToHandler中包含Feign.builder()、Feign.client()等信息
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}

这里就是使用了JDK动态代理,实际上返回的Feign动态代理的对象类似于:ReflectiveFeign$FeignInvocationHandler@7642

这也和我们第一讲中的debug截图一致了,到了这里feign动态代理对象的生成原理都已经很清楚了。

最后debug一下,看下最终生成的动态代理对象:

总结

最后用一张图总结Feign动态代理生成的规则:

  1. 生成Feign.builder(),里面包含Encoder、Decoder、Logger等组件,还有application.properties中相关的feign client配置信息
  2. 生成Feign.client(),默认为LoadBalancerFeignClient
  3. 生成默认Targter对象:HystrixTargter
  4. builder、client、targter 通过JDK动态代理生成feign动态代理对象

一张图总结:

申明

本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!

感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫

【一起学源码-微服务】Feign 源码二:Feign动态代理构造过程的更多相关文章

  1. 【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结

    前言 想说的话 这里已经梳理完Eureka.Ribbon.Feign三大组件的基本原理了,今天做一个总结,里面会有一个比较详细的调用关系流程图. 说明 原创不易,如若转载 请标明来源! 博客地址:一枝 ...

  2. 【一起学源码-微服务】Ribbon源码五:Ribbon源码解读汇总篇~

    前言 想说的话 [一起学源码-微服务-Ribbon]专栏到这里就已经全部结束了,共更新四篇文章. Ribbon比较小巧,这里是直接 读的spring cloud 内嵌封装的版本,里面的各种config ...

  3. 【一起学源码-微服务】Nexflix Eureka 源码十:服务下线及实例摘除,一个client下线到底多久才会被其他实例感知?

    前言 前情回顾 上一讲我们讲了 client端向server端发送心跳检查,也是默认每30钟发送一次,server端接收后会更新注册表的一个时间戳属性,然后一次心跳(续约)也就完成了. 本讲目录 这一 ...

  4. 【一起学源码-微服务】Nexflix Eureka 源码十三:Eureka源码解读完结撒花篇~!

    前言 想说的话 [一起学源码-微服务-Netflix Eureka]专栏到这里就已经全部结束了. 实话实说,从最开始Eureka Server和Eureka Client初始化的流程还是一脸闷逼,到现 ...

  5. 微服务架构 | 4.2 基于 Feign 与 OpenFeign 的服务接口调用

    目录 前言 1. OpenFeign 基本知识 1.1 Feign 是什么 1.2 Feign 的出现解决了什么问题 1.3 Feign 与 OpenFeign 的区别与对比 2. 在服务消费者端开启 ...

  6. 31.【微服务架构】SpringCloud之Feign(五)

    Feign简介 Feign 是一个声明web服务客户端,这便得编写web服务客户端更容易,使用Feign 创建一个接口并对它进行注解,它具有可插拔的注解支持包括Feign注解与JAX-RS注解,Fei ...

  7. 通过Dapr实现一个简单的基于.net的微服务电商系统(二)——通讯框架讲解

    首先感谢张队@geffzhang公众号转发了上一篇文章,希望广大.neter多多推广dapr,让云原生更快更好的在.net这片土地上落地生根. 目录:一.通过Dapr实现一个简单的基于.net的微服务 ...

  8. 通过Dapr实现一个简单的基于.net的微服务电商系统(二十)——Saga框架实现思路分享

    今天这篇博文的主要目的是分享一下我设计Saga的实现思路来抛砖引玉,其实Saga本身非常的类似于一个简单的工作流体系,相比工作流不一样的部分在于它没有工作流的复杂逻辑处理机制(比如会签),没有条件分支 ...

  9. 【一起学源码-微服务】Feign 源码一:源码初探,通过Demo Debug Feign源码

    前言 前情回顾 上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是DiscoveryEnableNIWSServerList,同时在Dynam ...

随机推荐

  1. H3C V.24接口线缆

  2. HTML静态网页--JavaScript-DOW操作

    1.DOM的基本概念 DOM是文档对象模型,这种模型为树模型:文档是指标签文档:对象是指文档中每个元素:模型是指抽象化的东西. 2.Windows对象操作 一.属性和方法: 属性(值或者子对象): o ...

  3. LA 5031 Graph and Queries —— Treap名次树

    离线做法,逆序执行操作,那么原本的删除边的操作变为加入边的操作,用名次树维护每一个连通分量的名次,加边操作即是连通分量合并操作,每次将结点数小的子树向结点数大的子树合并,那么单次合并复杂度O(n1lo ...

  4. Java日志框架——JCL

    JCL,全称为"Jakarta Commons Logging",也可称为"Apache Commons Logging". 一.JCL原理 1.基本原理 JC ...

  5. 清除SVN未版控文件

    用Git时,git clean -df 可以清除所有没有add的文件,得到一个干净的工作空间. 用SVN没有这样的命令,当然可以 svn export 得到一个干净的工作空间,但会花很长时间,而且没有 ...

  6. monaco-editor使用

    monaco-editor是一款非常好用的web代码编辑器,那么如何把他加到自己的项目中呢. 1.下载插件 npm install monaco-editor@0.8.3 2.初始化编辑器值 < ...

  7. JMeter分布式负载测试(吞吐量控制器)

    在本节中,我们将学习如何使用吞吐量控制器在JMeter中创建分布式负载测试计划. 出于测试目的,我们将在我们网站 www.yiibai.com 的URL下的某些网页上创建分布式负载.这些网页包括: 主 ...

  8. linux下文件的一些文件颜色的含义

    红色---->代表压缩文件 红色闪烁---->代表链接文件有问题 黄色---->代表设备文件 深蓝色---->代表目录 浅蓝色----->代表链接文件 绿色----> ...

  9. 字符串format格式方式

    1.format后面都是个元组类型!!! 不一一对应则报错,比如少了一个替换的元素,format可以少但是不能多,一多就会报错 括号里可以加索引,看例2 可以用索引只取一个值,比如括号里都是{1} r ...

  10. 【Composer】PHP开发者必须了解!

    Composer是一个非常流行的PHP包依赖管理工具,已经取代PEAR包管理器,对于PHP开发者来说掌握Composer是必须的. 对于使用者来说Composer非常的简单,通过简单的一条命令将需要的 ...