FeignClient调用原理

要启用FeignClient首先必须在启动类上加上注解@EnableFeignClients,EnableFeignClients代码如下

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

注意到注解@Import(FeignClientsRegistrar.class),FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,在启动时会执行registerBeanDefinitions动态注册

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

我们首先看registerDefaultConfiguration,代码不多,直接贴代码

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"));
		}
	}
  • 获取配置信息defaultAttrs
  • 注册默认配置类信息,配置类从defaultConfiguration中获取并且名称为 "default." + metadata.getClassName() 比如启动类为TestApplication,那么名称为default.com.test.TestApplication
registerClientConfiguration将配置信息注册为FeignClientSpecification
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());
	}
FeignClientSpecification其实就是一个key-value结构体,key就是配置名称,value就是配置类
FeignClientSpecification(String name, Class<?>[] configuration) {
		this.name = name;
		this.configuration = configuration;
	}

EnableFeignClients注解中参数defaultConfiguration为全局配置类,如果FeignClient没有配置则会获取defaultConfiguration的配置,defaultConfiguration可以配置为任意类,比如

@EnableFeignClients(defaultConfiguration = GlobalFeignClientConfiguration.class)

GlobalFeignClientConfiguration该如何指定配置呢?我们知道可以通过feign.Builder来手动创建FeignClient,在feign.Builder中有以下变量

public static class Builder {
    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 FieldQueryMapEncoder();
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    private Options options = new Options();
  ...
}

这些都可以通过defaultConfiguration重新定义,比如下面这个指定了Loger.Level

public class GlobalFeignClientConfiguration {
    @Bean
    public Level level() {
        return Level.FULL;
    }
}

当然也可以通过配置文件进行配置

feign:
  client:
    config:
      feignName:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full
        errorDecoder: com.example.SimpleErrorDecoder
        retryer: com.example.SimpleRetryer
        requestInterceptors:
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        decode404: false
        encoder: com.example.SimpleEncoder
        decoder: com.example.SimpleDecoder
        contract: com.example.SimpleContract
  • registerFeignClients

registerFeignClients先扫描所有带注解FeignClient的类,并注册到Spring容器中

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		//...扫描FeignClient类(部分代码省略)
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
        
        //... 注册Feign客户端(部分代码省略)
				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());
				String name = getClientName(attributes);
				registerClientConfiguration(registry, name,
						attributes.get("configuration"));
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

会将Client注册为FeignClientFactoryBean,这样Spring Boot在获取Feign实例时就会调用FeignClientFactoryBean.getTarget方法

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		//...省略部分代码
  	AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

最终会通过targeter.target创建一个代理对象

<T> T getTarget() {
		FeignContext context = applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
  	//...省略部分代码
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(type, name, url));
	}

创建代理对象的代码位于feign.Feign.target中,至此整个Feign从初始化到最终实例化就全部完成

Nacos FeignClient调用原理

如果使用Nacos作为注册中心,只要在依赖中添加nacos的starter并且在配置文件中指定nacos的地址就接入完成

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>

接入nacos后,Feign调用将从nacos配置中心获取获取服务信息,比较重要的就是服务的地址和端口,那么这一切时如何实现的,我们就从spring-cloud-starter-alibaba-nacos-discovery开始,spring-cloud-starter-alibaba-nacos-discovery本身没有包含任何的代码(不知道为何这样设计),但依赖了spring-cloud-alibaba-nacos-discovery

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
</dependencies>

这里面包含starter相关的代码,spring.factories定义如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.alibaba.cloud.nacos.NacosDiscoveryAutoConfiguration,\
  com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\
  com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
  com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientAutoConfiguration,\
  com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration

我们这里关注NacosDiscoveryClientConfigServiceBootstrapConfiguration

@ConditionalOnClass(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration
@ImportAutoConfiguration({ NacosDiscoveryClientAutoConfiguration.class,
		NacosDiscoveryAutoConfiguration.class })
public class NacosDiscoveryClientConfigServiceBootstrapConfiguration {
}

这里使用了注解ImportAutoConfiguration,该注解会自动导入配置的类。 我们进入NacosDiscoveryClientAutoConfiguration

@Configuration
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class,
		CommonsClientAutoConfiguration.class })
public class NacosDiscoveryClientAutoConfiguration {
	@Bean
	public DiscoveryClient nacosDiscoveryClient(
			NacosDiscoveryProperties discoveryProperties) {
		return new NacosDiscoveryClient(discoveryProperties);
	}
  //...省略部分代码
}

这里会注入一个NacosDiscoveryClient对象,该对象实现接口DiscoveryClient。DiscoveryClient在spring cloud中负责从注册中心获取服务列表,也就是说底层Feign并不是直接和注册中心打交道,而是通过DiscoveryClient发现服务,那么只要实现了DiscoveryClient就能实现自定义注册中心。

NacosDiscoveryClient.java
public class NacosDiscoveryClient implements DiscoveryClient {
	private static final Logger log = LoggerFactory.getLogger(NacosDiscoveryClient.class);
	public static final String DESCRIPTION = "Spring Cloud Nacos Discovery Client";
	private NacosDiscoveryProperties discoveryProperties;
	public NacosDiscoveryClient(NacosDiscoveryProperties discoveryProperties) {
		this.discoveryProperties = discoveryProperties;
	}
	@Override
	public String description() {
		return DESCRIPTION;
	}
	@Override
	public List<ServiceInstance> getInstances(String serviceId) {
		try {
			String group = discoveryProperties.getGroup();
			List<Instance> instances = discoveryProperties.namingServiceInstance()
					.selectInstances(serviceId, group, true);
			return hostToServiceInstanceList(instances, serviceId);
		}
		catch (Exception e) {
			throw new RuntimeException(
					"Can not get hosts from nacos server. serviceId: " + serviceId, e);
		}
	}
  //...省略部分代码
}

以获取服务实例getInstances为例,discoveryProperties.namingServiceInstance()获取了一个NamingService对象,这个对象包含nacos api所有的操作,有了这个对象就能和nacos通信。

,
Trackback

no comment untill now

Add your comment now