Feign源码学习

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 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 basePackages;
   Map 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 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 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 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 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 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 loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget 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 target(FeignClientFactoryBean factory, Feign.Builder feign,
         FeignContext context, Target.HardCodedTarget target) {
      return feign.target(target);
   }
}

Feign.Builder#target()

public  T target(Target 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 newInstance(Target target) {
  // 这里生成所有方法MethodHandler
  // nameToHandler由Contract解析target得到
  // 在MethodHandler中存在对应方法的RestTemplate.Factory和SynchronizedMethodHandler
  Map nameToHandler = targetToHandlersByName.apply(target);
  Map methodToHandler = new LinkedHashMap();
  List defaultMethodHandlers = new LinkedList();

  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、函数的调用和网络请求

Feign源码学习_第1张图片
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 varBuilder = new LinkedHashMap();
  // 遍历MethodMetadata中所有关于参数的索引及其对应名称的配置信息。
  for (Entry> 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 queryMap = toQueryMap(value);
    template = addQueryMapQueryParameters(queryMap, template);
  }

  // 设置headerMap参数
  if (metadata.headerMapIndex() != null) {
    template =
        addHeaderMapHeaders((Map) 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源码学习)