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的时候,尽量指定对应的配置类,否则两个服务之间都是黑盒,在开发的时候可能会发生意想不到的错误,造成时间浪费。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!