Nacos Feign调用研究
Posted wls1036
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nacos Feign调用研究相关的知识,希望对你有一定的参考价值。
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通信。
以上是关于Nacos Feign调用研究的主要内容,如果未能解决你的问题,请参考以下文章
商城项目04_SpringCloud Alibaba概述Nacos作为注册配置中心声明式远程调用Feign
教程SpringCloud+Nacos+Feign+Gateway搭建教程(推荐)