• 从服务间的一次调用分析整个springcloud的调用过程(二)


    先看示例代码

    @RestController
    @RequestMapping("/students")
    public class StudentController {
    
        @Autowired
        private TeacherFeignClient teacherFeignClient;
    
        @GetMapping("/{id}")
        public ResponseEntity<Teacher> getTeacher(@PathVariable("id") int id) {
            return ResponseEntity.ok(teacherFeignClient.findTeacher(5));
        }
    }
    @FeignClient(name = "teacher-service",path = "/teachers")
    public interface TeacherFeignClient {
    
        @GetMapping("/{id}")
        Teacher findTeacher(@PathVariable("id") int id);
    
    }

      1.  首先我们肯定知道spring会基于TeacherFeignClient生成代理类Proxy,代理类的代码如下

        public final Teacher findTeacher(int var1) throws  {
            try {
                return (Teacher)super.h.invoke(this, m3, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }

          代理类中的invoke方法指向的是feign.ReflectiveFeign.FeignInvocationHandler.invoke方法,之后会走向调用SynchronousMethodHandler invoke方法,其中SynchronousMethodHandler中的target属性就是包装了feignclient的相关属性,比如service,url等; 在这个类里面还有一些很重要的属性,比如List<RequestInterceptor> requestInterceptors,这个就是spring调用的拦截器,比如我们可以自定义拦截器,添加一些请求头;还有比如errorDecoder,自定义对feign请求异常结果的封装

    然后会调用executeAndDecode方法,该方法第一步就会根据target构造Request对象,这个方法最终会调用Cilent接口的实现类LoadBalancerFeignClient。

            

    @Override
        public Response execute(Request request, Request.Options options) throws IOException {
            try {
                URI asUri = URI.create(request.url());
                String clientName = asUri.getHost();
                URI uriWithoutHost = cleanUrl(request.url(), clientName);
                FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                        this.delegate, request, uriWithoutHost);
    
                IClientConfig requestConfig = getClientConfig(options, clientName);
                return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
                        requestConfig).toResponse();
            }
            catch (ClientException e) {
                IOException io = findIOException(e);
                if (io != null) {
                    throw io;
                }
                throw new RuntimeException(e);
            }
        }
    View Code

      2.  LoadBalancerFeignClient execute方法先构造RibbonRequest,然后会去获取IClientConfig,再去调用SpringClientFactory的getInstance方法,走到NamedContextFactory getInstance方法。

        @Override
        public <C> C getInstance(String name, Class<C> type) {
            C instance = super.getInstance(name, type);
            if (instance != null) {
                return instance;
            }
            IClientConfig config = getInstance(name, IClientConfig.class);
            return instantiateWithConfig(getContext(name), type, config);
        }
    View Code

      3. 最终会通过AnnotationConfigApplicationContext getBean方法经过一些列流程走到 RibbonClientConfiguration 装载IClientConfig bean方法,最后就获得了当前service ribbon的相关配置

        @Bean
        @ConditionalOnMissingBean
        public IClientConfig ribbonClientConfig() {
            DefaultClientConfigImpl config = new DefaultClientConfigImpl();
            config.loadProperties(this.name);
            config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
            config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
            config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
            return config;
        }
    View Code

      4. 回到LoadBalancerFeignClient execute方法64行获取到IClientConfig后,此时有个核心步骤就是根据当前clientName获取到一个FeignLoadBalancer的实现,可以看到其中有一个cache属性,如果cache有的话就从cache返回,这也就feign第一次调用会慢的原因之一,因为首次需要去加载这个FeignLoadBalancer;首次加载的时候因为这里我们没有配置retryFactory,所以会返回一个 FeignLoadBalancer

        public FeignLoadBalancer create(String clientName) {
            FeignLoadBalancer client = this.cache.get(clientName);
            if(client != null) {
                return client;
            }
            IClientConfig config = this.factory.getClientConfig(clientName);
            ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
            ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
            client = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
                loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
            this.cache.put(clientName, client);
            return client;
        }
    View Code

     5. 获取到FeignLoadBalancer会执行它父类AbstractLoadBalancerAwareClient中的executeWithLoadBalancer(该方法中牵扯到负载均衡的逻辑,会在下一篇中说到),该方法最终还是会执行FeignLoadBalancer的execute方法,方法中会获取request的client,该client默认是feign.Client.Default,实现就是通过构造HttpURLConnection发起http请求

        @Override
        public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
                throws IOException {
            Request.Options options;
            if (configOverride != null) {
                RibbonProperties override = RibbonProperties.from(configOverride);
                options = new Request.Options(
                        override.connectTimeout(this.connectTimeout),
                        override.readTimeout(this.readTimeout));
            }
            else {
                options = new Request.Options(this.connectTimeout, this.readTimeout);
            }
            Response response = request.client().execute(request.toRequest(), options);
            return new RibbonResponse(request.getUri(), response);
        }
    View Code
  • 相关阅读:
    格式化Format使用
    ASP.NET几种清除页面缓存的方法
    repeater中分页aspnetpager是遇到的问题
    利用正则表达式去掉html代码
    ASP.NET母版页中调用内容页的方法和web用户控件调用.aspx页面里的方法
    PHP常用代码大全
    用 JA Transmenu 模块做多级弹出菜单
    简单的 "改变" joomla 后台administrator目录
    数据库设计中的14个技巧
    Joomla模板制作教程(转)
  • 原文地址:https://www.cnblogs.com/minjay/p/15897845.html
Copyright © 2020-2023  润新知