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


    前言#

    打算系统分析下Feign的代码,上一篇讲了下Feign的历史,本篇的话,先讲下Feign相关的beanDefinition,beanDefinition就是bean的设计图,bean都是按照beanDefinition来制造的。

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

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

    EnableFeignClients引入的FeignClientSpecification#

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

    Copy
    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为例,该注解的定义如下:

    Copy
    @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients

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

    Copy
    org.springframework.cloud.openfeign.FeignClientsRegistrar public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 1 registerDefaultConfiguration(metadata, registry); // 2 registerFeignClients(metadata, registry); }

    上面的1处,如下:

    Copy
    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类很简单,属性就两个:

    Copy
    public class FeignClientSpecification implements NamedContextFactory.Specification { private String name; private Class<?>[] configuration; }

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

    Copy
    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.

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

    Copy
    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#

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

    Copy
    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等

    Copy
    public @interface FeignClient { String value() default ""; Class<?>[] configuration() default {}; }

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

    以如下为例:

    Copy
    @FeignClient(name = "demoService") public interface DemoServiceFeignClient

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

    Copy
    public class FeignClientSpecification implements NamedContextFactory.Specification { private String name; private Class<?>[] configuration; }

    如果指定configuration属性:

    Copy
    @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

    Copy
    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:

    image-20231216211043164

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




  • 相关阅读:
    JavaBean专项练习(学生信息管理系统)(增删改查)(每步详细解释和思路)
    微信录屏怎么录?微信聊天记录怎么录制下来
    什么是信息摘要?
    【mitmproxy】一、简介与快速上手
    PyTorch人脸识别
    GRS认证里 “收货人问题” 的最新解读
    张益唐的朗道-西格尔零点猜想的论文公布,专家认为该论文尚未完整解决零点猜想
    pytest框架二次开发
    Language Adaptive Weight Generation for Multi-task Visual Grounding 论文阅读笔记
    java每日一题:static与final的区别
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/17908400.html