Feign源码解析

在之前简单写了一个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

o
这里也是一样,会去获取我们的配置类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发起请求,得到响应

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值