美文网首页
Feign源码学习

Feign源码学习

作者: RealityVibe | 来源:发表于2021-03-27 16:07 被阅读0次

    Feign在Spring Cloud中主要用于封装Http请求细节,让微服务之间的调用同服务内调用一样便捷。

    Open Feign的源码实现过程主要可以概括为以下几点

    1. 通过@EnableFeignClients引入FeignClientsRegistrar。
    2. FeignClientsRegistrar实现了ImportBeanDefinition接口,扫描对应路径下被@EnableFeign注解修饰的接口(必须是接口),生成对应的FeignClientSpecifition。
    3. FeignAutoConfiguration注入所有的FeignClientSpecification的实例,注入到FeignContext中。
    4. 当接口调用时,通过CGLIB动态代理的形式,与已有的RestTemplate整合请求参数,生成最后的RestTemplate。
    5. RestTemplate被转换为Request,使用了Ribbon还会被转换为RibbonRequest
    6. 通过Ribbon进行负载均衡,将url中的应用名转化为ip。
    7. 调用Request内的Client执行真正的Http请求,client可以是ApacheHttpClient等实例。
    8. 返回结果response,使用了Ribbon还会转化为RibbonResponse。
    9. 根据返回结果的状态码使用Decoder或者ErrorDecoder进行解析。Decoder等组件都维护在FeignContext上下文中。

    由此得到Feign的启用和请求调用的大体步骤,这些步骤又可以分为三个阶段:BeanDefinition的注册实例的初始化函数的调用和网络请求三个部分,分别以FeignClientRegistrar、FeignClientFactoryBean、SynchronousMethodHandler为链路起点。

    1、BeanDefinition的注册

    1.1、@EnableFeignClients注解启用Feign

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(FeignClientsRegistrar.class) // 引入FeignClientsRegistrar,用来处理@FeignClient
    public @interface EnableFeignClients {   
       /**
        * 用来指定自动扫描的包
        */
       String[] value() default {};
       String[] basePackages() default {};
       Class<?>[] basePackageClasses() default {};
    
       /**
        * 自定义配置
        */
       Class<?>[] defaultConfiguration() default {};
    
       /**
        * 指定FeignClient注解修饰的类,如果不为空,将禁用FeignClient自动扫描
        */
       Class<?>[] clients() default {};
    }
    

    1.2、FeignClientsRegistrar注册

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
      // 处理@EnableFeignClients注解,从配置参数来构建Feign的自定义Configuration来注册
       registerDefaultConfiguration(metadata, registry);
      // 扫描@FeignClient注解修饰的类,注册BeanDefinition到BeanDefinitionRegistrar中
       registerFeignClients(metadata, registry);
    }
    

    1.2.1、处理@EnableFeignClients注解,注册配置

    @EnableFeignClients的自定义配置类是被@Configuration注解修饰的配置类,它会提供一系列组装FeignClient的各类组件实例。这些组件包括:Client、Targeter、Decoder、Encoder和Contract等。

    private void registerDefaultConfiguration(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
       Map<String, Object> defaultAttrs = metadata
             .getAnnotationAttributes(EnableFeignClients.class.getName(), true);
    
       if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
          String name;
          if (metadata.hasEnclosingClass()) {
             name = "default." + metadata.getEnclosingClassName();
          }
          else {
             name = "default." + metadata.getClassName();
          }
          // 注册BeanDefinition到Spring容器中
          registerClientConfiguration(registry, name,
                defaultAttrs.get("defaultConfiguration"));
       }
    }
    

    1.2.2、处理@FeignClient注解,注册FeignClient

    public void registerFeignClients(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
       ClassPathScanningCandidateComponentProvider scanner = getScanner();
       scanner.setResourceLoader(this.resourceLoader);
    
       Set<String> basePackages;
       Map<String, Object> attrs = metadata
             .getAnnotationAttributes(EnableFeignClients.class.getName());
       AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
             FeignClient.class);
       final Class<?>[] clients = attrs == null ? null
             : (Class<?>[]) attrs.get("clients");
         // 配置扫描器,加入过滤条件
       if (clients == null || clients.length == 0) {
          scanner.addIncludeFilter(annotationTypeFilter);
          basePackages = getBasePackages(metadata);
       }
       else {
          final Set<String> clientClasses = new HashSet<>();
          basePackages = new HashSet<>();
          for (Class<?> clazz : clients) {
             basePackages.add(ClassUtils.getPackageName(clazz));
             clientClasses.add(clazz.getCanonicalName());
          }
          AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
             @Override
             protected boolean match(ClassMetadata metadata) {
                String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                return clientClasses.contains(cleaned);
             }
          };
          scanner.addIncludeFilter(
                new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
       }
    
      // 通过scanner从扫描路径下扫描候选FeignClient
       for (String basePackage : basePackages) {
          Set<BeanDefinition> candidateComponents = scanner
                .findCandidateComponents(basePackage);
          for (BeanDefinition candidateComponent : candidateComponents) {
             if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(),
                      "@FeignClient can only be specified on an interface");
    
                Map<String, Object> attributes = annotationMetadata
                      .getAnnotationAttributes(
                            FeignClient.class.getCanonicalName());
    
                String name = getClientName(attributes);
                registerClientConfiguration(registry, name,
                      attributes.get("configuration"));
    
                // 注册BeanDefinition到Spring容器中
                registerFeignClient(registry, annotationMetadata, attributes);
             }
          }
       }
    }
    

    2.3、FeignAutoConfiguration,将配置的实例放到FeignContext上下文

    public class FeignAutoConfiguration {
    
      // 自动注入所有的FeignClientSpecification配置Bean
       @Autowired(required = false)
       private List<FeignClientSpecification> configurations = new ArrayList<>();
    
       @Bean
       public FeignContext feignContext() {
          FeignContext context = new FeignContext();
          // 把这些配置Bean放到上下文中,创建FeignContext到Spring容器
          context.setConfigurations(this.configurations);
          return context;
       }
      
     //... 
    }
    

    2、实例的初始化

    前一步有FeignClientRegistrar完成BeanDefinition的注册后,FeignClientFactoryBean接过使命,进行Bean的初始化。

    2.1、FeignClientFactoryBean

    FeignClientFactoryBean实现了FactoryBean接口,作为工厂类,Spring容器通过调用它的getObject方法来获取对应的Bean实例。通过判断@FeignClient注解是否有url配置,决定生成的是LoadBalancerFeignClient还是普通的FeignClient,因为前者更常见,这里着重介绍LoadBalancerFeignClient。LoadBalancerFeignClient的负载均衡由Ribbon支持,在这里不做详细介绍。

    @Override
    public Object getObject() throws Exception {
       return getTarget();
    }
    
    // 获取FeignClient实例
    <T> T getTarget() {
       FeignContext context = this.applicationContext.getBean(FeignContext.class);
        // 从上下文中获取Encode,Contract等各种组件放到Feign.Builder中
       Feign.Builder builder = feign(context);
    
        // 判断对应FeignClient是否有url
        // 有url会生成LoadBalancerFeignClient
        // 否则返回普通的FeignClient
       if (!StringUtils.hasText(this.url)) {
          if (!this.name.startsWith("http")) {
             this.url = "http://" + this.name;
          }
          else {
             this.url = this.name;
          }
          this.url += cleanPath();
            // 生成LoadBalancerFeignClient,带有负载均衡
          return (T) loadBalance(builder, context,
                new HardCodedTarget<>(this.type, this.name, this.url));
       }
      
       // 没有配置url,返回普通的FeignClient
      // ...
    }
    
    // 获取LoadBalancerFeignClient
    // 这里会从上下文中获取Client和Targeter,如果尚未初始化会触发他们的实例化
    // 在FeignClientAutoConfiguration中有Targeter的
    protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                HardCodedTarget<T> target) {
        // 从上下文中获取Client实例,默认为ApacheHttpClient
            Client client = getOptional(context, Client.class);
            if (client != null) {
          // 在Builder中放入Client
                builder.client(client);
          // 获取Client实例
                Targeter targeter = get(context, Targeter.class);
          // target()调用Builder.build()方法,然后调用newInstance方法生成Proxy
                return targeter.target(this, builder, context, target);
            }
    
            // ... 
        }
    

    2.2、Targeter

    Targeter是一个接口,它的target方法生成对应的实例对象。他有两个实现类,分别为DefaultTargeter和HystrixTargeter。OpenFeign使用HystrixTargeter这一层抽象来封装关于Hystrix的实现。

    DefaultTargeter

    DefaultTargeter只是调用了Feign.Builder的target方法。

    class DefaultTargeter implements Targeter {
       @Override
       public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
             FeignContext context, Target.HardCodedTarget<T> target) {
          return feign.target(target);
       }
    }
    

    Feign.Builder#target()

    public <T> T target(Target<T> target) {
      // newInstance()调用的是ReflectiveFeign的newInstance()
      return build().newInstance(target);
    }
    

    2.3、 重要的ReflectiveFeign

    2.3.1、 newInstance()的两件事

    newInstance()主要做了两件事

    1. 扫描了该FeignClient接口类中的函数信息,生成对应的MethodHandler。targetToHandlersByName.apply(target);
    2. 使用Proxy生成FeignClient的实例对象。
    • Contract

    在生成对应的MethodHandler过程中,BaseContract的parseAndValidateMetadata方法会依次解析接口类的注解,函数注解和函数的参数注解,将这些注解包含的信息封装到MethodMetadata对象中,将@RequestMapping的uri拼接到函数的uri中等操作。

    在MethodHandler中包含了SynchronizedMethodHandler,内含RestTemplate.Factory用来生成最后的请求RestTemplate。
    
    @Override
    public <T> T newInstance(Target<T> target) {
      // 这里生成所有方法MethodHandler
      // nameToHandler由Contract解析target得到
      // 在MethodHandler中存在对应方法的RestTemplate.Factory和SynchronizedMethodHandler
      Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
      Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
      List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    
      for (Method method : target.type().getMethods()) {
        if (method.getDeclaringClass() == Object.class) {
          continue;
        } else if (Util.isDefault(method)) {
          DefaultMethodHandler handler = new DefaultMethodHandler(method);
          defaultMethodHandlers.add(handler);
          methodToHandler.put(method, handler);
        } else {
          methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
      }
      // 这里把target和MethodHandler的映射关系
      // 创建出一个FeignInvocationHandler
      InvocationHandler handler = factory.create(target, methodToHandler);
      // 使用Proxy生成FeignClient的实例对象。
      T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
          new Class<?>[] {target.type()}, handler);
    
      // 把defaultMethodHandler绑定到proxy
      for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
        defaultMethodHandler.bindTo(proxy);
      }
      return proxy;
    }
    

    2.3.2、第一件事:扫描了该FeignClient接口类中的函数信息

    2.3.3、第二件事:使用Proxy生成FeignClient的实例对象

    3、函数的调用和网络请求

    OpenFeign网络请求流程图

    3.1、SynchronousMethodHandler#invoke

    调用RestTemplate.Factory的create方法生成RestTemplate方法。

    @Override
    public Object invoke(Object[] argv) throws Throwable {
      // 调用RestTemplate.Factory的create方法生成RestTemplate实例
      RequestTemplate template = buildTemplateFromArgs.create(argv);
      Retryer retryer = this.retryer.clone();
      while (true) {
        try {
          // Target将template转换为request实例
          // client调用request,返回response
          return executeAndDecode(template);
        } catch (RetryableException e) {
         // ...
        }
      }
    }
    

    3.2、RestTemplate.Factory#create

    根据实际的调用参数和之前的RestTemplate结合,生成RestTemplate实例。

    @Override
    public RequestTemplate create(Object[] argv) {
      RequestTemplate mutable = RequestTemplate.from(metadata.template());
      if (metadata.urlIndex() != null) {
        int urlIndex = metadata.urlIndex();
        checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
        mutable.target(String.valueOf(argv[urlIndex]));
      }
      Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
      // 遍历MethodMetadata中所有关于参数的索引及其对应名称的配置信息。
      for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
        int i = entry.getKey();
        Object value = argv[entry.getKey()];
        if (value != null) { // Null values are skipped.
          if (indexToExpander.containsKey(i)) {
            value = expandElements(indexToExpander.get(i), value);
          }
          for (String name : entry.getValue()) {
            varBuilder.put(name, value);
          }
        }
      }
        
      RequestTemplate template = resolve(argv, mutable, varBuilder);
      // 设置queryMap参数
      if (metadata.queryMapIndex() != null) {
        // add query map parameters after initial resolve so that they take
        // precedence over any predefined values
        Object value = argv[metadata.queryMapIndex()];
        // 调用Encoder进行入参解析
        Map<String, Object> queryMap = toQueryMap(value);
        template = addQueryMapQueryParameters(queryMap, template);
      }
    
      // 设置headerMap参数
      if (metadata.headerMapIndex() != null) {
        template =
            addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
      }
    
      return template;
    }
    

    3.3、SynchronousMethodHandler#executeAndDecode

    在这个流程里用到了Encoder、Decoder、ErrorDecoder组件;

    Object executeAndDecode(RequestTemplate template) throws Throwable {
      // 1、通过RequestInterceptor机制,
      // 通过拦截器为每个请求增加固定的header信息
      Request request = targetRequest(template);
      Response response;
      long start = System.nanoTime();
      try {
        // 2、调用client的execute方法进行http请求
        response = client.execute(request, options);
      } catch (IOException e) {
        throw errorExecuting(request, e);
      }
      boolean shouldClose = true;
      try {
        if (Response.class == metadata.returnType()) {
          // ... 省略进行一些body校验的代码
          // Ensure the response body is disconnected
          byte[] bodyData = Util.toByteArray(response.body().asInputStream());
          return response.toBuilder().body(bodyData).build();
        }
        // http请求状态码为在[200, 300)代表调用成功
        if (response.status() >= 200 && response.status() < 300) {
          if (void.class == metadata.returnType()) {
            return null;
          } else {
            Object result = decode(response);
            shouldClose = closeAfterDecode;
            return result;
          }
        } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
          // 调用异常,且返回类型为Void
          Object result = decode(response);
          shouldClose = closeAfterDecode;
          return result;
        } else {
          // 调用失败,errorDecoder组件解析返回信息
          throw errorDecoder.decode(metadata.configKey(), response);
        }
      } catch (IOException e) {
        throw errorReading(request, response, e);
      } finally {
        if (shouldClose) {
          ensureClosed(response.body());
        }
      }
    }
    

    RequestInterceptor

    OpenFeign也提供了RequestInterceptor机制,在由RestTemplate生成Request的过程中,会调用所有的RequestInterceptor对RestTemplate进行处理。

    Request targetRequest(RequestTemplate template) {
      // 使用请求拦截器为每个请求增加固定的header信息
      for (RequestInterceptor interceptor : requestInterceptors) {
        interceptor.apply(template);
      }
      return target.apply(template);
    }
    

    Client

    Client用来进行真正的Http请求,前文提到当@FeignClient中有url时,getTarget()中会生成LoadBalancerFeignClient,Ribbon实现了负载均衡决定调用哪台机器。

    // LoadBalancerFeignClient#execute
    @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);
            // 将request封装成了RibbonRequest
          FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                this.delegate, request, uriWithoutHost);
    
          IClientConfig requestConfig = getClientConfig(options, clientName);
          // 这里涉及到Ribbon如何实现负载均衡,具体内容将在Ribbon源码中展开。
          return lbClient(clientName)
                .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
       }
       catch (ClientException e) {
          // ... 不重要
       }
    }
    

    真正执行的Client有OkHttpClient和RibbonClient两个子类。OkhttpClient调用OkHttp的相关组件进行网络请求的发送。

    参考

    [Spring Cloud微服务架构进阶

    相关文章

      网友评论

          本文标题:Feign源码学习

          本文链接:https://www.haomeiwen.com/subject/icdyhltx.html