• SpringCloud: FeignClient原理解析



    Feign简介

    Feign 是一个 Java 到 HTTP 的客户端绑定器,Feign 通过将注解处理为模板化请求来工作。参数在输出之前直接应用于这些模板。尽管 Feign 仅限于支持基于文本的 APIs,但它极大地简化了系统方面,例如重放请求。此外,Feign 使得对转换进行单元测试变得简单。

    FeignClient处理过程

    在这里插入图片描述

    想要开启FeignClient,首先要素就是添加@EnableFeignClients注解。其主要功能是初始化FeignClient的配置和动态执行client的请求。

    @SpringBootApplication
    @EnableFeignClients
    public class CrmApplication {
       public static void main(String[] args) {     
           SpringApplication.run(CrmApplication.class, args);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    EnableFeignClients的源代码,其核心是

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(FeignClientsRegistrar.class)
    public @interface EnableFeignClients {
        String[] value() default {};
        String[] basePackages() default {};
        Class<?>[] basePackageClasses() default {};
        Class<?>[] defaultConfiguration() default {};
        Class<?>[] clients() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其中@Import(FeignClientsRegistrar.class)是用来初始化FeignClient配置的。我们接着看其代码,找到核心实现代码

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            registerDefaultConfiguration(metadata, registry);
            registerFeignClients(metadata, registry);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    FeignClientsRegistrar 实现了ImportBeanDefinitionRegistrar, ImportBeanDefinitionRegistrar提供了动态注册bean的能力,等于说除了spring本身的一般的通过配置文件注册bean的流程,开放了一个hook的口子,让用户自己去注册bean。所有实现了该接口的类的都会被ConfigurationClassPostProcessor处理,

    ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖其的bean初始化的,也能被aop、validator等机制处理。

    registerDefaultConfiguration(metadata, registry)是用来加载@EnableFeignClients中的defaultConfiguration和@FeignClient中的configuration配置文件。

    registerDefaultConfiguration(metadata, registry)方法用来注册默认配置,如在EnableFeignClients里面配置的defaultConfiguration属性,如果配置了,就会进行加载注册,registerDefaultConfiguration会将其配置信息封装成一个FeignClientSpecification注册到容器中,该对象非常重要,包含FeignClient需要的重试策略,超时策略,日志等配置,如果某个服务没有设置,则读取默认的配置。

    registerDefaultConfiguration(metadata, registry)是用来加载@EnableFeignClients中的defaultConfiguration和@FeignClient中的configuration配置文件。代码实现代码比较简单,不再细说。

    registerFeignClients(metadata, registry)是用来加载@EnableFeignClients中的其他配和@FeignClient中的其他配置。这是该文章要说的重点。

        private void registerFeignClient(BeanDefinitionRegistry registry,
                AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
            String className = annotationMetadata.getClassName();
            BeanDefinitionBuilder definition = BeanDefinitionBuilder
                    .genericBeanDefinition(FeignClientFactoryBean.class);
            validate(attributes);
            definition.addPropertyValue("url", getUrl(attributes));
            definition.addPropertyValue("path", getPath(attributes));
            String name = getName(attributes);
            definition.addPropertyValue("name", name);
            String contextId = getContextId(attributes);
            definition.addPropertyValue("contextId", contextId);
            definition.addPropertyValue("type", className);
            definition.addPropertyValue("decode404", attributes.get("decode404"));
            definition.addPropertyValue("fallback", attributes.get("fallback"));
            definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    
            String alias = contextId + "FeignClient";
            AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    
            boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
             beanDefinition.setPrimary(primary);
            String qualifier = getQualifier(attributes);
            if (StringUtils.hasText(qualifier)) {
                alias = qualifier;
            }
            BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                    new String[] { alias });
            BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    **registerFeignClients(metadata, registry)**方法主要是扫描类路径,对所有的FeignClient生成对应的 BeanDefinition,其中又执行了,registerClientConfiguration(registry, name,attributes.get(“configuration”)); 这个方法是对每个feignClient自己配置的configuration进行注册。而 registerFeignClient(registry, annotationMetadata, attributes);方法会将定义的feignClient注册到容器中。跟进去可以看到 BeanDefinitionBuilder definition = BeanDefinitionBuilder
    .genericBeanDefinition(FeignClientFactoryBean.class); spring会将FeignClient注册成一个FeignClientFactoryBean.class,这个是最关键的,注册成了一个FactoryBean而不是普通的bean。

    该初始化是对FeignClientFactoryBean的初始化,接着我们进入FeignClientFactoryBean的代码中

        protected Feign.Builder feign(FeignContext context) {
            FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
            Logger logger = loggerFactory.create(this.type);
            // @formatter:off
            Feign.Builder builder = get(context, Feign.Builder.class)
                    // required values
                    .logger(logger)
                    .encoder(get(context, Encoder.class))
                    .decoder(get(context, Decoder.class))
                    .contract(get(context, Contract.class));
            // @formatter:on
            configureFeign(context, builder);
            return builder;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    该段代码就是动态实现FeignClient的基本逻辑,从这里可以看到,它实现了下面几个组件:Feign.Builder、logger、encoder、decoder和contract。
    我们先继续看configureFeign(context, builder)的代码

        protected void configureFeign(FeignContext context, Feign.Builder builder) {
            FeignClientProperties properties = this.applicationContext
                    .getBean(FeignClientProperties.class);
            if (properties != null) {
                if (properties.isDefaultToProperties()) {
                    configureUsingConfiguration(context, builder);
                    configureUsingProperties(
                            properties.getConfig().get(properties.getDefaultConfig()),
                            builder);
                    configureUsingProperties(properties.getConfig().get(this.contextId),
                            builder);
                }
                else {
                    configureUsingProperties(
                            properties.getConfig().get(properties.getDefaultConfig()),
                            builder);
                    configureUsingProperties(properties.getConfig().get(this.contextId),
                            builder);
                    configureUsingConfiguration(context, builder);
                }
            }
            else {
                configureUsingConfiguration(context, builder);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    其中configureUsingConfiguration(…)是使用我们定义的属性去更新Feign.Builder;configureUsingProperties是用我们定义的default属性去更新Feign.Builder。
    继续看configureUsingConfiguration(…)

        protected void configureUsingConfiguration(FeignContext context,
                Feign.Builder builder) {
            Logger.Level level = getOptional(context, Logger.Level.class);
            if (level != null) {
                builder.logLevel(level);
            }
            Retryer retryer = getOptional(context, Retryer.class);
            if (retryer != null) {
                builder.retryer(retryer);
            }
            ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
            if (errorDecoder != null) {
                builder.errorDecoder(errorDecoder);
            }
            Request.Options options = getOptional(context, Request.Options.class);
            if (options != null) {
                builder.options(options);
            }
            Map<String, RequestInterceptor> requestInterceptors = context
                    .getInstances(this.contextId, RequestInterceptor.class);
            if (requestInterceptors != null) {
                builder.requestInterceptors(requestInterceptors.values());
            }
            QueryMapEncoder queryMapEncoder = getOptional(context, QueryMapEncoder.class);
            if (queryMapEncoder != null) {
                builder.queryMapEncoder(queryMapEncoder);
            }
            if (this.decode404) {
                builder.decode404();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    这段代码的逻辑是从对应的context中分别查找logLevel、retryer、errorDecoder、options、requestInterceptors、queryMapEncoder、decode404等组件,然后重新初始化Feign.Builder,从而达到定制FeignClient的目的。

    上面是FeignClient的注册的过程,那什么时候实例化呢,当然是在我们getBean的时候,发现是个FeignClientFactoryBean,那spring会调用getObject()来返回一个对象。所以接下来我们只需要看FeignClientFactoryBean.class的getObject()是如何的返回对象就可以了。

  • 相关阅读:
    CSS-DAY1
    Linux 救援模式
    现代密码学-数字签名
    一分钟学会如何使用 git 将本地文件上传至Coding代码库
    [附源码]java毕业设计音乐交流平台
    问题随记 —— PyCharm 连接远程服务器的 Python 环境
    day5:Node.js 第三方库
    微服务系列之分布式日志 ELK
    数据结构与算法设计分析——贪心算法的应用
    无涯教程-JavaScript - ATAN函数
  • 原文地址:https://blog.csdn.net/zhanggqianglovec/article/details/126137765