欢迎光临
我们一直在努力

@EnableFeignClients 扫描注册流程

启用 @EnableFeignClients 时会 @import 一个 FeignClientsRegistrar.class

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients

FeignClientsRegistrar 中,通过 #registerBeanDefinitions 方法中调用 #registerFeignClients 进行 FeignClient 注册

class FeignClientsRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }
}

#registerFeignClients 则优先判断 @EnableFeignClients 注解中是否指定了 clients 属性,如果指定了,则仅导入 clients 中指定的类,如果未指定,则启用 scanner 扫描 @FeignClient 注解。最终,将 FeignClient 收到到 candidateComponents 集合中

LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();

Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);
    scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
    Set<String> basePackages = getBasePackages(metadata);
    for (String basePackage : basePackages) {
        candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
    }
}else {
    for (Class<?> clazz : clients) {
        candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
    }
}

candidateComponents 收集完成后,遍历并调用 #registerFeignClient 进行客户端注册

for (BeanDefinition candidateComponent : candidateComponents) {
    // 。。。省略N行
    registerFeignClient(registry, annotationMetadata, attributes);
}

#registerFeignClient 方法中,则为每一个FeignClient创建 FeignClientFactoryBean 对象,FeignClientFactoryBean 实现 Spring FactoryBean 接口,因此实际创建 FeignClient 对象是在 FeignClientFactoryBean#getObject 方法,也就是最终指向 #getTarget 如下:

public class FeignClientFactoryBean implements FactoryBean<Object> ... {
    @Override
    public Object getObject() {
        return getTarget();
    }

    <T> T getTarget() {
        FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class) : applicationContext.getBean(FeignContext.class);
        // 关于 Builder 详见文章末尾
        Feign.Builder builder = feign(context);

        // 第1种返回方式
        if (!StringUtils.hasText(url)) {
            return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
        }

        // 。。。省略N行,第2种返回方式
        applyBuildCustomizers(context, builder);
        Targeter targeter = get(context, Targeter.class);
        return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
    }
}

判断 @FeignClient 中的 url 属性是否赋值,可以分为两种情况进行实例构建

  • 方式1:未赋值则通过 #loadBalance 进行构建
  • 方式2:如果url赋值,则根据URL为准,直接构建 FeignClient

常规开发中,一般使用 方式1#loadBalance 如下:

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
    Client client = getOptional(context, Client.class);
    if (client != null) {
        builder.client(client);
        applyBuildCustomizers(context, builder);
        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?");
}

targeter.target 再跟进去发现实际使用的是 Feign.Builder builder#target 方法

public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
 Target.HardCodedTarget<T> target) {
    return feign.target(target);
}

builder#target 如下:

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

public Feign build() {
    // 。。。省略N行
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

再跟进去也就是 ReflectiveFeign#newInstance 方法,这个方法就是真正创建FeignClient代理对象的方法

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

创建 FeignClient Proxy 之后,实际上每一个方法都有一个 Handler,接口为:MethodHandler,feign提供两个实现类,如下:

  • SynchronousMethodHandler
  • DefaultMethodHandler

在上面的代码中,if (Util.isDefault(method)) 成立,则使用 DefaultMethodHandler,否则使用 nameToHandler.get 出来的 MethodHandler,基本上是 SynchronousMethodHandler,也有可能是如下实现:

// targetToHandlersByName.apply(target) 里面存在一个判断
if (md.isIgnored()) {
    result.put(md.configKey(), args -> {
        throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
    });
}

Util.isDefault

  public static boolean isDefault(Method method) {
    // Default methods are public non-abstract, non-synthetic, and non-static instance methods
    // declared in an interface.
    // method.isDefault() is not sufficient for our usage as it does not check
    // for synthetic methods. As a result, it picks up overridden methods as well as actual default
    // methods.
    final int SYNTHETIC = 0x00001000;
    return ((method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC | SYNTHETIC)) == Modifier.PUBLIC)
        && method.getDeclaringClass().isInterface();
  }

所以,最常见的处理器就是 SynchronousMethodHandler 处理器,它的 invoke 方法,就是真正执行 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 {
          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;
        }
    }
}

关于 Feign.Builder

从整体流程来看,最终FeignClient的构建都是通过 Feign.Builder 来处理,这个 Feign.Builder 本身就是用来构建 FeignClient 的,但是它还有几个子类:

  • Spring Cloud OpenFeign 中,提供 FeignCircuitBreaker.Builder
  • Spring Cloud Alibaba Sentinel 中,提供 SentinelFeign.Builder

FeignCircuitBreaker.Builder 则在 FeignClientsConfiguration 中进行配置,如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CircuitBreaker.class)
@ConditionalOnProperty("feign.circuitbreaker.enabled")
protected static class CircuitBreakerPresentFeignBuilderConfiguration {

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean({ Feign.Builder.class, CircuitBreakerFactory.class })
    public Feign.Builder defaultFeignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    @ConditionalOnBean(CircuitBreakerFactory.class)
    public Feign.Builder circuitBreakerFeignBuilder() {
        return FeignCircuitBreaker.builder();
    }
}

SentinelFeign.Builder 则在 SentinelFeignAutoConfiguration 中进行配置,如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SphU.class, Feign.class })
public class SentinelFeignAutoConfiguration {
    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "feign.sentinel.enabled")
    public Feign.Builder feignSentinelBuilder() {
        return SentinelFeign.builder();
    }
}

所以这就解释了为什么Sentinel适配Feign时,需要配置 feign.sentinel.enabled=true

赞(0)
LiuYD's 个人技术分享 » @EnableFeignClients 扫描注册流程

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址