要启用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
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
candidateComponents = new LinkedHashSet<>(); - Map
attrs = metadata - .getAnnotationAttributes(EnableFeignClients.class.getName());
- AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
- FeignClient.class);
- //…扫描FeignClient类(部分代码省略)
- for (BeanDefinition candidateComponent : candidateComponents) {
- if (candidateComponent instanceof AnnotatedBeanDefinition) {
- //… 注册Feign客户端(部分代码省略)
- Map
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
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 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的地址就接入完成
查看文本复制打印?
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery 2.1.1.RELEASE
接入nacos后,Feign调用将从nacos配置中心获取获取服务信息,比较重要的就是服务的地址和端口,那么这一切时如何实现的,我们就从spring-cloud-starter-alibaba-nacos-discovery开始,spring-cloud-starter-alibaba-nacos-discovery本身没有包含任何的代码(不知道为何这样设计),但依赖了spring-cloud-alibaba-nacos-discovery
查看文本复制打印?
com.alibaba.cloud spring-cloud-alibaba-nacos-discovery org.springframework.boot spring-boot-starter
这里面包含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
getInstances(String serviceId) { - try {
- String group = discoveryProperties.getGroup();
- List
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通信。