> 文章列表 > OpenFeign#1 - FeignClient 是如何注册的?

OpenFeign#1 - FeignClient 是如何注册的?

OpenFeign#1 - FeignClient 是如何注册的?

文章目录

  • @EnableFeignClients
    • FeignClientsRegistrar
      • registerDefaultConfiguration
      • registerFeignClients
  • @FeignClient
    • FeignClientFactoryBean
      • FeignContext
      • feign(FeignContext)

@EnableFeignClients

注解会导致 FeignClientsRegistrar 的注入.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {...Class<?>[] defaultConfiguration() default {};...
}

FeignClientsRegistrar

这是一个 FeignClient 的注册器, 实现了接口 ImportBeanDefinitionRegistrarregisterBeanDefinition(AnnotationMetadata metadata, BeanDefinitionRegistry registry)方法:

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {registerDefaultConfiguration(metadata, registry); // 1registerFeignClients(metadata, registry); // 2
}

registerDefaultConfiguration

其中, registerDefaultConfiguration 负责注册 @EnableFeignClients 的属性 defaultConfiguration 指定的配置类 (注意 name 属性的约定方式 -> FeignClientSpecification).
OpenFeign#1 - FeignClient 是如何注册的?

调用链:registerBeanDefinitions-registerDefaultConfiguration-registerClientConfiguration:

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {// 配置类被注入成 "FeignClientSpecification"BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);builder.addConstructorArgValue(name);builder.addConstructorArgValue(configuration);registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());
}

registerFeignClients

OpenFeign#1 - FeignClient 是如何注册的?

registerFeignClients则负责注册所有@FeignClient(将标注了该注解的接口注册为 FeignClientFactoryBean). 同时以 name 为标识注册该 FeignClient 的配置类. 调用链:registerBeanDefinitions-registerFeignClients-registerFeignClient

其中, registerFeignClients 方法的具体职责是:

  1. 获取 @EnableFeignClients 的 clients 属性指定的"标注了 @FeignClient 的 Class, 该属性非空时会和第二步互斥.
  2. 如果 @EnableFeignClients 的 clients 属性没有指定, 则会启用基于属性 basePackages 或者服务本身 basePackage 对 @FeignClient 的候选类的扫描.
  3. 注册基于 clientName 的配置对象:
    1. 获取 clientName: contextId || value || name || serviceId [详见: org.springframework.cloud.openfeign.FeignClientsRegistrar#getClientName)]
    2. 调用 registerClientConfiguration(BeanDefinitionRegistry, clientName, 注解上配置的 configuration[])
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerClientConfiguration
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);builder.addConstructorArgValue(name);builder.addConstructorArgValue(configuration);registry.registerBeanDefinition(// 注意 name 的定义方式与 `registerDefaultConfiguration` 的区别name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());
}
  1. 注册通过第1步或者第2步获取到的"候选类", 调用 registerFeignClient:
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();// 本质上也是注册成 FeignClientFactoryBeanBeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);... /* 将注解属性添加到 BeanDefinitionBuilder */String alias = contextId + "FeignClient";AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);// has a default, won't be nullboolean 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);
}

至此, @EnableFeignClients 的流程结束. 最终会输出默认的 FeignClientSpecification 与 FeignClient 配对的 FeignClientSpecification, 和 FeignClientFactoryBean. 整体流程图如下:
OpenFeign#1 - FeignClient 是如何注册的?
从上一节我们知道, @EnabelFeignclients 会扫描并将标注了 @FeignClient 注解的 Bean 注册成 FeignClientFactoryBean, 下面我们主要看看这个 FactoryBean 的能力.

@FeignClient

从上一节我们知道, @EnabelFeignclients 会扫描并将标注了 @FeignClient 注解的 Bean 注册成 FeignClientFactoryBean, 下面我们主要看看这个 FactoryBean 的能力.

FeignClientFactoryBean

上一节已经提到, @FeignClient 标识的接口会被 “根据 @EnableFeignClients 配置的规则扫描出来” 随后 “注册成 FeignClientFacotryBean(在 registerFeignClient 方法中)”.

补充说明
在 Spring 中,FactoryBean 是一个工厂 Bean,用来创建代理 Bean。工厂 Bean 是一种特殊的 Bean,对于需要获取 Bean 的消费者而言,它是不知道 Bean 是普通 Bean 或是工厂 Bean 的。工厂 Bean 返回的实例不是工厂 Bean 本身,而是会返回执行了工厂 Bean 中 FactoryBean#getObject 逻辑的实例.
来看看 FeignClientFactoryBean 对接口 FactoryBean#getObject() 的实现方式:

<T> T getTarget() {FeignContext context = applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);if (!StringUtils.hasText(url)) { // 查看@FeignClient的url是否为空if (!name.startsWith("http")) {url = "http://" + name;} else {url = name;}url += cleanPath();return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, 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 LoadBalancerFeignClient) {// not load balancing because we have a url, but ribbon is on the classpath, so unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url, but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate()}builder.client(client);}Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}

FeignContext

首先看第一行: applicationContext.getBean(FeignContext.class) 从 IOC 容器中获取 FeignContext 的实例. FeignContext 是在 OpenFeignStarter 的 spring.factories 被配置为自动注入的:
OpenFeign#1 - FeignClient 是如何注册的?
FeignAutoConfiguration 会将 FeignContext 注册到容器中, 同时将 "通过 registerClientConfiguration 注入的由用户指定的配置类(@EnableFeignClients 的 configuration 和 @FeignClient 的 configuration) (FeignClientSpecification) " 设值到 FeignContext 中:
OpenFeign#1 - FeignClient 是如何注册的?

feign(FeignContext)

接下来我们再回到 FeignClientFactoryBean 的 getTarget() 方法, feign(context) 负责组织 Feign.Builder:
首先, encoder, decoder 和 contract 由 @EnableFeignClients@FeignClient 注解属性 configuration 指定. 其中:

  • encoder: feign.codec.Encoder. 负责在当方法参数没有被标注 @Param 时, 将对象解析成 HTTP 请求体.
  • decoder: feign.codec.Decoder. 负责将 HTTP 响应解析成具体的返回值. 当状态码是 2xx, 并且返回类型不是 void 和 Response 时, 被调用.
  • contract: feign.Contract. 定义了接口能够识别的注解和值.
// org.springframework.cloud.openfeign.FeignClientFactoryBean#feign
protected Feign.Builder feign(FeignContext context) {FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);Logger logger = loggerFactory.create(type);Feign.Builder builder = get(context, Feign.Builder.class).logger(logger).encoder(get(context, Encoder.class)).decoder(get(context, Decoder.class)).contract(get(context, Contract.class));configureFeign(context, builder);return builder;
}

补充说明
org.springframework.cloud.openfeign.FeignClientFactoryBean#get
get(FeignContext, Class) 方法和其后续调用逻辑的核心在于基于 FeignContext (extends NamedContextFactory) 创建一个基于 contextId 的 AnnotationConfigApplicationContext, 进而注册当前 (contextId 指定) FeignContext 持有的 List 中对应的 FeignClient 的配置类及其中 @Bean, 见下图:
OpenFeign#1 - FeignClient 是如何注册的?
OpenFeign#1 - FeignClient 是如何注册的?
另外, FeignContext 也持有了所有已创建的 AnnotationConfigApplicationContext 与 contextId 的对应关系的 “缓存”:
OpenFeign#1 - FeignClient 是如何注册的?
整体调用流程如下:
OpenFeign#1 - FeignClient 是如何注册的?


接着, configureFeign(FeignContext, Feign.Builder), 其作用是根据属性或者配置类(@EnableFeignClients 的 configuration 和 @FeignClient 的 configuration) 配置 Feign.Builder:

// org.springframework.cloud.openfeign.FeignClientFactoryBean#configureFeign
protected void configureFeign(FeignContext context, Feign.Builder builder) {FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());if (properties != null && inheritParentContext) {if (properties.isDefaultToProperties()) {configureUsingConfiguration(context, builder);configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);configureUsingProperties(properties.getConfig().get(contextId), builder);} else {configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);configureUsingProperties(properties.getConfig().get(contextId), builder);configureUsingConfiguration(context, builder);}} else {configureUsingConfiguration(context, builder);}
}

重点关注 getOptional(context, FeignClientConfigurer.class) 子句, 跟进去:
1.org.springframework.cloud.openfeign.FeignClientFactoryBean#getOptional:

protected <T> T getOptional(FeignContext context, Class<T> type) {return context.getInstance(contextId, type);
}

2.org.springframework.cloud.context.named.NamedContextFactory#getInstance(java.lang.String, java.lang.Class<T>):

public <T> T getInstance(String name, Class<T> type) {AnnotationConfigApplicationContext context = getContext(name);try {return context.getBean(type);} catch (NoSuchBeanDefinitionException e) {}return null;
}

3.org.springframework.cloud.context.named.NamedContextFactory#getContext:

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

4.org.springframework.cloud.context.named.NamedContextFactory#createContext:

protected AnnotationConfigApplicationContext createContext(String name) {// AnnotationConfigApplicationContext 是一个 application context. 其继承了 GenericApplicationContext 的全部实现, 同时实现了 AnnotationConfigRegistry. 它额外扩展了能通过扫描 package 或是直接调用注册方法的方式来加载带有特定 Annotation Bean 的功能AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();if (this.configurations.containsKey(name)) {for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {context.register(configuration); // 注册配置类及其内部 @Bean}}for (Map.Entry<String, C> entry : this.configurations.entrySet()) {if (entry.getKey().startsWith("default.")) {for (Class<?> configuration : entry.getValue().getConfiguration()) {context.register(configuration); // 注册默认配置}}}context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName, Collections.<String, Object>singletonMap(this.propertyName, name)));if (this.parent != null) {// Uses Environment from parent as well as beanscontext.setParent(this.parent);context.setClassLoader(this.parent.getClassLoader());}context.setDisplayName(generateDisplayName(name));context.refresh(); // ! 刷新 AnnotationConfigApplicationContextreturn context;
}

结束, 现在我们知道了 FeignClient 是如何注册并应用配置类的.
下一篇, 我们讨论如何在 Starter 中注册 FeignClient: OpenFeign#2 - 在 Starter 中手动注册 FeignClient