图文Debug一步一步带你看清SpringCloud openfeign源码执行过程

版本

Version: spring-cloud-openfeign-core:3.0.1

Spring Cloud openfeign 使用

我们首先看看Spring Cloud openfeign 是如何使用的,然后基于使用进行一个源码分析

  1. 添加依赖
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
<!--            <version>3.0.1</version>-->
        </dependency>
  1. 开启Feign
@SpringBootApplication
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.classargs);
    }

}
  1. 接口调用
@FeignClient(name = "test", url = "127.0.0.1:10080")
public interface PlutusClient {

    @RequestMapping(method = RequestMethod.GET, value ="/test/v1/testRpc")
    String testRpc();
}

源码分析

1. 开启Feign自动装配注解 @EnableFeignClients

  • @EnableFeignClients.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients 
{
  
  //等价于basePackages属性,更简洁的方式
  String[] value() default {};
  //指定多个包名进行扫描
  String[] basePackages() default {};
  //指定多个类或接口的class,扫描时会在这些指定的类和接口所属的包进行扫描
  Class<?>[] basePackageClasses() default {};
  //为所有的Feign Client设置默认配置类
  Class<?>[] defaultConfiguration() default {};
  //指定用@FeignClient注释的类列表。如果该项配置不为空,则不会进行类路径扫描
  Class<?>[] clients() default {};
  
}

我们可以看到这个注解上面添加了@Import 注解,不知道@Import 注解的参考 我之前的博文

想写Spring Boot SDK?先深入学习下@Import 注解吧

可以看到@Import 注解导入了一个配置类FeignClientsRegistrar.class , 所以核心处理逻辑都在这个类了

2. 配置类 FeignClientsRegistrar.class

这里不会贴所有源代码,防止大家看不懂,我们一步一步基于方法来分析,部分非主流程细节将省略

首先FeignClientsRegistrar 实现了接口ImportBeanDefinitionRegistrar,这个接口就是spring动态注册Bean的方法, 所以我们直接看FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions 的方法,这个方法就是源码的入口

  • ImportBeanDefinitionRegistrar
public interface ImportBeanDefinitionRegistrar {
  
  default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
  registerBeanDefinitions(importingClassMetadata, registry);
 }

 default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
 }
  
}
  • FeignClientsRegistrar
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrarResourceLoaderAwareEnvironmentAware {
  
  @Override
 public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  registerDefaultConfiguration(metadata, registry);
  registerFeignClients(metadata, registry);
 }
  
  

}

这里可以看到有两个方法registerDefaultConfigurationregisterFeignClients

  • registerDefaultConfiguration

看名字就知道是注册默认的配置类,我们简单的看看吧,这个方法并不是主流程

private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
   // 获取注解 EnableFeignClients 上的值
  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();
   }
   registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
  }
 }

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
  builder.addConstructorArgValue(name);
  builder.addConstructorArgValue(configuration);
  registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
    builder.getBeanDefinition())
;
 }

可以看看defaultAttrs的值

图文Debug一步一步带你看清SpringCloud openfeign源码执行过程
image-20220210220229774

然后将FeignClientSpecification注入spring容器中

  • registerFeignClients

这里是生成接口的代理类的核心逻辑,我们重点来看看

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

  LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
   // 获取注解 EnableFeignClients 上的值
  Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
  final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
   // 1. 未指定EnableFeignClients注解中的clients属性,则去扫描
  if (clients == null || clients.length == 0) {
      // 获取 ClassPath扫描器
   ClassPathScanningCandidateComponentProvider scanner = getScanner();
      //  为扫描器设置资源加载器org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
   scanner.setResourceLoader(this.resourceLoader);
      // 添加 注解类型过滤器,只过滤@FeignClient
   scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
      // 获取需要扫描的基础包集合
   Set<String> basePackages = getBasePackages(metadata);
      // 扫描基础包,且满足过滤条件下的接口封装成BeanDefinition加入集合candidateComponents中
   for (String basePackage : basePackages) {
    candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
   }
  }
   // 2. 指定了EnableFeignClients注解中的clients属性
  else {
   for (Class<?> clazz : clients) {
    candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
   }
  }

  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");
    // 4. 获取 client FeignClient注解上的属性
    Map<String, Object> attributes = annotationMetadata
      .getAnnotationAttributes(FeignClient.class.getCanonicalName());

    String name = getClientName(attributes);
        // 5. 注册配置类
    registerClientConfiguration(registry, name, attributes.get("configuration"));
    // 6.生成 feign client代理类
    registerFeignClient(registry, annotationMetadata, attributes);
   }
  }
 }
  1. EnableFeignClients 注解中没有指定client属性就去扫描包
  2. 如果EnableFeignClients 指定了client class就直接去注册这些feign client
  3. 我们看看candidateComponents里面包含的class
图文Debug一步一步带你看清SpringCloud openfeign源码执行过程
在这里插入图片描述

可以看到扫描到了我们定义的client:PlutusClient

但是需要注意这里的class 还是只是接口,所以我们需要生成这个接口的代理类

  1. 遍历我们扫描到的feign Client,首先获取到FeignClient注解上的属性
图文Debug一步一步带你看清SpringCloud openfeign源码执行过程
image-20220210222008096
  1. 通过方法registerClientConfiguration注册client的配置类
  2. 通过方法registerFeignClient 生成feign client的代理类

我们进去这个方法看看

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
   Map<String, Object> attributes)
 
{
  String className = annotationMetadata.getClassName();
  Class clazz = ClassUtils.resolveClassName(className, null);
  ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
    ? (ConfigurableBeanFactory) registry : null;
  String contextId = getContextId(beanFactory, attributes);
  String name = getName(attributes);
    // 核心类 FeignClientFactoryBean 实现了 FactoryBean 接口
  FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
  factoryBean.setBeanFactory(beanFactory);
  factoryBean.setName(name);
  factoryBean.setContextId(contextId);
  factoryBean.setType(clazz);
  BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
   factoryBean.setUrl(getUrl(beanFactory, attributes));
   factoryBean.setPath(getPath(beanFactory, attributes));
   factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
   Object fallback = attributes.get("fallback");
   if (fallback != null) {
    factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
      : ClassUtils.resolveClassName(fallback.toString(), null));
   }
   Object fallbackFactory = attributes.get("fallbackFactory");
   if (fallbackFactory != null) {
    factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
      : ClassUtils.resolveClassName(fallbackFactory.toString(), null));
   }
   return factoryBean.getObject();
  });
  definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  definition.setLazyInit(true);
  validate(attributes);

  String alias = contextId + "FeignClient";
  AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
  beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
  beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

  // has a default, won't be null
  boolean primary = (Boolean) attributes.get("primary");

  beanDefinition.setPrimary(primary);

  String qualifier = getQualifier(attributes);
  if (StringUtils.hasText(qualifier)) {
   alias = qualifier;
  }

  BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
  BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
 }

代码有点长,我们挑重点看

  1. 可以看到FeignClientFactoryBean 实现了FactoryBean 接口,spring中实现了FactoryBean接口的类在获取的时候都是获取的他的代理对象即getObject()方法返回的对象,所以代理对象生成的逻辑我们直接去看getObject()就好
 @Override
 public Object getObject() {
  return getTarget();
 }

 <T> getTarget() {
    //1. 从应用上下文中获取创建 feign 客户端的上下文对象 FeignContext
  FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
    : applicationContext.getBean(FeignContext.class)
;
    // 2. 构建一个 Feign.Builder 对象,最终有Builder对象来生成Feign代理对象
  Feign.Builder builder = feign(context);
  // FeignClient 属性 url 没有被指定
  if (!StringUtils.hasText(url)) {
   if (!name.startsWith("http")) {
    url = "http://" + name;
   }
   else {
    url = name;
   }
   url += cleanPath();
      // 3. 通过loadBalance负载均衡并生成feign Client代理对象 
   return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
  }
    // @FeignClient 属性 url 属性被指定的情况,则不需要负载均衡
  if (StringUtils.hasText(url) && !url.startsWith("http")) {
   url = "http://" + url;
  }
  String url = this.url + cleanPath();
  Client client = getOptional(context, Client.class);
  if (client != null) {
   if (client instanceof FeignBlockingLoadBalancerClient) {
    // not load balancing because we have a url,
    // but Spring Cloud LoadBalancer is on the classpath, so unwrap
        // 因为指定了明确的服务节点url,所以这里不需要负载均衡
    client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
   }
   builder.client(client);
  }
  Targeter targeter = get(context, Targeter.class);
    // 生成最终的feign client 实例
  return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
 }
  1. 构建一个FeignContext,包含后面生成Feign需要用到的一些属性

  2. 构建一个Feign.Builder 供后续创建代理对象使用

    protected Feign.Builder feign(FeignContext context) {
       // log 相关不用管
      FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
      Logger logger = loggerFactory.create(type);

      // @formatter:off
      Feign.Builder builder = get(context, Feign.Builder.class)
        // required values
        .logger(logger)
        .encoder(get(contextEncoder.class))//编码器
        .decoder(get(contextDecoder.class))//解码器
        .contract(get(contextContract.class))
    ;// 上下文
      // @formatter:on
      // 设置一些客户端属性 比如feign读取超时时间、连接超时时间等
      configureFeign(context, builder);
       // 扩展给外部使用者,外部使用者可以给Feign.Builder设置其他参数
      applyBuildCustomizers(context, builder);

      return builder;
     }
  3. 通过loadBalance负载均衡并生成feign Client代理对象

我们进入loadBalance() 方法看看

protected <T> loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
   // 1.获取从spring中获取Client实现类 FeignBlockingLoadBalancerClient
  Client client = getOptional(context, Client.class);
  if (client != null) {
   builder.client(client);
      // 2. 获取Targeter 实现类 DefaultTargeter
   Targeter targeter = get(context, Targeter.class);
   return targeter.target(this, builder, context, target);
  }

  throw new IllegalStateException(
    "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
 }

我们通过debug可以看到Client 的实现类为FeignBlockingLoadBalancerClient

图文Debug一步一步带你看清SpringCloud openfeign源码执行过程
image-20220211102802828

那么这个类在哪里创建的呢?很明显就是就自动装配配的,我们通过搜索FeignBlockingLoadBalancerClient 并查看在哪使用最终定位到在DefaultFeignLoadBalancerConfiguration 中装配的

图文Debug一步一步带你看清SpringCloud openfeign源码执行过程
image-20220211103748620

很明显是通过springboot的spi自动装配的,这里就不分析了,我们继续回到主线

这里Targeter 获取到的实现类是DefaultTargeter 分析方式和上面类似就不展开了

最后生成代理对象的方法就是这里了

  • Feign.class
public <T> target(Target<T> target) {
      return build().newInstance(target);
    }
  // 最终根据各种配置生成一个 ReflectiveFeign 对象 
    public Feign build() {
      Client client = Capability.enrich(this.client, capabilities);
      Retryer retryer = Capability.enrich(this.retryer, capabilities);
      List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
          .map(ri -> Capability.enrich(ri, capabilities))
          .collect(Collectors.toList());
      Logger logger = Capability.enrich(this.logger, capabilities);
      Contract contract = Capability.enrich(this.contract, capabilities);
      Options options = Capability.enrich(this.options, capabilities);
      Encoder encoder = Capability.enrich(this.encoder, capabilities);
      Decoder decoder = Capability.enrich(this.decoder, capabilities);
      InvocationHandlerFactory invocationHandlerFactory =
          Capability.enrich(this.invocationHandlerFactory, capabilities);
      QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);

      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }

// ReflectiveFeign.java 方法
// 创建最终的feign客户端实例 : 一个 ReflectiveFeign$FeignInvocationHandler 的动态代理对象
public <T> newInstance(Target<T> target) {
   // MethodHandler 接口实现类默认 √ 真正的代理逻辑实现
    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)));
      }
    }
   // factory = InvocationHandlerFactory.Default 内部类
   // 这里 create 创建为 ReflectiveFeign 参考下面create方法
    InvocationHandler handler = factory.create(target, methodToHandler);
   // 创建feign客户端实例的动态代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);
  // 将缺省方法处理器绑定到feign客户端实例的动态代理对象上
    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

// InvocationHandlerFactory.Default.class
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }

代理类真正的逻辑实现是在SynchronousMethodHandler方法里面

public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        // 远程调用
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }


Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      // 这个client是 Client.Default  去调用
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);


    if (decoder != null)
      return decoder.decode(response, metadata.returnType());

    CompletableFuture<Object> resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);

    try {
      if (!resultFuture.isDone())
        throw new IllegalStateException("Response handling not done");

      return resultFuture.join();
    } catch (CompletionException e) {
      Throwable cause = e.getCause();
      if (cause != null)
        throw cause;
      throw e;
    }
  }

可以看到核心实现又是委托给了实际的网络调用是在Client中调用的图文Debug一步一步带你看清SpringCloud openfeign源码执行过程

整体的执行流程就是这样了,大致流程源码就分析完成 了

总结

总的流程来说就是当我们使用@EnableFeignClients 注解后就启动了feign的客户端扫描注册机制,然后扫描那些被@FeignClient装饰的接口,然后为这些接口生成动态代理类,这些代理类最终的代理实现是交给SynchronousMethodHandler 去实现,而真正实现远程调用又是交给了Client.Default去执行网络请求


原文始发于微信公众号(小奏技术):图文Debug一步一步带你看清SpringCloud openfeign源码执行过程

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/30079.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!