likes
comments
collection
share

OpenFeign源码2-Bean注册过程和调用过程

作者站长头像
站长
· 阅读数 9

欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈

0. 环境

  • nacos版本:1.4.1
  • Spring Cloud : Hoxton.SR9(没用2020.0.2版本后面说明
  • Spring Boot :2.4.4
  • Spring Cloud alibaba: 2.2.5.RELEASE
  • Spring Cloud openFeign 2.2.2.RELEASE

测试代码:github.com/hsfxuebao/s…

2020.0.X版本开始的OpenFeign底层不再使用Ribbon了

1. FeignClient的bean注册过程

OpenFeign源码2-Bean注册过程和调用过程

1.1 @EnableFeignClients

  想要集成 Feign 客户端,需要我们通过注解 @EnableFeignClients 来开启。这个注解开启了FeignClient的解析过程。这个注解的声明如下,它用到了一个@Import注解,我们知道Import是用来导入一个配置类的,接下来去看一下FeignClientsRegistrar的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}

  FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。它的作用类似于ImportSelector。

  然后就会进入 FeignClientsRegistrar# registerBeanDefinitionsregisterDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClientBeanDeifinition 添加到 spring 容器中.

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   // todo 将@EnableFeignClients注解中的defaultConfiguration属性注册到一个缓存map
   registerDefaultConfiguration(metadata, registry);
   // todo 1) 扫描所有@FeignClient注解的接口,即扫描到所有Feign接口
   // 2) 将每个@FeignClient注解的configuration属性注册到一个缓存map
   // 3) 根据@FeignClient注解元数据生成 FeignClientFactoryBean 的BeanDefinition,
   //    并将这个BeanDefinition注册到一个缓存map
   registerFeignClients(metadata, registry);
}
  • registerDefaultConfiguration:
    private void registerDefaultConfiguration(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
       // 获取到@EnableFeignClients注解的属性值
       // 第二个参数true,表示将Class类型的属性变为了String类型
       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();
          }
          // todo 注册这个defaultConfiguration属性
          registerClientConfiguration(registry, name,
                defaultAttrs.get("defaultConfiguration"));
       }
    }
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
       // 生成一个Builder
       BeanDefinitionBuilder builder = BeanDefinitionBuilder
             .genericBeanDefinition(FeignClientSpecification.class);
       builder.addConstructorArgValue(name);
       builder.addConstructorArgValue(configuration);
       registry.registerBeanDefinition(
             name + "." + FeignClientSpecification.class.getSimpleName(),
             builder.getBeanDefinition()); // 获取到构建的BeanDefinition
    }
    
  • registerFeignClients
    public void registerFeignClients(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
       // 定义一个扫描器
       ClassPathScanningCandidateComponentProvider scanner = getScanner();
       // 初始化扫描器
       scanner.setResourceLoader(this.resourceLoader);
    
       Set<String> basePackages;
    
       // 获取@EnableFeignClients注解的属性元数据
       Map<String, Object> attrs = metadata
             .getAnnotationAttributes(EnableFeignClients.class.getName());
       AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
             FeignClient.class);
       // 获取clients属性值
       final Class<?>[] clients = attrs == null ? null
             : (Class<?>[]) attrs.get("clients");
       // 若clients属性为空,则启用类路径扫描
       if (clients == null || clients.length == 0) {
          // 为扫描器添加一个扫描@FeignClient注解的Filter
          scanner.addIncludeFilter(annotationTypeFilter);
          // todo 获取@EnableFeignClients注解中所有指定的基本包
          basePackages = getBasePackages(metadata);
       }
       else {
          // 指定了clients属性,将所有指定的Feign接口类添加到集合
          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)));
       }
    
       // 遍历这些基本包,将其它的@FeignClient接口找到并写入到集合
       for (String basePackage : basePackages) {
          Set<BeanDefinition> candidateComponents = scanner
                .findCandidateComponents(basePackage);
          // 遍历 候选组件 集合(所有Feign接口都在这个集合中)
          for (BeanDefinition candidateComponent : candidateComponents) {
             // 只处理Feign接口组件
             if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                // 从BeanDefinition中获取注解元数据
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                // 断言:若不是接口,直接抛出异常
                Assert.isTrue(annotationMetadata.isInterface(),
                      "@FeignClient can only be specified on an interface");
    
                // 获取@FeignClient注解属性
                Map<String, Object> attributes = annotationMetadata
                      .getAnnotationAttributes(
                            FeignClient.class.getCanonicalName());
                // todo 获取Feign名称
                String name = getClientName(attributes);
                // 将当前遍历的Feign接口的configuration注册到缓存map
                registerClientConfiguration(registry, name,
                      attributes.get("configuration"));
                // 将FeignClientFactoryBean的BeanDefinition注册到缓存map
                registerFeignClient(registry, annotationMetadata, attributes);
             }
          }
       }
    }
    
    registerFeignClient 在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器。
    private void registerFeignClient(BeanDefinitionRegistry registry,
          AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
       // 获取FeignClient接口的类全路径
       String className = annotationMetadata.getClassName();
       // 生成一个beanFactory,其会为FeignClientFactoryBean生成一些必要的组件
       BeanDefinitionBuilder definition = BeanDefinitionBuilder
             .genericBeanDefinition(FeignClientFactoryBean.class);
       validate(attributes);
       definition.addPropertyValue("url", getUrl(attributes));
       definition.addPropertyValue("path", getPath(attributes));
       String name = getName(attributes);
       definition.addPropertyValue("name", name);
       String contextId = getContextId(attributes);
       definition.addPropertyValue("contextId", contextId);
       definition.addPropertyValue("type", className);
       definition.addPropertyValue("decode404", attributes.get("decode404"));
       definition.addPropertyValue("fallback", attributes.get("fallback"));
       definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
       definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    
       String alias = contextId + "FeignClient";
       // 获取到FeignClientFactoryBean的beanDefinition
       AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    
       boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be null
    
       beanDefinition.setPrimary(primary);
    
       String qualifier = getQualifier(attributes);
       if (StringUtils.hasText(qualifier)) {
          alias = qualifier;
       }
    
       // 生成beanDefinition的holder,通过holder可以获取到这个beanDefinition
       BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
             new String[] { alias });
       // 把BeanDefinition的这个bean定义注册到IOC容器
       BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
    
    我们关注一下,BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过 genericBeanDefinition 来构建的,并且传入了一个FeignClientFactoryBean的类,代码如下。
    public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
       BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
       builder.beanDefinition.setBeanClass(beanClass);
       return builder;
    }
    

1.2 小节

综上代码分析,其实实现逻辑很简单。

  1. 创建一个BeanDefinitionBuilder
  2. 创建一个工厂Bean,并把从@FeignClient注解中解析的属性设置到这个FactoryBean
  3. 调用registerBeanDefinition注册到IOC容器中

2. 动态代理过程详解

OpenFeign源码2-Bean注册过程和调用过程

我们可以发现,FeignClient被动态注册成了一个FactoryBean.

  Spring Cloud FengnClient实际上是利用Spring的代理工厂来生成代理类,所以在这里地方才会把所有的FeignClient的BeanDefinition设置为FeignClientFactoryBean类型,而FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。在Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean。工厂 Bean 是一种特殊的 Bean, 对于 Bean 的消费者来说, 他逻辑上是感知不到这个 Bean 是普通的 Bean 还是工厂 Bean, 只是按照正常的获取 Bean 方式去调用, 但工厂bean 最后返回的实例不是工厂Bean 本身, 而是执行工厂 Bean 的 getObject 逻辑返回的示例。

  简单来说,FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一个代理对象。

2.1 FeignClientFactoryBean.getObject

  getObject调用的是getTarget方法,它从applicationContext取出FeignContext,FeignContext继承了NamedContextFactory,它是用来统一维护feign中各个feign客户端相互隔离的上下文

springboot启动时会加载类路径下/META-INF/spring.factories中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类,FeignContext注册到容器是在FeignAutoConfiguration上完成的。FeignContext是全局唯一的上下文,它继承了NamedContextFactory,它是用来统一维护feign中各个feign客户端相互隔离的上下文

OpenFeign源码2-Bean注册过程和调用过程

@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
    FeignContext context = new FeignContext();
    context.setConfigurations(this.configurations);
    return context;
}

  在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations 的来源就是在前面registerFeignClients方法中将@FeignClient的配置 configuration。

  接着,构建feign.builder,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。FeignContext在上文中已经提到会为每个Feign客户端分配了一个容器,它们的父容器就是spring容器。

  配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。实际上他们最终调用的是Target.target()方法。

@Override
public Object getObject() throws Exception {
   return getTarget();
}

/**
 * @param <T> the target type of the Feign client
 * @return a {@link Feign} client created with the specified data and the context
 * information
 */
<T> T getTarget() {
   // 实例化Feign上下文对象FeignContext
   FeignContext context = this.applicationContext.getBean(FeignContext.class);
   // todo 从Spring子容器中获取相应的实例
   Feign.Builder builder = feign(context);
   // 若url属性为空,则说明其要采用负载均衡方式调用提供者,生成有负载均衡功能的代理类
   if (!StringUtils.hasText(this.url)) {
      // 若name属性不以http开头
      if (!this.name.startsWith("http")) {
         this.url = "http://" + this.name;
      }
      else {
         this.url = this.name;
      }
      // 将规范化的path属性连接到url后
      // 规范化path:若path属性不以斜杠(/)开头,则为其添加斜杠,以斜杠结尾,则去掉尾部斜杠
      this.url += cleanPath();
      // 负载均衡调用
      return (T) loadBalance(builder, context,
            new HardCodedTarget<>(this.type, this.name, this.url));
   }
   // 这是url不为空的情况,即采用直连方式访问提供者,则生成默认的代理类
   if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
      this.url = "http://" + this.url;
   }
   String url = this.url + cleanPath();
   // 从Spring子容器中获取Client
   Client client = getOptional(context, Client.class);
   if (client != null) {
      if (client instanceof LoadBalancerFeignClient) {
         // not load balancing because we have a url,
         // but ribbon is on the classpath, so unwrap
         client = ((LoadBalancerFeignClient) client).getDelegate();
      }
      if (client instanceof FeignBlockingLoadBalancerClient) {
         // not load balancing because we have a url,
         // but Spring Cloud LoadBalancer is on the classpath, so unwrap
         client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
      }
      builder.client(client);
   }
   // todo 生成默认的代理类
   Targeter targeter = get(context, Targeter.class);
   return (T) targeter.target(this, builder, context,
         new HardCodedTarget<>(this.type, this.name, url));
}

Feign.Builder用来构建Feign对象,基于builder实现上下文信息的构建,代码如下。

protected Feign.Builder feign(FeignContext context) {
    // todo
    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(context, Encoder.class))
        .decoder(get(context, Decoder.class))
        .contract(get(context, Contract.class)); //contract协议,用来实现模版解析(后面再详细分析)
    // @formatter:on

    configureFeign(context, builder);
    applyBuildCustomizers(context, builder);

    return builder;
}

从代码中可以看到,feign方法,主要是针对不同的服务提供者生成Feign的上下文信息,比如loggerencoderdecoder等。因此,从这个分析过程中,我们不难猜测到它的原理结构,如下图所示:

OpenFeign源码2-Bean注册过程和调用过程

父子容器隔离的实现方式如下,当调用get方法时,会从context中去获取指定type的实例对象。

//FeignContext.java
protected <T> T get(FeignContext context, Class<T> type) {
    T instance = context.getInstance(contextId, type);
    if (instance == null) {
        throw new IllegalStateException(
            "No bean found of type " + type + " for " + contextId);
    }
    return instance;
}

接着,调用NamedContextFactory中的getInstance方法:

//NamedContextFactory.java
public <T> T getInstance(String name, Class<T> type) {
    //根据`name`获取容器上下文
    AnnotationConfigApplicationContext context = this.getContext(name);

    try {
        //再从容器上下文中获取指定类型的bean。
        return context.getBean(type);
    } catch (NoSuchBeanDefinitionException var5) {
        return null;
    }
}

getContext方法根据namecontexts容器中获得上下文对象,如果没有,则调用createContext创建。

protected AnnotationConfigApplicationContext getContext(String name) {
    if (!this.contexts.containsKey(name)) {
        synchronized(this.contexts) {
            if (!this.contexts.containsKey(name)) {
                this.contexts.put(name, this.createContext(name));
            }
        }
    }

    return (AnnotationConfigApplicationContext)this.contexts.get(name);
}

2.2 生成动态代理

loadBalance :生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端。

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
      HardCodedTarget<T> target) {

    //Feign发送请求以及接受响应的http client,默认是Client.Default的实现,可以修改成OkHttp、HttpClient等。
   Client client = getOptional(context, Client.class);
   if (client != null) {
      builder.client(client);//针对当前Feign客户端,设置网络通信的client

      //targeter表示HystrixTarger实例,因为Feign可以集成Hystrix实现熔断,所以这里会一层包装。
      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-netflix-ribbon?");
}

Client client = (Client)this.getOptional(context, Client.class); 从上下文中获取一个 Client,默认是LoadBalancerFeignClient。它是在FeignRibbonClientAutoConfiguration这个自动装配类中,通过Import实现的

@Import({ HttpClientFeignLoadBalancedConfiguration.class,
        OkHttpFeignLoadBalancedConfiguration.class,
        DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
  .....
}

  这里的通过 DefaultFeignLoadBalancedConfiguration 注入客户端 Client 的实现

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
            SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
                clientFactory);
    }
}

  接下去进入 targeter.target(this, builder, context, target) ,携带着构建好的这些对象去创建代理实例 ,这里有两个实现 HystrixTargeter 、DefaultTargeter 很显然,我们没有配置 Hystrix ,这里会走 DefaultTargeter

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.Feign.Builder#target(feign.Target)

public <T> T target(Target<T> target) {
      return build().newInstance(target);
}

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

  最终会调用 ReflectiveFeign.newInstance

  这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。

  从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的InvocationHandler

public <T> T newInstance(Target<T> target) {
    // 解析接口注解信息
    //根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的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)));
      }
    }
    
    InvocationHandler handler = factory.create(target, methodToHandler);
  // 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}

  targetToHandlersByName.apply(target) :根据Contract协议规则,解析接口类的注解信息,解析成内部表现:targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个<method,MethodHandler>的map,放入InvocationHandler的实现FeignInvocationHandler中。

public Map<String, MethodHandler> apply(Target target) {
      List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
      Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
      for (MethodMetadata md : metadata) {
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
          buildTemplate =
              new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else if (md.bodyIndex() != null) {
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else {
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
        }
        if (md.isIgnored()) {
          result.put(md.configKey(), args -> {
            throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
          });
        } else {
          result.put(md.configKey(),
              factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
        }
      }
      return result;
}

2.3 小节

上述代码,其实也不难理解。

  1. 解析@FeignClient接口声明的方法,根据不同方法绑定不同的处理器。

    1. 默认方法,绑定DefaultMethodHandler
    2. 远程方法,绑定SynchronousMethodHandler
  2. 使用JDK提供的Proxy创建动态代理

MethodHandler,会把方法参数、方法返回值、参数集合、请求类型、请求路径进行解析存储,如下图所示。 OpenFeign源码2-Bean注册过程和调用过程

  1. 被声明为@FeignClient注解的类,在被注入时,最终会生成一个动态代理对象FeignInvocationHandler

    当触发方法调用时,会被FeignInvocationHandler#invoke拦截,FeignClientFactoryBean在实例化过程中所做的事情如下图所示。

    OpenFeign源码2-Bean注册过程和调用过程

总结来说就几个点:

  1. 解析Feign的上下文配置,针对当前的服务实例构建容器上下文并返回Feign对象
  2. Feign根据上下围配置把 log、encode、decoder、等配置项设置到Feign对象中
  3. 对目标服务,使用LoadBalance以及Hystrix进行包装
  4. 通过Contract协议,把FeignClient接口的声明,解析成MethodHandler
  5. 遍历MethodHandler列表,针对需要远程通信的方法,设置SynchronousMethodHandler处理器,用来实现同步远程调用。
  6. 使用JDK中的动态代理机制构建动态代理对象。

3. OpenFeign调用过程

OpenFeign调用过程图示: OpenFeign源码2-Bean注册过程和调用过程 在前面的分析中,我们知道OpenFeign最终返回的是一个 ReflectiveFeign.FeignInvocationHandler 的对象。那么当客户端发起请求时,会进入到 FeignInvocationHandler.invoke 方法中,这个大家都知道,它是一个动态代理的实现。

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }
      // todo 利用分发器筛选方法,找到对应的handler 进行处理
      return dispatch.get(method).invoke(args);
}

而接着,在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下。

@Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        // todo
        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;
      }
    }
}

  经过上述的代码,我们已经将restTemplate拼装完成,上面的代码中有一个 executeAndDecode() 方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    //转化为Http请求报文
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }
     
    Response response;
    long start = System.nanoTime();
    try {
      //发起远程通信
      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) {
    // .......

}

  经过上面的分析,这里的 client.execute client 的类型是LoadBalancerFeignClient,这里就很自然的进入 LoadBalancerFeignClient#execute

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);
        }

}

  其实这个execute里面得流程就是 Ribbon 的那一套。我们可以简单的看一下。首先是构造URI,构造RibbonRequest,选择 LoadBalance,发起调用。

  来看一下lbClient 选择负载均衡器的时候做了什么

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 = this.loadBalancedRetryFactory != null
                ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
                        this.loadBalancedRetryFactory)
                : new FeignLoadBalancer(lb, config, serverIntrospector);
        this.cache.put(clientName, client);
        return client;
}

  可以得出的结论就是 this.factory.getLoadBalancer(clientName)Ribbon 源码里的获取方式一样,无疑这里获取的就是默认的 ZoneAwareLoadBalancer。然后包装成一个 FeignLoadBalancer 进行返回

  既然负载均衡器选择完了,那么一定还有个地方通过该负载去选择一个服务,接着往下看:

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

        try {
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
        } catch (Exception e) {
            Throwable t = e.getCause();
            if (t instanceof ClientException) {
                throw (ClientException) t;
            } else {
                throw new ClientException(e);
            }
        }

}

  上面这段代码就是通过获取到的负载进行执行请求,但是这个时候 服务还没有选择,我们跟进去 submit 请求看一看究竟:

public Observable<T> submit(final ServerOperation<T> operation) {
        final ExecutionInfoContext context = new ExecutionInfoContext();
        // .........
        Observable<T> o = 
                (server == null ? selectServer() : Observable.just(server))
                .concatMap(new Func1<Server, Observable<T>>() {
                    //........
                });
       // .......

}

  可以看到这里有个 selectServer的方法 ,跟进去:

public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
        String host = null;
        int port = -1;
        if (original != null) {
            host = original.getHost();
        }
        if (original != null) {
            Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);        
            port = schemeAndPort.second();
        }

    // Various Supported Cases
    // The loadbalancer to use and the instances it has is based on how it was registered
    // In each of these cases, the client might come in using Full Url or Partial URL
    ILoadBalancer lb = getLoadBalancer();
    if (host == null) {
        // ............
    } else {
        // ...........if (shouldInterpretAsVip) {
            Server svc = lb.chooseServer(loadBalancerKey);
            if (svc != null){
                host = svc.getHost();
                if (host == null){
                    throw new ClientException(ClientException.ErrorType.GENERAL,
                            "Invalid Server for :" + svc);
                }
                logger.debug("using LB returned Server: {} for request: {}", svc, original);
                return svc;
            } else {
                // just fall back as real DNS
                logger.debug("{}:{} assumed to be a valid VIP address or exists in the DNS", host, port);
            }
        } else {
            // consult LB to obtain vipAddress backed instance given full URL
            //Full URL execute request - where url!=vipAddress
            logger.debug("Using full URL passed in by caller (not using load balancer): {}", original);
        }
    }
    // ..........
    return new Server(host, port);
}

  可以看到的是这里获取到了之前构造好的 ZoneAwareLoadBalancer 然后调用 chooseServer 方法获取server ,这个是跟Ribbon 中是一样的流程,这里就不赘述了。

  获取到了server 后,会回调先前 executeWithLoadBalancer 方法里构造的 ServerOperation 的 call 方法:

return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();

  然后会执行 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig) 进行最后的调用,实际上这里走的是 FeignLoadBalancer#execute

@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);
}

  而这里调用的 request.client().execute(request.toRequest(), options) 则是 DefaultFeignLoadBalancedConfiguration 注入的 LoadBalancerFeignClient ,在构造 LoadBalancerFeignClient 的时候 ,传递了个 feign.Client.Default ,然后利用 feign.Client.Default 构造了一个 RibbonRequest

  所以这里走 feign.Client.Default#execute

@Override
public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection, request);
}

  利用 JDK 提供的 HttpURLConnection 发起远程的 HTTP通讯。至此发起请求的流程就完成了。下面附上一张这个过程的流程图,对于Ribbon的调用过程请参考 :Ribbon 源码分析。

4. 总结

Spring Cloud OpenFeign 的核心工作原理经上文探究可以非常简单的总结为:

  1. 通过 @EnableFeignCleints 触发 Spring 应用程序对 classpath@FeignClient 修饰类的扫描
  2. 解析到 @FeignClient 修饰类后, Feign 框架通过扩展 Spring Bean Deifinition 的注册逻辑, 最终注册一个 FeignClientFacotoryBean 进入 Spring 容器
  3. Spring 容器在初始化其他用到 @FeignClient 接口的类时, 获得的是 FeignClientFacotryBean 产生的一个代理对象 Proxy.
  4. 基于 java 原生的动态代理机制, 针对 Proxy 的调用, 都会被统一转发给 Feign 框架所定义的一个 InvocationHandler , 由该 Handler 完成后续的 HTTP 转换, 发送, 接收, 翻译HTTP响应的工作。 OpenFeign源码2-Bean注册过程和调用过程

参考文章

spring-cloud-openfeign-2.2.2源码分析 springcloud-source-study学习github地址 图说系列:OpenFeign源码解析 Spring Cloud 源码分析之OpenFeign Spring Cloud openFeign学习【3.0.2版本】 微服务入门到入土