OpenFeign--核心流程
背景/绪论:
在调用Feign的时候经常遇到各种奇奇怪怪的问题,包括但不限于反序列化失败,调用失败,回调失败,序列化后的参数不对,类型不对等问题,于是对Feign的启动流程进行了简要了解,着重对正反序列化的客制化配置以及可能会遇到的问题进行了阐述,希望能为各位解决相关问题提供一点绵薄之力。
文章大体内容转载自:
https://www.cnblogs.com/zyly/p/14669806.html
郑重声明:
本文仅对Feign的启动原理和过程做简要阐述,达到抛砖引玉的效果,不对非SpringCloud包下的其他模块负责(因为我菜🌽),如果实在感兴趣,可以以后慢慢互相进行分享。
相关知识:
Feign配置:
https://blog.csdn.net/u010862794/article/details/73649616
BeanDefination:
https://juejin.cn/post/6956230722512224292
Spring钩子函数:
https://www.cnblogs.com/zyly/p/13236679.html#_label5
Spring ComponentScan源码解析:
https://blog.csdn.net/it_lihongmin/article/details/103557198
FactoryBean源码解析:
https://juejin.cn/post/6844903954615107597
BeanFactory和FactoryBean的区别:
https://www.cnblogs.com/aspirant/p/9082858.html
Feign启动原理:
@EnableFeignClients注解启动
总体流程图:

代码入口:
在SpringCloud中,@FeignClient 注解需要通过@EnableFeignClients注解来提供解析的功能,否则前者将不起作用,所以可以把@EnableFeignClients注解当作代码的切入点。
在启动类中使用了@EnableFeignClients注解,并且提供了basePackages作为注解的属性。那么就进入这个注解中,查看其做了什么?

可以看到这个注解提供了比basePackage更加细粒度配置属性basePackageClasses和clients,但是因为业务原因,大多数情况下还是选择basePackages。
那么这个类是如何对@FeignClient 的类进行扫描的呢?
答案就在类头部的@Import 注解里面,这引用了FeignClientsRegistrar.class
## 
FeignClientsRegistrar
进入FeignClientsRegistrar类的源代码中,发现其实现和继承关系为:
```plain
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware
实现了三个接口,其中有ImportBeanDefinitionRegistrar,实现这个接口的类需要实现方法,在实现该方法后,用以完成相关的Bean注册。详情可见-相关知识--Spring钩子函数。
在FeignClientsRegistrar类的registerBeanDefinitions方法主要负责动态注入 IOC Bean,分别注入 Feign 配置类、FeignClient。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注入Feign配置类
registerDefaultConfiguration(metadata, registry);
// 注入FeignClient
registerFeignClients(metadata, registry);
}
注入Feign的全局默认配置类
进入第一个注入Feign配置类的方法-- registerDefaultConfiguration
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 获取EnableFeignClients注解的属性
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
// 如果defaultAttrs不为空,并且配置了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"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
上述代码干了以下几件事:
- 获取 @EnableFeignClients 注解上的属性以及对应 value。
- 使用BeanDefinitionBuilder构造器为FeignClientSpecification类生成BeanDefinition,这个BeanDefinition是对FeignClientSpecification Bean的定义,保存了FeignClientSpecification Bean 的各种信息,如属性、构造方法参数等。其中@EnableFeignClients 注解上的defaultConfiguration属性就是作为构造方法参数传入的。而bean名称为 default. + @EnableFeignClients 修饰类(一般是启动类)全限定名称 + FeignClientSpecification,所以这个BeanDefinition的粒度是全体FeignClients,详情请参见:相关知识--BeanDefinition
- @EnableFeignClients defaultConfiguration 默认为 {},如果没有相关配置,默认使用 FeignClientsConfiguration 并结合 name 填充到 FeignClientSpecification,最终会被注册为 IOC Bean
![图片]()
注入FeignClients
进入第二个方法registerFeignClients中,看看到底做了些什么
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
// 将要配置的client取出
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
// 如果没有取出clients,则扫描basePackage下的所有加了@FeignClient的类
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// 对于所有取出后的clients形成的BeanDefinition
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 判断是不是接口类型,如果不是,报错
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
// 获取@FeignClient上的注解以及其对应的值
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
// 加载单Client的名称 contextId>value>name>serviceId,如果都没有,就报错
String name = getClientName(attributes);
// 对于单Client,生成并注册其专属FeignClientSpecification
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
注册单个Client
参考:https://blog.csdn.net/it_lihongmin/article/details/109027896
对于单个Client,进行FeignClient注册,代码如下:
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 通过ClassName来找到Class
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
// 获取上下文id
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
// 生成FeignClientBean工厂
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(clazz, () -> {
// 设置bean工厂的url,path,是否解码404,回调方法和工厂等参数
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));
}
// 返回的是bean工厂的getObject方法的返回值
return factoryBean.getObject();
});
// 设置bean定义的自动装配和初始化属性
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
// 使用指定的bean工厂注册给定的bean定义
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
上述代码做的工作:
1. 初始化FeignClientFactoryBean类型的BeanDefinition,将所有以@FeignClient修饰的接口用FactoryBean的形式获取,最终的加强返回值为factoryBean.getObject() 。由于FeignClientFactoryBean 继承自 FactoryBean,也就是说,当我们定义 @FeignClient 修饰接口时,注册到 IOC 容器中 Bean 类型变成了 FeignClientFactoryBean。
在 Spring 中,FactoryBean 是一个工厂 Bean,用来创建代理 Bean。工厂 Bean 是一种特殊的 Bean,对于需要获取 Bean 的消费者而言,它是不知道 Bean 是普通 Bean 或是工厂 Bean 的。工厂 Bean 返回的实例不是工厂 Bean 本身,该实例是由工厂 Bean 中 FactoryBean#getObject 逻辑所创建的。
2. 将@FeignClient上的注解信息解析到BeanDefinition中
3. 最后将BeanDefinition以holder形式注册到给定的容器中
前文总结:
上文提到,最终每一个@FeignClient 所注解的接口最后都会被FeignClientFactoryBean所代理,所生成。总结下来,就是为每一个@FeignClient创建一个FeignClientSpecification、FeignClientFactoryBean,其中FeignClientSpecification保存这个@FeignClient的configuration 属性信息,而FeignClientFactoryBean中收集了这个FeignClient其他的属性。由于FeignClientFactoryBean 继承自 FactoryBean,也就是说,当我们定义 @FeignClient 修饰接口时,注册到 IOC 容器中 Bean 类型变成了 FeignClientFactoryBean,在 Spring 中,FactoryBean 是一个工厂 Bean,用来创建代理 Bean。工厂 Bean 是一种特殊的 Bean,对于需要获取 Bean 的消费者而言,它是不知道 Bean 是普通 Bean 或是工厂 Bean 的。工厂 Bean 返回的实例不是工厂 Bean 本身,该实例是由工厂 Bean 中 FactoryBean#getObject 逻辑所创建的。
再回顾一下开头的总体流程图,是不是就清晰很多呢。

FeignClient创建过程分析:
FeignClientFactroyBean:
FeignClientFactoryBean继承关系:

1 .它会在类初始化时执行一段逻辑:依据InitializingBean 接口。
2.如果它被别的类 @Autowired 进行注入,返回的不是它本身,而是 FactoryBean#getObject 返回的类:依据 Spring FactoryBean 接口。
3.它能够获取 Spring 上下文对象:依据 Spring ApplicationContextAware 接口。
FeignClientFactoryBean的getObject方法:将功能委派给getTarget实现
FeignClientFactoryBean#getTarget
@Override
public Object getObject() {
return getTarget();
}
所以移步到getTarget函数内,鉴于函数太长,我们分步来看。
<T> T getTarget() {
FeignContext context = beanFactory != null
? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
......
}
这里提出一个疑问?FeignContext是什么, 什么时候、在哪里被注入到 Spring 容器里的?
用了 SpringBoot 怎么会不使用自动装配的功能呢,FeignContext 就是在 FeignAutoConfiguration 中被成功创建。

在FeignAutoConfiguration中,向Spring容器注入FeignContext :

并设置其配置为configurations ,而configurations 是通过@Autowired注入,即List
FeignContext自动注入
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
// 在这里获取全部的FeignClient的FeignClientSpecification
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
// 将其作为属性注入到FeignContext上下文中
context.setConfigurations(this.configurations);
return context;
}
...
}
接着我们回到代码中,往下走一行,到达feign(context)函数
feign(context):
刚刚我们提到,在FeignContext中拥有所有client的specificion信息,而specifiction存储着所有的client的configeration,也就是说,在创建对应的代理类的时候,注解中的配置信息是可见的。
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// @formatter:off
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);
applyBuildCustomizers(context, builder);
return builder;
}
通过上下文对象,创建Feign的builder(建造者模式),然后进行Feign配置以及客制化,最后返回builder。
所以我们进入到get方法中,看看有什么玄机。
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.contextId, type);
if (instance == null) {
throw new IllegalStateException(
"No bean found of type " + type + " for " + this.contextId);
}
return instance;
}
//FeignContext方法
public <T> T getInstance(String name, Class<T> type) {
//根据name获取context实例
AnnotationConfigApplicationContext context = getContext(name);
//根据type类型从子容器获取Bean实例
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
移步到根据name获取context实例的getContext方法中,看根据name从容器的contexts中获取子容器的操作是线程安全的单例缓存模式。
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
具体的根据name创建context,其内的关键方法都在springframework包下,不在本文讨论范围。
//这里的name是@FeignContent中的contentId值
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//@FeignClient没有配置configuration属性 不会执行 this.configurations 保存的是FeignClientConfiguration类型的列表,也就是之前我们介绍到的注入Spring容器中的FeignClient配置
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
// @EnableFeignClient没有配置defaultConfiguration属性 不会执行
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
// 注入默认配置类FeignClientsConfiguration,会注入默认的feignEncoder、feignDecoder等
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
//设置父容器、子容器不存在去父容器查找
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
回到getInstance方法中,在创建context方法后,根据type去获取context中的对象,其内的关键方法都在springframework包。
让我们回到feign方法
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// @formatter:off
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);
applyBuildCustomizers(context, builder);
return builder;
}
configureFeign 方法主要进行一些配置赋值,比如超时、重试、404 配置等,就不再细说了。
到这里有必要总结一下创建 FeignClientFactoryBean的前半场代码 :
- 注入@FeignClient 服务时,其实注入的是 FactoryBean#getObject 返回代理工厂对象。
2.通过 IOC 容器获取 FeignContext 上下文。
3.,创建 Feign.Builder 对象时会创建 Feign 服务对应的子容器。
4.从子容器中获取日志工厂、编码器、解码器等 Bean 为 Feign.Builder 设置配置,比如超时时间、日志级别等属性,每一个服务都可以个性化设置。
让我们回到getTarget方法:
{
......
Feign.builder = feign(context);
// 如果没有配置url,则走负载均衡模块获取对应的url
if (!StringUtils.hasText(url)) {
if (LOG.isInfoEnabled()) {
LOG.info("For '" + name
+ "' URL not provided. Will try picking an instance via load-balancing.");
}
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
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 LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((RetryableFeignBlockingLoadBalancerClient) client)
.getDelegate();
}
builder.client(client);
}
// 否则直接走以下分支
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(type, name, url));
}
接着走入Feign#target实现中,

首先会创建反射类 ReflectiveFeign,其中ReflectiveFeign是Feign的实现类:
Feign#build()
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
// Capability.enrich将核心工件暴露出来,允许客户端做一定程度的客制化实现
// 使用Capability对各个组件进行包装
// 在对对象进行封装的时候,扫描给定的对象中的enrich方法,如果返回值等于目标值,则触发并且回调
Client client = Capability.enrich(this.client, capabilities);
Retryer retryer = Capability.enrich(this.retryer, capabilities);
List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
.map(ri -> Capability.enrich(ri, capabilities))
.collect(Collectors.toList());
Logger logger = Capability.enrich(this.logger, capabilities);
Contract contract = Capability.enrich(this.contract, capabilities);
Options options = Capability.enrich(this.options, capabilities);
Encoder encoder = Capability.enrich(this.encoder, capabilities);
Decoder decoder = Capability.enrich(this.decoder, capabilities);
InvocationHandlerFactory invocationHandlerFactory =
Capability.enrich(this.invocationHandlerFactory, capabilities);
QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
// 这里将刚刚暴露出的字段进行封装,依次装填后填入ReflectiveFeign类中,返回
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);
}
}
接着对返回后的ReflectiveFeign调用其instance(target)方法:
ReflectiveFeign#newInstance
public <T> T newInstance(Target<T> target) {
//将装饰了@FeignClient的接口方法封装为方法处理器,包括Spring MVC注解逻辑处理
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//接口方法对应的MethodHandler
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
//添加JDK8以后出现的接口中默认方法
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
//1.如果是object 方法跳过 2.default方法添加defaultMethodHandlers 3、否则添加methodToHandler
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)));
}
}
//根据targert、methodToHandler创建InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
//根据JDK Proxy创建动态代理类
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
- 将@FeignClient 接口方法封装为 MethodHandler 包装类,每一个方法对应一个MethodHandler,MethodHandler的实现类是SynchronousMethodHandler。
![图片]()
可以看到每个MethodHandler都包含了客户端、日志、请求模板、编码解码器等参数,通过这些参数就可以构建相应接口的Http请求。
-
遍历接口中所有方法,过滤 Object 方法,并将默认方法以及 FeignClient 方法分类。
-
创建动态代理对应的 InvocationHandler ,默认InvocationHandler 的实现类为ReflectiveFeign.FeignInvocationHandler,然后利用Proxy.newProxyInstance创建 Proxy 实例。
-
接口内 default 方法绑定动态代理类。
后续对于所有的请求都会调用代理的方法invoke(),而这里的InvocationHandler的实现类为ReflectiveFeign,所以为了理解服务分发的原理,应该查看ReflectiveFeign#invoke方法。
ReflectiveFeign#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
// 在隐藏的代码中对equals,hashCode,toString方法都做了兼容
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();
}
// 为服务提供方法分发,dispatch中存储着方法到方法解决器的映射
return dispatch.get(method).invoke(args);
}
在前文我们提到,在Feign中,MethodHandler的实现类为SynchronousMethodHandler,所以查看其invoke方法。
SynchronousMethodHandler#invoke
RequestTemplate:构建 Request 模版类。
Options:存放连接、超时时间等配置类。
Retryer:失败重试策略类。
@Override
public Object invoke(Object[] argv) throws Throwable {
// 组装请求
RequestTemplate template = buildTemplateFromArgs.create(argv);
// 从参数中过滤options
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;
}
}
}
组装请求:
@Override
public RequestTemplate create(Object[] argv) {
// 构建请求模板
RequestTemplate mutable = RequestTemplate.from(metadata.template());
// 设置feign目标
mutable.feignTarget(target);
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
mutable.target(String.valueOf(argv[urlIndex]));
}
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
RequestTemplate template = resolve(argv, mutable, varBuilder);
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
Object value = argv[metadata.queryMapIndex()];
Map<String, Object> queryMap = toQueryMap(value);
template = addQueryMapQueryParameters(queryMap, template);
}
if (metadata.headerMapIndex() != null) {
template =
addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
}
return template;
}
De/Encode配置:
调用Encoder.encode:
protected RequestTemplate resolve(Object[] argv,
RequestTemplate mutable,
Map<String, Object> variables) {
Map<String, Object> formVariables = new LinkedHashMap<String, Object>();
for (Entry<String, Object> entry : variables.entrySet()) {
if (metadata.formParams().contains(entry.getKey())) {
formVariables.put(entry.getKey(), entry.getValue());
}
}
try {
// 对请求和请求体进行编码
encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, mutable);
} catch (EncodeException e) {
throw e;
} catch (RuntimeException e) {
throw new EncodeException(e.getMessage(), e);
}
return super.resolve(argv, mutable, variables);
}
}
调用Decoder.decode逻辑包含在executeAndDecode里
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null)
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
了解Feign能给我们带来什么:
feign的configeration到底有什么用
-->可以指定特定的coder和encoder以及contract,logger,完成客制化配置
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// @formatter:off
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);
applyBuildCustomizers(context, builder);
return builder;
}
对于同一个微服务,在多个类中定义多个Configeration到底是哪一个生效了?
SpringCloud会为每一个@FeignClient的服务形成一个独特的子容器,子容器的Configeration彼此独立,从而对于两个@FeignClient的类的Configeration如果不同,则生效的也不同。

public <T> T getInstance(String name, Class<T> type) {
//根据name获取context实例
AnnotationConfigApplicationContext context = getContext(name);
//根据type类型从子容器获取Bean实例
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
Feign对于指定的configeration的encoder和decoder在什么时候调用,怎么调用以及序列化
-->详情请看De/Encoder配置
对于不指定feign的confideraition,会发生什么,默认配置是什么?
-->在 FeignClientsConfigeration中,该类会被标记为@Configeration,并且在容器中缺少Encoder和Decoder的时候,会注入该bean,那么在向容器内找对应配置的时候,如果没找到,就会向父容器中查找,向子FeignClient中注入。
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider) {
return springEncoder(formWriterProvider, encoderProperties);
}
反序列化出现问题怎么排查?

如果Http调用与Feign调用返回的结果不一致,比如某些参数被传递,应该不为null却为null,则基本可以确定是反序列化有问题,反序列化有问题主要集中在所选的Deocder的版本和历史原因,可以按照Decoder版本+遇到的问题进行百度。以下列举几个遇到的问题:
@JsonProperty问题
确定该调用的Feign服务的返回值是否带有@JsonProperty字段,如果是,则确定Decoder是否是Jackson,如果不是,则更换Jackson作为反序列化的Decoder。
代码:
@PostConstruct
public void init() {
Jackson2ObjectMapperBuilder objectMapperBuilder = (new Jackson2ObjectMapperBuilder()).serializationInclusion(Include.NON_NULL);
this.converters = new HttpMessageConverters(new HttpMessageConverter[]{new MappingJackson2HttpMessageConverter(objectMapperBuilder.build())});
this.httpMessageConverters = () -> {
return this.converters;
};
this.decoder = new ResponseEntityDecoder(new SpringDecoder(this.httpMessageConverters));
}
科学记数法问题
如果不该出现科学记数法的地方的数据变成了科学记数法,则检查本地的Decoder是否是Gson,Gson在处理数据的时候会自动转换成Double处理,如果数据足够大,则会变成科学记数法。这个时候可以切换Jackson作为Decoder,看看能否解决问题。
https://blog.csdn.net/u010648159/article/details/83002837
案例:
在调用
XXXClientGateway发生的科学记数法问题
@PostMapping("getLifeLine")
public String getVipLevelByStudentNumber(@RequestBody BaseVO userId) {
PlatformUserAttributeRequest request = new PlatformUserAttributeRequest();
String giao = userId.getLabel();
request.setUserIdList(Collections.singletonList(giao));
BaseResponse<Map<String, PlatformUserAttributeValueListDTO>> userRes =
openUserAttributeClientGateway.userDetailBatch(request);
return userRes.getData().toString();
}
此时GateWay 的配置
@FeignClient(
value = "xxx-SERVICE",
contextId = "openUserAttributeClient",
configuration = {JacksonFeignClientConfiguration.class},
fallbackFactory = OpenUserAttributeClientFallbackFactory.class
)
传入userId,正常结果为:结果为Long,正常
dataMap={xxxAppLastActiveDateTime=1721291583656, xxxeDateTime=1721292587656, xxxAppLastActiveDateTime=1721291584656, xxxxLastActiveDateTime=1721291587656})}"
去掉configeration,返回的结果为,结果为科学记数法,一眼顶针
dataMap={xxxAppLastActiveDateTime=1.721291583656E12, xxxeDateTime=1.721292587656E12, xxxAppLastActiveDateTime=1.721291584656E12, xxxLastActiveDateTime=1.721291587656E12})}"
按图索骥,查找本地的默认的Deocder是什么:
@Configuration
public class FeignConfig {
@Bean
public Decoder decoder() {
return new FeignGsonDecoder(Json.GSON);
}
}
直接百度:Gson Long变科学记数法问题
问题 https://www.jianshu.com/p/482bf0fa4d42
原因 https://blog.csdn.net/qq_40813329/article/details/125385081
最后选择使用SpringCloud给定的Decoder,解决问题。
启示:
- 在调用别的模块的Feign接口的时候,可以要求对方提供相应的API包的解码器和编码器作为configeration。
- 在对外提供Feign的时候,尽量指定对应的配置类,否则两个服务之间都是黑盒,在开发的时候可能会发生意想不到的错误,造成时间浪费。







浙公网安备 33010602011771号