前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Feign源码解析:初始化过程(一)

Feign源码解析:初始化过程(一)

作者头像
低级知识传播者
发布2023-12-18 15:39:59
2110
发布2023-12-18 15:39:59
举报

前言

打算系统分析下Feign的代码,上一篇讲了下Feign的历史,spring cloud生态中Feign、Ribbon、loadbalancer的一些历史

本篇的话,先讲下Feign相关的beanDefinition,beanDefinition就是bean的设计图,bean都是按照beanDefinition来制造的。

Feign相关的bean不少,有一些是因为我们的Feign相关注解而引入的,有一部分是因为spring的自动装配来自动引入的。

今天讲讲因为我们注解引入的那些。

EnableFeignClients引入的FeignClientSpecification

如果项目用到Feign,在@SpringBootApplication注解的主类上,我们一般还会加上@EnableFeignClients注解。

代码语言:javascript
复制
package a.b.c;
    
@SpringBootApplication
@EnableFeignClients
public class DemoApplication

实际上,spring的beanFactory初始化一般就是分两步,第一步,收集beanDefinition列表,第二步,根据beanDefinition生成并初始化bean。

收集beanDefinition相当重要,但是beanDefinition从哪来来呢,一开始的时候,都是框架默认注册了几个,应用自身的beanDefinition一般要从主类来,也就是上面说的@SpringBootApplication注解的主类。

一开始就会去解析主类上的注解,包名;解析包名的原因是,拿到包名后,默认就会去扫描这个包名下的class,再找到注解了@configuration、@controller、@service、@component之类的bean作为beanDefinition;解析注解的原因是,可以根据注解,引入更多的beanDefinition。

以@EnableFeignClients为例,该注解的定义如下:

代码语言:javascript
复制
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients

所以这里会通过Import引入更多的beanDefinition。

代码语言:javascript
复制
org.springframework.cloud.openfeign.FeignClientsRegistrar

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 1
    registerDefaultConfiguration(metadata, registry);
    // 2
    registerFeignClients(metadata, registry);
}

上面的1处,如下:

代码语言:javascript
复制
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
String name = "default." + metadata.getClassName();
registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
    builder.addConstructorArgValue(name);
    builder.addConstructorArgValue(configuration);
    registry.registerBeanDefinition(builder.getBeanDefinition());
}

主要是获取@EnableFeignClients这个注解相关的默认属性,然后注册了一个FeignClientSpecification类型的bean。

这个FeignClientSpecification类很简单,属性就两个:

代码语言:javascript
复制
public class FeignClientSpecification implements NamedContextFactory.Specification {

 private String name;

 private Class<?>[] configuration;
}    

一个name,一个configuration,其实就是代表了一个要如何创建和配置FeignClient的配置,包含了如何创建feign的encoder、decoder等。

代码语言:javascript
复制
A custom @Configuration for all feign clients. Can contain override @Bean definition for the pieces that make up the client, for instance feign.codec.Decoder, feign.codec.Encoder, feign.Contract.

我们举个例子,如下的代码,

代码语言:javascript
复制
package a.b.c;
    
@SpringBootApplication
@EnableFeignClients
public class DemoApplication

最终生成的FeignClientSpecification beanDefinition,beanName为:default.a.b.c.DemoApplication.FeignClientSpecification,属性:

name:default.a.b.c.DemoApplication

configuration:空数组

FeignClient注解引入的FeignClientSpecification

说完这个,继续说如下的二处:

代码语言:javascript
复制
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 1
    registerDefaultConfiguration(metadata, registry);
    // 2
    registerFeignClients(metadata, registry);
}

2处内部,是首先获取@EnableFeignClients注解的类所在的包名,然后在这个包名下扫描注解了@FeignClient的类,扫描到的这些类,那肯定是弄了一个beanDefinition了,这个可以倒推,毕竟每个@FeignClient注解的类最终都变成了一个bean嘛。

里面有一点值得讲,FeignClient注解中有一个属性,叫configuration,它支持你指定一个class,里面可以覆盖FeignClient内部的部分组件,如Feign的Encoder等

代码语言:javascript
复制
public @interface FeignClient {
    String value() default "";
    Class<?>[] configuration() default {};
}    

这个你指定的配置类,是可以被多个FeignClient复用的,所以,spring内部也是只会存一份,这一份配置,就通过一个创建一个类型为FeignClientSpecification.class的bean来保存。

以如下为例:

代码语言:javascript
复制
@FeignClient(name = "demoService")
public interface DemoServiceFeignClient

由于没有指定configuration属性,这里生成的FeignClientSpecification bean中,name就是demoService,configuration就是null:

代码语言:javascript
复制
public class FeignClientSpecification implements NamedContextFactory.Specification {

 private String name;

 private Class<?>[] configuration;
}

如果指定configuration属性:

代码语言:javascript
复制
@FeignClient(name = "demoService", configuration = CustomConfig.class)
public interface DemoServiceFeignClient

FeignClientSpecification中的configuration就会是CustomConfig.class。

这个FeignClientSpecification.class的bean的名字,默认则会是demoService.FeignClientSpecification。

FeignClient对应的beanDefinition

接下来,就是注册一个FeignClient对应的beanDefinition了。

详细的源码可以看这里:org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient

FeignClient的bean是较难创建的,所以这里是用了工厂bean:FeignClientFactoryBean

代码语言:javascript
复制
String name = getName(attributes);

FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
    factoryBean.setUrl(getUrl(beanFactory, attributes));
    factoryBean.setPath(getPath(beanFactory, attributes));
    ...

    return factoryBean.getObject();
});

BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

这里的beanname,一般也就是FeignClient的全路径名。

汇总

最终其实就引入了三个bean:

首先是由@enableFeignClients引入的FeignClientSpecification,然后是@enableFeignClients注解所在的包名下的由@FeignClient注解引入的FeignClientSpecification,再一个就是FeignClient本身的bean(一个factoryBean)

本文参与?腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-16,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 低级知识传播者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体同步曝光计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • EnableFeignClients引入的FeignClientSpecification
  • FeignClient注解引入的FeignClientSpecification
  • FeignClient对应的beanDefinition
  • 汇总
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com