关于nacos:Nacos-Feign调用研究

30次阅读

共计 8446 个字符,预计需要花费 22 分钟才能阅读完成。

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 通信。

正文完
 0