SpringCloud-FeignClient原理解析

Feign简介

Feign是 Spring Cloud 提供的⼀个声明式的伪 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);
   }
}

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 {};
}


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

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }

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);
    }

**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;
    }

该段代码就是动态实现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);
        }
    }

其中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();
        }
    }

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

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

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值