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