feign的bean创建过程-底层请求过程-源码走读
feign的bean创建过程,请求过程-源码走读
目标
- 了解feign的bean的创建过程
- 了解feign发送请求时的过程
前置知识:了解spring的bean创建过程
feign的版本是spring-cloud-openfeign-core:4.1.2
采用的nacos做注册中心
项目需要配置 spring.cloud.loadbalancer.nacos.enabled=true
bean的创建过程
一般我们会通过注解
@EnableFeignClients(basePackages = {“org.example.**.feign”})
指定扫描的bean
启动项目之后,就会在对应目录下面扫描出来,feign是采用factoryBean的形式创建bean的,所以断点直接打到FeignClientFactoryBean.getObject
1 |
|
loadBalance 里面实际创建是最后一步调用target函数,一步一步点进去之后,会发现是采用jdk代理创建的
1 | protected <T> T loadBalance(Feign.Builder builder, FeignClientFactory context, HardCodedTarget<T> target) { |
InvocationHandler 是一个内部类实现的 FeignInvocationHandler
1 | static class FeignInvocationHandler implements InvocationHandler { |
feign的调用过程
直接从方法入口进入
1 | (value = "database", contextId = "db") |
第一节提到,代理是jdk动态代理,所以预期应该是invocationHandler里面执行,直接把断点打到FeignInvocationHandler的invoke上面
1 | static class FeignInvocationHandler implements InvocationHandler { |
dispatch 是一个map,里面存储的是方法对应的SynchronousMethodHandler,都是和http请求相关的属性
1 | final class SynchronousMethodHandler implements MethodHandler { |
1中 options可以这么实现
1 | (value = Constant.FEIGN_URL_PREFIX + "/dbInfo/{id}") |
2 中的重试规则,默认不重试
1 | Retryer NEVER_RETRY = new Retryer() { |
抛出 RetryableException 有两个位置 ioExecption 和 返回的resp header是否有 Retry-After 字段
1 | private CompletableFuture<Object> executeAndDecode(RequestTemplate template, Options options) { |
这是ErrorDecoder的代码
1 | public Exception decode(String methodKey, Response response) { |
来看3中executeAndDecode请求是如何执行的
executeAndDecode 执行请求
1 | Object executeAndDecode(RequestTemplate template, Options options) throws Throwable { |
execute的过程
1 | public Response execute(Request request, Request.Options options) throws IOException { |
loadBalancer.choose的调用
1 | public <T> ServiceInstance choose(String serviceId, Request<T> request) { |
getInstanceResponse会从多个节点里面选一个出来
1 | private Response<ServiceInstance> getInstanceResponse( |
然后我们退回发送请求的地方
1 | public Response execute(Request request, Request.Options options) throws IOException { |
最后就是发送请求了
1 | static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options, |
最后的convertAndSend就不仔细看了,目前代码可知,使用的是java.net的发送方式。如果用httpclient,feign会自动装配,只需要引入依赖即可。
总结and面试
首先是feign要初始化bean,EnableFeignClients要写上feign在哪些包里面,扫描出来。
feign的bean是用FeignClientFactoryBean实现的,听名字就知道这实现了FactoryBean接口,里面getObject实现的很复杂。里面需要设置请求url,设置负载均衡的bean等信息。然后会用jdk代理的方式,把这个feign的bean执行代理。实现一个FeignInvocationHandler,重写invoke方法,把远程调用这个过程通过代理实现。
然后在请求的时候,因为要走代理,代理里面实现了如下几个内容。一个是参数信息,比如请求超时时间,这个可以通过函数参数传进来,然后这些参数会封装到请求里面。代理还帮忙做了一些重试,默认是不重试的。
发送请求的位置在executeAndDecode函数。我们可以实现 RequestInterceptor 接口,发送请求的时候,会执行这个interceptor,对header做一些处理。然后因为一般微服务都是不直接写ip的,要向配置中心获取实例地址。假设配置中心是nacos,向nacos请求获得所有的实例,然后nacos会有算法,从健康的实例里面选出一个实例,这样就有ip端口了,就可以得到完整的url。然后在编码一下请求体,就可以发送请求了。