Feign 原理解析
基本原理
现在已经了解了 Ribbon 的负载均衡原理,我们可以来猜想下,Feign 的原理,仅仅通过一个注解 @FeignClient + 一个接口,就可以服务之间的调用。
- 通过 @FeignClient 在注解中的name,确定服务名,然后 RibbonClient 使用服务名去获取负载均衡器 loadBalancer,再通过负载均衡算法获取到 ip:port, 然后构建的请求为 http://nacos-component-provider/test/{id},当 id = 1时,最后通过参数封装等,请求为http://nacos-component-provider/test/1,然后进行服务名替换,http://ip:port/test/1。这个就是最终的请求,也是 @FeignClient 注解的作用。
- 那么就需要看下接口的作用了,定义了一个接口,肯定是不能调用接口的方法的,需要有实现类,这时候就就会通过代理,反射生成一个代理类,最后执行最终生成的请求。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
@AliasFor("name")
String value() default "";
}
@FeignClient(value = "nacos-component-provider")
public interface ProviderClient {
@GetMapping("test/{id}")
String test(@PathVariable("id") Integer id);
}
Feign 初始化
要想使用 Feign,就首先需要加上 @EnableFeignClients 注解开启 Feign功能,然后创建接口和调用方法,接着加上注解 @FeignClient 就可以进行使用了。
- 首先 @EnableFeignClients 注解是怎么开启 Feign 功能的,可以看到注解上 @Import FeignClientsRegistrar (feign client 注册类), 将扫描后的接口注册。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
}
-
进入 FeignClientsRegistrar,查看 registerBeanDefinitions ,这里是使用了 @EnableFeignClients,那么这里是注册的核心逻辑 -
将 @FeignClient 注解的接口解析接口和注解的配置信息,包装为 一个 FeignClientFactoryBean 工厂 bean ,注册进 spring 的 beanDefinationMap中。 -
看到这里发现只是简单的将 beanDefinition 信息注册进 beanMap,但是还没有真正实例化,那么到底是在哪里实例化的呢?由于这个 bean 是 FeignClientFactoryBean,那么肯定是这个 bean 工厂进行创建的,进入这个类。发现一个重载的 getObject 方法,很明显这个是创建 bean 的方法,肯定是有人依赖 FeignClientBean 也就是我们这里的 ProviderClient,当被自动注入时,就会去获取getBean(ProviderClient.class) 方法,接着就会调用 bean 工厂去创建 bean。下图就是 IOC 的实际运行情况。 -
接着就会调用 FeignClientFactoryBean#getObject() 中创建 bean ,进入 getObject(),然后调用了 getTarget(),从 spring 容器中获取了 FeignContext 对象,可以看一下 FeignContext 什么时候注入的,发现在 FeignAutoConfiguration#feignContext() 就进行注入了,也就是 springboot 加载时,就实例化好了。FeignContext 官方注释:给每一个 client 都具有设置一个 springApplicationContext的属性,方便每个 client 获取需要的 bean。简单来说就是每个client 都可以具备 springApplicationContext 的功能。 -
使用 FeignContext 构造 Feign.Builder构造器,看名字可以知道就是生成 feiclient 的构造器,将需要的参数进行封装,此时的 ProviderClient 接口的调用url的前缀为:http://nacos-component-provider -
调用 loadBalance() 方法,构造了 HystrixTargeter 对象,然后调用 HystrixTargeter#target 方法,发现这里调用了 Feign#target, 这里就是通过反射将之前的 Feign.Builder 创建出来代理对象,但是还有 ProviderClient 中的方法的 springmvc 的 @RequestMapping 等注解没有解析。 -
执行 build()方法,主要是构建配置hystrix集成所需的组件 ReflectiveFeign 对象,然后执行构建代理 bean 的核心方法newInstance(target)。
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
-
首先通过反射获取 ProviderClient 类中的所有方法,构建为 ProviderClient#test() 为key,value为对应的 methodHandler 形式的 map,其中 methodHandler 具有一个负载均衡客户端 LoadBalancerFeignClient ,用于后面使用 LoadBalancerFeignClient 调用方法,这里是为了后面获取 method上注解的属性,例如 url,httpMethod 等属性准备。 -
使用 jdk动态代理,创建完成代理 bean,其中 InvocationHandler 就是最终的调用方,它包括了 ProviderClient 的方法处理器等信息。到这里就是给 ProviderClient 接口构建了个代理对象,然后使用代理对象进行服务的调用。为什么用 jdk动态代理呢?很简单呀,因为这是一个接口,jdk 生成代理对象则必须要实现一个接口。 -
当调用 ProviderClient#test 方法时,观察注入的 ProviderClient,就是我们上一步生成好的代理对象,然后调用 ReflectiveFeign#invoke() 实现对方法的调用。 -
根据调用的方法,使用 dispatch 调用对应的 methodHandler,dispatch 就是之前构造代理对象中的方法映射 map,然后调用 SynchronousMethodHandler#invoke() 方法,根据方法上的注解信息 value=“test/{id}”、参数id=1,创建 RequestTemplate 对象,其中 URI=test/1。 -
使用 LoadBalancerFeignClient执行 request 请求,此时请求已经是一个完整的请求,调用LoadBalancerFeignClient#execute 执行请求的替换和请求调用。 -
通过服务名构建一个 FeignLoadBalancer(其中就是一个ZoneAwareLoadBalancer)动态获取 nacos 上服务名对应的注册的所有服务,然后再进行服务的选取,例如轮询、随机等负载均衡策略,默认是轮询可用的服务列表。 -
将获取到的服务 server 和旧的uri,进行组装,获取一个最终请求的uri,然后进行请求调用。使用默认的 Client#execute 执行 http 请求调用,然后获取结果,将返回结果进行处理。这就是 feignClient 的调用流程。
|