Feign源码深度刨析-(3)核心组件:FeignClientFactoryBean(上)

“不积跬步,无以至千里。”

前面说了,@EnableFeignClients 这个注解往容器中导入了一个组件 FeignClientsRegistrar ,这个组件实现了 ImportBeanDefinitionRegistrar接口,那么它的 registerBeanDefinitions() 方法就会往spring容器中导入一些组件

registerFeignClients(metadata, registry);

这个方法会搞一个组件扫描器ClassPathScanningCandidateComponentProvider,专门扫描我们自己写的Feign客户端的接口,即添加了@FeignClient注解的接口

扫描之后,会放进一个Set集合中,然后遍历这个集合,依次调用registerFeignClient()方法将其注册到spring容器中

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);
    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 = name + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

    boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

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

可以看到,方法并不长

BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);

上来就看到一段极为特别的代码,FeignClientFactoryBean,这是一个工厂Bean,在Spring中是比较特殊的一个组件,相当于把扫描出来的我们自己写的那些打上了@FeignClient注解的接口,全部都注册为了一个工厂Bean,后续使用的时候直接调用getObject()方法即可,你觉得这个方法会在什么时候被调用呢?当然是给Controller组件DI(依赖注入)的时候!!!这个我们后续会去验证

然后下面的一系列addXXX()方法,就是读取@FeignClient注解里面的属性放进Bean的定义信息里面的一个过程

比如 definition.addPropertyValue(“name”, name); 就是把服务名称给进bean的定义信息里面去

然后 getBeanDefinition(),成功拿到一个 AbstractBeanDefinition,其实没啥好说的,spring基操,这里面就包含了服务名称等我们配置在 @FeignClient 注解里面的参数

最后封装成一个 BeanDefinitionHolder,调用一个spring的工具类给注册到容器中,BeanName就是类全限定名。

后续Controller中,需要使用Feign客户端的时候,只需要声明一个接口类即可,再使用@Autowired注解,就会把动态代理注入到Controller的属性中,这个叫DI,依赖注入,因此在实例化Controller组件的时候会去调用我们这个FeignClientFactoryBeangetObject()方法,获取代理对象

@Override
public Object getObject() throws Exception {
    return getTarget();
}

getTarget(),获取代理对象的逻辑都在这个方法里面

/**
  * @param <T> the target type of the Feign client
  * @return a {@link Feign} client created with the specified data and the context information
  */
<T> T getTarget() {
    FeignContext context = applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);

    if (!StringUtils.hasText(this.url)) {
        String url;
        if (!this.name.startsWith("http")) {
            url = "http://" + this.name;
        }
        else {
            url = this.name;
        }
        url += cleanPath();
        return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                                                                       this.name, url));
    }
    if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
        this.url = "http://" + this.url;
    }
    String url = this.url + cleanPath();
    Client client = getOptional(context, Client.class);
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            // not load balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient)client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
        this.type, this.name, url));
}

@param the target type of the Feign client,返回值类型就是FeignClient的类型,这是肯定的,不然没法赋值给Controller的属性

FeignContext context = applicationContext.getBean(FeignContext.class);

这行代码啥意思,还记得之前写ribbon的时候有说过,一个被调服务对应一个spring容器,容器里面包含了那个服务的自己的组件,ILoadBalancer、IRule等等,这里也是一样的,每个FeignContext也是代表一个被调服务的独立容器,里面包含自己的一些组件,比如编码器解码器,日志组件,注解解释器等等

然后下面调用了一个feign()方法拿到一个Feign.Builder

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

FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);

这里就是从被调服务对应的spring容器中获取属于自己的 FeignLoggerFactory 组件

protected <T> T get(FeignContext context, Class<T> type) {
    T instance = context.getInstance(this.name, type);
    if (instance == null) {
        throw new IllegalStateException("No bean found of type " + type + " for "
                                        + this.name);
    }
    return instance;
}

instance就是当前服务对应的spring容器里面的FeignLoggerFactory组件,而getInstance()方法就是从容器中那组件,this.name就是服务名称,type就是FeignLoggerFactory.class,即组件的类型

public <T> T getInstance(String name, Class<T> type) {
    AnnotationConfigApplicationContext context = getContext(name);
    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                                                            type).length > 0) {
        return context.getBean(type);
    }
    return null;
}

接着走, Logger logger = loggerFactory.create(this.type);

拿到一个Logger组件,就是用来记录feign调用过程中产生的日志的,默认使用的是Slf4jLogger

Feign.Builder builder = get(context, Feign.Builder.class)

然后从spring容器中获取一个Feign.Builder组件,这是比较关键的一行代码,这个组件肯定也是通过自动配置类去注入到容器中的

org.springframework.cloud.openfeign 这个包下面找到了一个 FeignClientsConfiguration,发现就是说这里面配置了大量的组件,就包括了这个Feign.Builder

@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
    return HystrixFeign.builder();
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
    return Feign.builder().retryer(retryer);
}

会发现说,这里找到了两个Builder,一个是HystrixFeign,这个很明显是跟Hystrix整合的时候使用的

另外一个就是默认的Builder了,只不过整合了一个retryer,就是一个重试的机制,默认使用的是这个,

除非你在配置文件中有feign.hystrix.enabled相关的配置

.logger(logger)

.encoder(get(context, Encoder.class))

.decoder(get(context, Decoder.class))

.contract(get(context, Contract.class));

接着分别把logger、encoder、decoder、contract组件注入到这个Feign.Builder中

当然也都在从这个被调服务对应的spring容器中获取的

logger默认使用的是Slf4jLogger

encoder默认使用的是SpringEncoder

decoder默认使用的是ResponseEntityDecoder

contract默认使用的是SpringMvcContract

全部都是在FeignClientsConfiguration这个配置类配置了,之前我们在feign的初探章节给大家说了默认组件,这次再带着源码看一下,验证我们的结论,我写的博文,每一篇,每一字,全都的结论都是来源于源码,源码见真知!平时遇到问题,也尽量有能力的话,去翻翻源码,不要“面向百度编程”,不太好,培养不出来能力

@Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {
    return new DefaultFeignLoggerFactory(logger);
}
@Override
public Logger create(Class<?> type) {
    return this.logger != null ? this.logger : new Slf4jLogger(type);
}
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
    return new SpringEncoder(this.messageConverters);
}
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
    return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
    return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}

ok,总结一下,扫描器扫描出来我们打了@FeignClient注解的接口,会把他们注册为l类型为FeignClientFactoryBean的工厂Bean,然后在适当的时候(实例化Controller组件的时候,DI)调用getObject()方法获取,这篇文章主要在说FeignClientFactoryBean这个组件怎么获取一个FeignClient的动态代理,我控制一下篇幅,不要太长,不然容易阅读疲劳,下一篇文章继续讲动态代理的构建流程。

最后,也是稍微多说一点,其实写这种源码分析的博客,说简单很简单,按照自己的思路,胡乱的粘贴一些东西,写出来,也花不了多少时间,但最后写完,仅自己看得懂,也许同样深入研究过那个源码的大佬,看得懂,对于大多数朋友是不友好的,也得不到反馈

想要真正写的很透彻,高质量的文章,也没那么容易

首先,需要深入理解这个技术的核心源码,要做到全局性的把握

其次,要学会抓大放小,源码里面细枝末节的东西太多,什么都写,没有深度,要有自己先过滤一下,把重点的东西展示出来,还要分析的深度恰如其分,还不能分析的过于的细节,容易让读者一头雾水,抓不住重点;

最后,还要把难点写明白,自己理解是一码事,让别人看懂是一码事,需要反复审稿,尽量做到通俗易懂。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值