一文看懂Openfeign服务调用原理

2021-12-20 17:11:40 浏览数 (1)

一、概述

OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。

OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

二、简单使用

引入依赖

代码语言:javascript复制
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

编写服务引用

代码语言:javascript复制
@Component
@Slf4j
public class DemoAPIProxy implements InitializingBean {

    @Autowired
    private DemoAPI demoAPI;

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info(this.demoAPI.hello());
    }
}

@FeignClient(name = "openfeign-provider")
@RequestMapping("demo")
public interface DemoAPI {

    @GetMapping("hello")
    String hello();
}

服务启动类

代码语言:javascript复制
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"org.example.api"})
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}

这样我们就完成了openfeign的服务引用。

三、生成代理

开启Openfeign能力是通过EnableFeignClients来实现:

代码语言:javascript复制
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

  String[] value() default {};

  String[] basePackages() default {};

  Class<?>[] basePackageClasses() default {};

  Class<?>[] defaultConfiguration() default {};

  Class<?>[] clients() default {};
}

value是basePackages别名,指向basePackages,是通过类的方式定义扫描路径,defaultConfiguration是默认配置,clients通过具体类的方式指定扫描范围,与basePackages互斥,我们通常使用basePackages方式。

EnableFeignClients导入了FeignClientsRegistrar类,看一下类继承关系:

FeignClientsRegistrar本质是一个ImportBeanDefinitionRegistrar,并且持有环境变量和资源加载器的能力,FeignClientsRegistrar重写了registerBeanDefinitions方法,该方法在容器上下文刷新(启动时调用refresh)时被调用,调用时机此处不展开分析,我们看一下FeignClientsRegistrar#registerBeanDefinitions实现:

代码语言:javascript复制
public void registerBeanDefinitions(AnnotationMetadata metadata,
    BeanDefinitionRegistry registry) {
  registerDefaultConfiguration(metadata, registry);
  registerFeignClients(metadata, registry);
}

先注册默认配置,然后注册FeignClient相关类,接着看注册默认配置:

代码语言:javascript复制
//注册默认配置
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();
    }
    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());
}

很简单,从EnableFeignClients注解中取出默认配置,然后注册成FeignClientSpecification类定义备用,FeignClientSpecification是包装了名称和配置的类:

代码语言:javascript复制
class FeignClientSpecification implements NamedContextFactory.Specification {

  private String name;

  private Class<?>[] configuration;

}

接着看注册FeignClient:

代码语言:javascript复制
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)));
  }

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

        registerFeignClient(registry, annotationMetadata, attributes);
      }
    }
  }
}

先调用getScanner方法生成类扫描器:

代码语言:javascript复制
protected ClassPathScanningCandidateComponentProvider getScanner() {
  return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
    @Override
    protected boolean isCandidateComponent(
        AnnotatedBeanDefinition beanDefinition) {
      boolean isCandidate = false;
      if (beanDefinition.getMetadata().isIndependent()) {
        if (!beanDefinition.getMetadata().isAnnotation()) {
          isCandidate = true;
        }
      }
      return isCandidate;
    }
  };
}

扫描器初始条件限定为被注解标注的独立接口.

然后设置过滤条件为带FeignClient注解的接口,然后设置扫描包路径或者指定类集合,然后调用registerFeignClient方法执行注册逻辑:

代码语言:javascript复制
private void registerFeignClient(BeanDefinitionRegistry registry,
    AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
  String className = annotationMetadata.getClassName();
  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";
  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;
  }

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

将属性处理后封装成FeignClientFactoryBean注册到上下文容器中,而它又是个FactoryBean:

在之前文章《@Autowired注解原理分析》有讲到对于FactoryBean类型的类定义,会在注入的时候调用getObject来返回具体实例,具体实现后边分析。@EnableFeignClient生成接口代理的时序图如下:

四、代理注入

继续用前边的例子来说:

代码语言:javascript复制
@FeignClient(name = "openfeign-provider")
@RequestMapping("demo")
public interface DemoAPI {

    @GetMapping("hello")
    String hello();
}

@Autowired
private DemoAPI demoAPI;

为什么DemoAPI是二方服务定义的接口,消费方是看不到具体实现的,为什么可以直接拿来注入并且使用呢?

我们前边说被FeignClient标注的接口会包装成FeignClientFactoryBean注册到容器中,在使用的时候通过getObject来注入,那么我们来看一下FeignClientFactoryBean的getObject实现:

代码语言:javascript复制
public Object getObject() throws Exception {
  return getTarget();
}

<T> T getTarget() {
  FeignContext context = this.applicationContext.getBean(FeignContext.class);
  Feign.Builder builder = feign(context);

  if (!StringUtils.hasText(this.url)) {
    if (!this.name.startsWith("http")) {
      this.url = "http://"   this.name;
    }
    else {
      this.url = this.name;
    }
    this.url  = cleanPath();
    return (T) loadBalance(builder, context,
        new HardCodedTarget<>(this.type, this.name, this.url));
  }
  if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
    this.url = "http://"   this.url;
  }
  String url = this.url   cleanPath();
  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();
    }
    builder.client(client);
  }
  Targeter targeter = get(context, Targeter.class);
  return (T) targeter.target(this, builder, context,
      new HardCodedTarget<>(this.type, this.name, url));
}

getObject会调用getTarget,先从容器上下文获取FeignContext实例,那么FeignContext是个什么东西呢?

代码语言:javascript复制
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

  public FeignContext() {
    super(FeignClientsConfiguration.class, "feign", "feign.client.name");
  }

}

他是创建Feign实例的工厂,继承于NamedContextFactory类(后边分析),那么FeignContext什么时候生成的呢?

来看另一个配置类:

代码语言:javascript复制
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
    FeignHttpClientProperties.class })
public class FeignAutoConfiguration {

  @Autowired(required = false)
  private List<FeignClientSpecification> configurations = new ArrayList<>();

  @Bean
  public HasFeatures feignFeature() {
    return HasFeatures.namedFeature("Feign", Feign.class);
  }

  @Bean
  public FeignContext feignContext() {
    FeignContext context = new FeignContext();
    context.setConfigurations(this.configurations);
    return context;
  }

  @Configuration
  @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
  protected static class HystrixFeignTargeterConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Targeter feignTargeter() {
      return new HystrixTargeter();
    }

  }

  @Configuration
  @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
  protected static class DefaultFeignTargeterConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Targeter feignTargeter() {
      return new DefaultTargeter();
    }

  }
  ......
}

FeignAutoConfiguration是Feign的自动配置类,创建了FeignContext,并且选择性创建了Targeter,以及省略掉的feignClient配置.

回到getTarget,获取到FeignContext之后,调用feign方法使用FeignContext生成Feign.Builder:

代码语言:javascript复制
protected Feign.Builder feign(FeignContext context) {
  FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
  Logger logger = loggerFactory.create(this.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));
  // @formatter:on

  configureFeign(context, builder);

  return builder;
}

这里边从上下文里边获取Feign.Builder实例,那么它又是什么?什么时候生成的?

代码语言:javascript复制
public static class Builder {

    private final List<RequestInterceptor> requestInterceptors =
        new ArrayList<RequestInterceptor>();
    private Logger.Level logLevel = Logger.Level.NONE;
    private Contract contract = new Contract.Default();
    private Client client = new Client.Default(null, null);
    private Retryer retryer = new Retryer.Default();
    private Logger logger = new NoOpLogger();
    private Encoder encoder = new Encoder.Default();
    private Decoder decoder = new Decoder.Default();
    private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    private Options options = new Options();
    private InvocationHandlerFactory invocationHandlerFactory =
        new InvocationHandlerFactory.Default();
    private boolean decode404;
    private boolean closeAfterDecode = true;
    private ExceptionPropagationPolicy propagationPolicy = NONE;
}

Builder是Feign的一个静态内部类,封装了Client、Retryer、Encoder、Decoder和InvocationHandlerFactory等相关组件.

在FeignClientsConfiguration配置类中定义和注入:

代码语言:javascript复制
@Configuration
public class FeignClientsConfiguration {


  @Autowired(required = false)
  private Logger logger;

  @Bean
  @ConditionalOnMissingBean
  public Decoder feignDecoder() {
    return new OptionalDecoder(
        new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
  }

  @Bean
  @ConditionalOnMissingBean
  @ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
  public Encoder feignEncoder() {
    return new SpringEncoder(this.messageConverters);
  }

  @Bean
  @ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
  @ConditionalOnMissingBean
  public Encoder feignEncoderPageable() {
    return new PageableSpringEncoder(new SpringEncoder(this.messageConverters));
  }

  @Bean
  @ConditionalOnMissingBean
  public Contract feignContract(ConversionService feignConversionService) {
    return new SpringMvcContract(this.parameterProcessors, feignConversionService);
  }

  @Bean
  public FormattingConversionService feignConversionService() {
    FormattingConversionService conversionService = new DefaultFormattingConversionService();
    for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {
      feignFormatterRegistrar.registerFormatters(conversionService);
    }
    return conversionService;
  }

  @Bean
  @ConditionalOnMissingBean
  public Retryer feignRetryer() {
    return Retryer.NEVER_RETRY;
  }

  @Bean
  @Scope("prototype")
  @ConditionalOnMissingBean
  public Feign.Builder feignBuilder(Retryer retryer) {
    return Feign.builder().retryer(retryer);
  }
  ......
}

FeignClientsConfiguration中也定义了默认编码解码和重试组件,Builder的作用于为prototype(不同的二方服务不共享)。

回到feign方法,从容器中拿到Feign.Builder,然后再拿到编解码器和springmvc注解契约装配Builder并返回。

接着看getTarget方法,如果url没有内容,则会走到loadBalance方法,否则从FeignContext获取Targeter并执行

Targeter.target返回调用。其实loadBalance最终也会调用Targeter.target,所以我们直接看loadBalance实现:

代码语言:javascript复制
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
    HardCodedTarget<T> target) {
  Client client = getOptional(context, Client.class);
  if (client != null) {
    builder.client(client);
    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?");
}

首先从context中获取Client,可以理解成最终执行网络请求的客户端,可以自己实现也可以使用FeignAutoConfiguration中定义的ApacheHttpClient或者OkHttpClient,具体使用哪一种classpath中引入了哪种实现和相关依赖,ApacheHttpClient需要引入feign.httpclient.ApacheHttpClient和httpclient,而OkHttpClient需要引入feign.okhttp.OkHttpClient和OkHttp,看一下我们前边FeignAutoConfiguration代码中省略的Client相关配置:

ApacheHttpClient

代码语言:javascript复制
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration {

  private final Timer connectionManagerTimer = new Timer(
      "FeignApacheHttpClientConfiguration.connectionManagerTimer", true);

  @Autowired(required = false)
  private RegistryBuilder registryBuilder;

  private CloseableHttpClient httpClient;

  @Bean
  @ConditionalOnMissingBean(HttpClientConnectionManager.class)
  public HttpClientConnectionManager connectionManager(
      ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
      FeignHttpClientProperties httpClientProperties) {
    final HttpClientConnectionManager connectionManager = connectionManagerFactory
        .newConnectionManager(httpClientProperties.isDisableSslValidation(),
            httpClientProperties.getMaxConnections(),
            httpClientProperties.getMaxConnectionsPerRoute(),
            httpClientProperties.getTimeToLive(),
            httpClientProperties.getTimeToLiveUnit(),
            this.registryBuilder);
    this.connectionManagerTimer.schedule(new TimerTask() {
      @Override
      public void run() {
        connectionManager.closeExpiredConnections();
      }
    }, 30000, httpClientProperties.getConnectionTimerRepeat());
    return connectionManager;
  }

  @Bean
  public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
      HttpClientConnectionManager httpClientConnectionManager,
      FeignHttpClientProperties httpClientProperties) {
    RequestConfig defaultRequestConfig = RequestConfig.custom()
        .setConnectTimeout(httpClientProperties.getConnectionTimeout())
        .setRedirectsEnabled(httpClientProperties.isFollowRedirects())
        .build();
    this.httpClient = httpClientFactory.createBuilder()
        .setConnectionManager(httpClientConnectionManager)
        .setDefaultRequestConfig(defaultRequestConfig).build();
    return this.httpClient;
  }

  @Bean
  @ConditionalOnMissingBean(Client.class)
  public Client feignClient(HttpClient httpClient) {
    return new ApacheHttpClient(httpClient);
  }

  @PreDestroy
  public void destroy() throws Exception {
    this.connectionManagerTimer.cancel();
    if (this.httpClient != null) {
      this.httpClient.close();
    }
  }

}

OkHttpClient

代码语言:javascript复制
@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
protected static class OkHttpFeignConfiguration {

  private okhttp3.OkHttpClient okHttpClient;

  @Bean
  @ConditionalOnMissingBean(ConnectionPool.class)
  public ConnectionPool httpClientConnectionPool(
      FeignHttpClientProperties httpClientProperties,
      OkHttpClientConnectionPoolFactory connectionPoolFactory) {
    Integer maxTotalConnections = httpClientProperties.getMaxConnections();
    Long timeToLive = httpClientProperties.getTimeToLive();
    TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
    return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
  }

  @Bean
  public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
      ConnectionPool connectionPool,
      FeignHttpClientProperties httpClientProperties) {
    Boolean followRedirects = httpClientProperties.isFollowRedirects();
    Integer connectTimeout = httpClientProperties.getConnectionTimeout();
    Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
    this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
        .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
        .followRedirects(followRedirects).connectionPool(connectionPool)
        .build();
    return this.okHttpClient;
  }

  @PreDestroy
  public void destroy() {
    if (this.okHttpClient != null) {
      this.okHttpClient.dispatcher().executorService().shutdown();
      this.okHttpClient.connectionPool().evictAll();
    }
  }

  @Bean
  @ConditionalOnMissingBean(Client.class)
  public Client feignClient(okhttp3.OkHttpClient client) {
    return new OkHttpClient(client);
  }

}

回到loadBalance,从上下文拿到Client后配置到Builder中,然后从上下文中拿到Targeter并调用target方法,Targeter实现有两个,分别是DefaultTargeter和HystrixTargeter,后者具有熔断和Fallback调用能力,我们直接看DefaultTargeter#target:

代码语言:javascript复制
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方法,继续看实现:

代码语言:javascript复制
public <T> T target(Target<T> target) {
  return build().newInstance(target);
}

先调用build返回Feign实例,然后调用newInstance方法,看build实现:

代码语言:javascript复制
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);
}

创建SynchronousMethodHandler.Factory和ParseHandlersByName然后封装成ReflectiveFeign(继承了Feign)并返回,然后看newInstance实现:

代码语言:javascript复制
public <T> T newInstance(Target<T> target) {
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);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
    new Class<?>[] {target.type()}, handler);

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

看到这里就比较清晰了,把接口中的方法和默认实现放到Map<Method, MethodHandler>中然后使用InvocationHandlerFactory.Default()创建InvocationHandler,然后使用jdk动态代理生成接口的代理并返回,这里主要看一下Feign的InvocationHandler实现:

代码语言:javascript复制
  static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }

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

      return dispatch.get(method).invoke(args);
    }
   }

到这里二方服务定义的接口就被@EnableFeignClient通过动态代理的方式注入到了服务中,整流程大致如下:

五、服务调用

正如我们前边所说,通过@Autowired或者@Resource注入的时候,注入的是被封装之后的代理类实现,

jdk动态代理持有的是ReflectiveFeign.FeignInvocationHandler类型的InvocationHandler,那么具体调用的时候,会调用FeignInvocationHandler#invoke:

代码语言:javascript复制
@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();
  }

  return dispatch.get(method).invoke(args);
}

前几个判断分支都是调用了Object的基本方法,最后dispatch.get(method).invoke(args)才是接口的业务方法调用,dispatch的类型是Map<Method, MethodHandler>,是接口中方法与MethodHandler的映射关系,而MethodHandler又被SynchronousMethodHandler.Factory封装成SynchronousMethodHandler(实现了MethodHandler):

代码语言:javascript复制
public MethodHandler create(Target<?> target,
                            MethodMetadata md,
                            RequestTemplate.Factory buildTemplateFromArgs,
                            Options options,
                            Decoder decoder,
                            ErrorDecoder errorDecoder) {
  return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
      logLevel, md, buildTemplateFromArgs, options, decoder,
      errorDecoder, decode404, closeAfterDecode, propagationPolicy);
}

那么dispatch.get(method).invoke(args)最终调用的就是SynchronousMethodHandler#invoke:

代码语言:javascript复制
  @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 {
        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;
      }
    }
  }

可以看到调用的时候默认是带有重试能力,默认是5次,具体调用交给executeAndDecode来实现:

代码语言:javascript复制
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 {
      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);
    }
   ....
   //省略
  }

核心是client.execute,我们选择性看一下OkHttpClient的实现:

代码语言:javascript复制
  @Override
  public feign.Response execute(feign.Request input, feign.Request.Options options)
      throws IOException {
    okhttp3.OkHttpClient requestScoped;
    if (delegate.connectTimeoutMillis() != options.connectTimeoutMillis()
        || delegate.readTimeoutMillis() != options.readTimeoutMillis()
        || delegate.followRedirects() != options.isFollowRedirects()) {
      requestScoped = delegate.newBuilder()
          .connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS)
          .readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS)
          .followRedirects(options.isFollowRedirects())
          .build();
    } else {
      requestScoped = delegate;
    }
    Request request = toOkHttpRequest(input);
    Response response = requestScoped.newCall(request).execute();
    return toFeignResponse(response, input).toBuilder().request(input).build();
  }

很明显,最终服务调用会委托给okhttp3.OkHttpClient来执行,然后组装结果和状态码返回调用,到这里openfeign的服务调用就分析完了,为了帮助理解和有更直观的概念,我们看一下服务调用时序图:

六、组件支撑

  • FeignClientsRegistrar:扫描@FeignClient注解的接口并注册成BeanDefinition到应用容器
  • FeignClientFactoryBean:注入远程服务接口代理实现
  • FeignContext:维护服务维度上下文,存储服务调用相关工具
  • Feign.Builder:用于构建服务调用的Client,维护服务调用相关组件(编解码器、重试组件、代理创建工厂等)
  • Targeter:用于组装服务接口代理
  • Feign:用于创建服务接口代理
  • ReflectiveFeign:接口代理实现
  • Client:封装网络调用工具,并提供请求调用能力(Default、ApacheHttpClient和OkHttpClient等)

总结

本文分析了Openfeign的服务调用流程,通过源码从生成代理、代理注入和服务调用维度分析了服务调用的具体实现,后边接涉及到了整个流程中涉及到的一些核心概念和组件,中间涉及到负载均衡部分没有展开深入分析,如果感兴趣可以自己翻阅相关资料或者源码,希望通过本篇文章的介绍,对大家Openfeign的使用和原理理解所有帮助,如若文中有错误或者理解偏差的地方,希望联系作者及时修改。

0 人点赞