在之前简单写了一个Fegin模拟远程调用的文章,所以对Feign的底层原理产生了兴趣,在工作之余看了一下Feign的相关源码,所以整理了这篇文章,记录一下这些天的学习心得。
在看Feign源码时,我们最先从Feign的注解出发,@EnableFeignClients是我们熟悉的开启Feign的注解。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
@Import中导入了FeignClientsRegistrar类,该类实现了ImportBeanDefinitionRegistrar接口,该接口的registerBeanDefinitions方法中,在初始化时会调用该接口通过BeanDefinitionRegistry完成BeanDefinition的注册。
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
this.registerDefaultConfiguration(metadata, registry);
this.registerFeignClients(metadata, registry);
}
这里传入的metadata传入的是我们fegin的配置类,这个AnnotationMetadata是Spring框架中的一个接口,用于表示类或方法上的注解元数据。

为了方便理解,配置类我也放在这里

第一个方法 : registerDefaultConfiguration
这里我们会拿到FeginClientConfig上EnableClients的所有注解属性,从中获取defalultConfiguration属性,并注册到BeanDefinitionMap中

这里我并没有设置配置类,Feign将会注册默认的配置类FeignClientsConfiguration。这个默认配置类提供了一些默认的配置,包括对Hystrix的支持、对Ribbon的支持以及一些默认的Feign客户端配置。这些默认配置可以满足大多数情况下的需求。
第二个方法:registerFeignClients

这里也是一样,会去获取我们的配置类FeignClientConfig的@EnableFeginClient的属性,首先会有拿取client属性,如果clients中有我们定义的FeginClient,我们会将其添加到我们的candidateComponents(BeanDeifition集合中),如果没有,则会创建一个扫描器,去扫描我们basePackage指定的包路径下添加了@FeginClient注解的类,并添加到我们的BeanDefinition中,
这里我的client属性是有值的,所以可以看到走的是第一种方式。
所以我们可以知道,我们去声明一个FeginClient是有两种方式的,一种通过client属性去指定,另外一种是通过包扫描的方式,feign默认是先采用第一种

接下来遍历我们的BeanDefinition集合,首先去判断这个FeignClient是不是接口类,不是则抛出异常,然后去获取我们FeignClient的configuration属性,并通过下面的registerClientConfiguration将该属性的配置类注册到我们的BeanDefinition中。
这里的registerFeignClient方法就是将我们添加了@FeginClient注解的组件添加到我们的Beandefinition中,下面来看看他实际做了什么。


这里的BeanDefinitionregistry会被转换成我们的ConfigurableBeanFactory类,然后获取contextId和name,type和isEnable,BeanDefinitionBuilder.genericBeanDefinition会去注册我们的BeanDenition,在这里会获取我们的url属性和path属性,这个就是我们实际调用的http地址了。同时去设置我们的fallback和fallbackFactory属性。
fallback:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
fallbackFactory:工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
完成属性填充后,通过Spring提供的registerBeanDefinition方法向BeanDefinitionRegistry注册了刚实例化的这个BeanDefinitionHolder。这里完成的是将FeignClient注解的类的信息交给工厂bean代理类,并将代理类的定义注册到Spring的容器中。
至此,已经把要创建的接口代理对象的信息放入registry里面,之后spring在启动调用refresh方法的时候会负责bean的实例化。在实例化过程中,调用FeignClientFactoryBean的getObject方法
factory.getBean方法

组装我们的http请求地址,调用loadBlance方法


继续往下执行,调用Feign.target方法,并且去调用newInstance方法
!(https://img-blog.csdnimg.cn/direct/2c8af9e3f84147488f3c16ea5bbd64aa.png)
最终调用了ReflectiveFeign类中的newInstance方法。其中名为nameToHandler的Map中存储了FeignClient接口中定义的方法。
看到下面的InvocationHandler和Proxy就很清楚了,这里是使用JDK动态代理的方式创建代理对象。创建InvocationHandler及代理对象过程。


这里的factory是InvocationHandlerFactory的对象,看一下它的create方法,用于创建FeignInvocationHandler实例来对方法进行拦截。在构造方法中传入了代理类的接口,以及需要代理的方法
通过JDK动态代理我们知道,在InvocationHandler中,invoke方法对进行方法拦截和逻辑增强。看一下关键的invoke方法是如何工作的:

调用SynchronousMethodHandler类的invoke方法

看下这个executeAndDecode方法,这里把请求交给了之前创建的LoadBalancerFeignClient,执行了它的execute方法。和开头说的一样,和Ribbon类似的调用流程。只不过需要区别一下的是,Ribbon是使用拦截器拦截请求,而Feign是使用动态代理的invoke方法对方法进行拦截并转发。

进入LoadBalancerFeignClient的execute方法,在其中构建了一个RibbonRequest的请求:

在这里我们首先会通过serviceId去获取实例,如果获取到的实例不为空,我们会通过实例调用的方式去请求接口,否则的话通过生成http连接去请求。
最后,对Feign的实现流程进行一下总结:
1、使用JDK动态代理为接口创建代理对象
2、执行接口的方法时,调用代理对象的invoker方法
3、读取FeignClient的注解得到要调用的远程服务的接口
4、通过Ribbon负载均衡得到一个要调用的服务提供者
5、使用HttpURLConnection发起请求,得到响应

1188

被折叠的 条评论
为什么被折叠?



