Feign源码深度刨析-(1)初探:@EnableFeignClients

本文详细剖析了Feign的核心组件,包括Encoder、Decoder、Logger和Contract,以及它们在微服务调用中的作用。通过@EnableFeignClients注解,Feign能够扫描并动态代理@FeignClient注解的接口,实现HTTP请求的自动化。FeignClientsRegistrar类在Spring容器中注册组件,为后续的接口调用和负载均衡做好准备。下一部分将深入探讨registerFeignClients()方法,揭示更多关于Feign如何构建和执行请求的细节。

“不积跬步,无以至千里。”

之前我们讲了ribbon的核心源码,其实在实际生产环境中,国内用ribbon+restTemplate去做微服务调用的甚少,因为会导致我们每次去调用人家一个接口,都要单独写一些代码,非常不简洁,所以我们这一个专题开始,研究一下feign的底层核心源码。

feign这个东西呢,底层也是依赖了ribbon做负载均衡的,所以是整合了ribbon的,feign的具体用法这里就不赘述了,相信来看这篇博文的,都是熟练使用的,技术,用,是很简单的,随便百度一下,csdn其他的文章,一搜一大堆,这个我就不写了,只讲核心的东西,源码。

feign里面,有一些核心的组件,需要先了解一下

  • EncoderDecoder,顾名思义,feign的编码器和解码器,调用接口的时候,如果传递的参数是对象,feign会通过Encoder组件进行编码,把对象进行json序列化,转成一个json串,默认使用ResponseEntityDecoder;另一头,你收到了一个json以后,再通过Decoder进行解码,转换成一个本地的对象,默认使用SpringEncoder

  • Logger,日志组件,feign是负责接口调用,发送http请求的,所以feign是可以打印这个接口调用请求的日志的,默认使用Slf4jLogger

  • Contract,你可以理解成spring mvc的注解解释器,使用feign的时候,方法的形参通常会使用类似@PathVariable、@RequestMapping等spring web mvc的注解,feign使用这个组件来解释注解,默认使用SpringMvcContract

  • FeignClient,feign核心入口,里面包含了一系列的组件,比如Encoder、Decoder、Logger、Contract等,与ribbon整合时使用LoadBalancerFeignClient

  • Feign.Builder,FeignClient的一个实例构造器,使用构造器模式来构造一个FeignClient,与hystrix整合时使用HystrixFeign.Builder

开始源码刨析之前,要明白一件事情,feign的入口在哪里?

其实我们想一想,无非就两个地方

  1. 我们在Application启动类上面打的@EnableFeignClient注解
  2. 我们在自定义的feignClient上面打的@FeignClient注解

就先从@EnableFeignClient入手吧

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    ......
}

两个点需要注意

  1. @Import(FeignClientsRegistrar.class),导入了一个FeignClientsRegistrar的组件
  2. Scans for interfaces that declare they are feign clients(via {@link FeignClient @FeignClient}),这个注解的注释提到“扫描那些标注了@FeignClient的feign客户端接口”

根据这些线索,我们基本判断,feign这里,有一个核心的机制,来扫描所有打上了@FeignClient注解的接口,然后推测一下,会对这个接口实现一个动态代理,并解析接口方法上标注的spring mvc注解,最终生成一个http请求,使用底层的通信组件整合ribbon,选择一个server,发送请求,接受响应并返回… …

当然,上面的内容只是个人的猜想,都源码的一些经验之谈,毕竟我们只是声明了一个feignclient的接口,就直接在controller中使用@Autowired注入使用了,并没有去手动注入此类型的Bean,猜想,不是瞎想,一定是有凭有据

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
		ResourceLoaderAware, EnvironmentAware {
            
            
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                                            BeanDefinitionRegistry registry) {
            registerDefaultConfiguration(metadata, registry);
            registerFeignClients(metadata, registry);
        }
}

FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,并实现了registerBeanDefinitions()方法!!!

了解spring的都知道,这是往spring容器中导入组件的一种方式

registerDefaultConfiguration(metadata, registry);

注册默认的配置,这个应该是解析一些上面提到的默认组件到spring容器里面去

registerFeignClients(metadata, registry);

注册FeignClients!!!这个极有可能就是扫描各个包下面的@FeignClient注解,然后生成@FeignClient的动态代理,注册这些@FeignClient到spring容器中,这样controller中才能自动注入Bean

先来看看第一个方法 registerDefaultConfiguration

private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    Map<String, Object> defaultAttrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

    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"));
    }
}

首先去解析EnableFeignClients,拿到注解的属性,然后判断一下

defaultAttrs != null && defaultAttrs.containsKey(“defaultConfiguration”)

这个表达式肯定是返回true的,因为即使我们没有配置属性,还是有默认值的

name = “default.” + metadata.getClassName();

拿到一个name,就是启动类的全限定类名,然后前面拼接一个“default”,类似

default.com.wc.resttemplate.Springboot01Application 这样的一个字符串

defaultAttrs.get(“defaultConfiguration”) 这个东西其实是空的,因为我们没有配置这个属性

然后调用了一个registerClientConfiguration(...)方法,把上面的参数传递了进去

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());
}

这里面其实没什么核心的东西,搞了一个BeanDefinitionBuilder,把name(“default+启动类全限定类名”)和configuration(空的)传进去作为一个bean的构造参数,可能接下来要用 BeanDefinitionBuilder 构造某个bean,构造这个bean的时候,需要往构造函数中传入两个入参,就是我们上面解析出来的 name 和 defaultConfiguration

这个 name + “.” + FeignClientSpecification.class.getSimpleName() ,

就是 default.com.wc.resttemplate.Springboot01Application.FeignClientSpecification 的一串东东

然后使用 registry.registerBeanDefinition(…) 把上面一串东东作为bean name注册到容器中

ok,看完了第一个方法 registerDefaultConfiguration,好像也没搞出来什么名堂,无非就是注册了一个奇怪的Bean,别的也没啥了,其实这个方法比较简单,不是太核心

下一讲来看第二个方法

registerFeignClients(metadata, registry);

注册feign clients,这里面会干什么事情,拭目以待!

### Feign 调用中 POST 请求执行失败的排查与解决 在微服务架构下,Feign 客户端常用于服务间的通信。当出现 `feign.RetryableException: Read timed out executing POST` 异常时,表明客户端在尝试调用远程服务并等待响应的过程中发生了超时。 #### 1. 配置 Feign 客户端超时时间 Feign 默认的连接和读取超时时间较短,在网络状况不佳或服务响应较慢的情况下容易触发超时异常。可以通过配置文件调整 Feign 的连接和读取超时时间,以提高容错能力: ```yaml feign: client: config: default: connect-timeout: 20000 read-timeout: 20000 ``` 此配置将默认的连接和读取超时时间延长至 20 秒,有助于避免因短暂延迟导致的请求失败[^2]。 #### 2. 检查线程池配置 如果服务本身运行正常但仍然频繁出现连接重置(Connection reset)等异常,则可能是由于线程池配置不当引起的。例如,核心线程数过少可能导致并发请求处理不及时,从而引发超时或连接中断。建议根据实际负载情况合理设置线程池参数: ```java @Bean public Executor feignExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(30); executor.setMaxPoolSize(60); executor.setQueueCapacity(500); executor.setThreadNamePrefix("feign-pool-"); executor.initialize(); return executor; } ``` 通过增加核心线程数和最大线程数,可以提升并发处理能力,减少因资源不足导致的请求失败[^3]。 #### 3. 确认服务地址与网络可达性 若 Feign 客户端使用服务名进行调用,并且服务注册中心(如 Eureka、Nacos)未能正确解服务实例地址,则可能造成请求失败。此时可尝试直接通过 IP 地址指定服务端点: ```java @FeignClient(value = "leak", url = "http://192.168.104.71:8888") public interface LeakFeignServiceFeign { // 接口定义 } ``` 该方式绕过了服务发现机制,适用于测试环境或单节点部署场景,有助于排除服务发现组件带来的不确定性因素[^4]。 #### 4. 日志分与链路追踪 对于生产环境中偶发的 `feign.RetryableException`,应结合日志系统(如 ELK)和服务链路追踪工具(如 SkyWalking、Zipkin)进行深入分,定位是网络层问题、服务端性能瓶颈还是客户端配置不当所致。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值