Spring Cloudspring cloud

Spring Cloud Feign 分析(一)之FeignCl

2021-03-20  本文已影响0人  Blog

当我们使用@FeignClient作为客户端请求方式时,使用起来非常简单,启动类直接标注上@EnableFeignClients注解,然后就可以使用标注了@FeignClient注解的接口类进行请求,但是这个@FeignClient注解背后的原理是怎么样?@FeignClient是如何注册到Spring IOC容器中的?这一节我们带着这些疑问分析@FeignClient的注册过程!


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

一如继往先看看应用入口的配置类@EnableFeignClients这个注解做了什么事情,@Import(FeignClientsRegistrar.class)这个地方看起来就是在注册@FeignClient注解标注的对象,那么我们继续往下看看FeignClientsRegistrar又做了哪些事情。


FeignClientsRegistrar#registerDefaultConfiguration

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }
    //根据EnableFeignClients注解生成一个默认的Feign客户端配置
    private void registerDefaultConfiguration(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        Map<String, Object> defaultAttrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            }
            else {
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name,
                    defaultAttrs.get("defaultConfiguration"));
        }
    }
}

根据@EnableFeignClients注解中配置的defaultConfiguration属性配置,生成一个FeignClientSpecification对象,name="default." + 应用启动类全名路径


FeignClientsRegistrar#registerFeignClients

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    ......
    public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        //满足条件的Bean组件(候选名单集合)
        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        //获取EnableFeignClients注解属性
        Map<String, Object> attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        //获取EnableFeignClients注解中的clients属性
        final Class<?>[] clients = attrs == null ? null
                : (Class<?>[]) attrs.get("clients");
        //如果没有配置这个属性,则默认扫描项目根目录中标注了@FeignClient注解的类
        if (clients == null || clients.length == 0) {
            //创建扫描器
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            //设置扫描器的资源加载器
            scanner.setResourceLoader(this.resourceLoader);
            //设置扫描器需要扫描的注解类型
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            //获取扫描的路径,会从@EnableFeignClients注解中获取相关配置
            Set<String> basePackages = getBasePackages(metadata);
            for (String basePackage : basePackages) {
                //将扫描器中返回的BeanDefinition集合添加到candidateComponents集合中
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        }
        else {
            //如果配置了clients属性,则直接使用
            for (Class<?> clazz : clients) {
                candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
            }
        }
        for (BeanDefinition candidateComponent : candidateComponents) {
            //只处理带注解的BeanDefinition
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(),
                        "@FeignClient can only be specified on an interface");
                //获取@FeignClient注解的属性
                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(FeignClient.class.getCanonicalName());
                //获取@FeignClient注解客户端名称
                String name = getClientName(attributes);
                //注册FeignClientSpecification,Bean名称为FeignClient客户端名称
                registerClientConfiguration(registry, name,
                        attributes.get("configuration"));
                //注册@FeignClient的代理对象
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
    ......
}

从以上的代码片段以及注释信息我们不难看出,注册@FeignClient注解的过程比较复杂,大致分为了以下步骤:

  1. 获取@EnableFeignClients注解中的clients属性
  2. 如果配置了clients属性值则直接加入到candidateComponents这个候选名单集合中
  3. 如果没有获取到clients配置,则开始创建扫描器
  4. 设置扫描器资源加载属性、扫描器需要扫描的注解类型(@FeignClient注解)
  5. 从@EnableFeignClients注解属性中获取扫描的路径
  6. 根据扫描路径创建BeanDefinition集合并加入到候选名单集合中
  7. 根据@FeignClient注解中的客户端名称创建FeignClientSpecification
  8. 生成@FeignClient注解的代理对象

从registerFeignClients这个注册步骤中就能看见步骤多,而且复杂,其中在第八步中还需要创建@FeignClient的代理对象,那么我们就继续聊聊这个创建代理对象的过程!


FeignClientsRegistrar#registerFeignClients

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    ......
    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        //获取@FeignClient注解对应的类名
        String className = annotationMetadata.getClassName();
        //转换为Class对象
        Class clazz = ClassUtils.resolveClassName(className, null);
        ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
                ? (ConfigurableBeanFactory) registry : null;
        //获取上下文id,其实就是获取的@FeignClient注解的name、value属性值
        String contextId = getContextId(beanFactory, attributes);
        //获取的@FeignClient注解的name、value属性值
        String name = getName(attributes);
        //创建FeignClientFactoryBean代理对象
        FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
        //设置beanFactory、Name、ContextId、ClassType
        factoryBean.setBeanFactory(beanFactory);
        factoryBean.setName(name);
        factoryBean.setContextId(contextId);
        factoryBean.setType(clazz);
        //初始化BeanDefinitionBuilder,并设置fallback降级策略
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(clazz, () -> {
                    factoryBean.setUrl(getUrl(beanFactory, attributes));
                    factoryBean.setPath(getPath(beanFactory, attributes));
                    factoryBean.setDecode404(Boolean
                            .parseBoolean(String.valueOf(attributes.get("decode404"))));
                    Object fallback = attributes.get("fallback");
                    if (fallback != null) {
                        factoryBean.setFallback(fallback instanceof Class
                                ? (Class<?>) fallback
                                : ClassUtils.resolveClassName(fallback.toString(), null));
                    }
                    Object fallbackFactory = attributes.get("fallbackFactory");
                    if (fallbackFactory != null) {
                        factoryBean.setFallbackFactory(fallbackFactory instanceof Class
                                ? (Class<?>) fallbackFactory
                                : ClassUtils.resolveClassName(fallbackFactory.toString(),
                                        null));
                    }
                    //返回代理对象
                    return factoryBean.getObject();
                });
        //AutowireMode类型
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        //懒加载模式
        definition.setLazyInit(true);
        //校验fallback设置
        validate(attributes);
        //别名
        String alias = contextId + "FeignClient";
        //返回BeanDefinition定义
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
        beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
        //是否为主Bean
        boolean primary = (Boolean) attributes.get("primary");
        beanDefinition.setPrimary(primary);
        //获取@FeignClient是否设置了别名属性,若设置了就使用
        String qualifier = getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }
        //注册代理对象返回的BeanDefinition到Spring IOC容器
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                new String[] { alias });
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
    ......
}

生成一个代理对象,我们发现好像更复杂了,步骤也特别多了,这里也总结下大致的步骤:

  1. 获取@FeignClient注解对应的类名并转换为Class类对象
  2. 获取@FeignClient注解的name、value属性值
  3. 创建FeignClientFactoryBean代理对象并设置beanFactory、Name、ContextId、ClassType参数
  4. 创建BeanDefinitionBuilder对象并设置factoryBean代理对象的Url、Path、FallBack参数,并返回代理对象
  5. 设置BeanDefinitionBuilder对象为懒加载模式
  6. 通过BeanDefinitionBuilder获取beanDefinition对象并设置beanDefinition相关的属性,如对象类型className,是否为主Bean
  7. 注册代理对象返回的BeanDefinition到Spring IOC容器中,如果设置了别名则使用@FeignClient注解中的别名值

这一章节我们讲解了FeignClient注册过程,这个过程中会为每一个@FeignClient对象都生成一个代理类对象,步骤比较多,这里也只是讲解了一部分,比如在factoryBean.getObject()这个返回代理对象的具体逻辑中还会涉及到Hystrix熔断、LoadBalance负载均衡相关的,这些逻辑会放在后面调用过程中进行讲解!

上一篇下一篇

猜你喜欢

热点阅读