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
就能实现自定义注册中心。
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通信。
no comment untill now