14、@EnableFeignClients注解源码分析

前言

在Cloud 中使用@EnableFeignClients启用Feign客户端,接下来分析下这个注解的基本原理。

作用

@EnableFeignClients注解会扫描包路径下的@FeignClient注解定义的接口,并注册到IOC容器中。

配置属性

配置属性主要是配置扫描路径:

@Retention(RetentionPolicy.RUNTIME)
@Target({

     ElementType.TYPE})
@Documented
@Import({

     FeignClientsRegistrar.class})
public @interface EnableFeignClients {

    // 配置扫描@FeignClient的路径,默认是当前标识了该注解类下的路径,和@ComponentScans一样
    String[] value() default {

     };
    // 扫描路径,多个
    String[] basePackages() default {

     };
    // 指定扫描某个类所在包下的所有类
    Class<?>[] basePackageClasses() default {

     };
    // 指定客户端配置配
    Class<?>[] defaultConfiguration() default {

     };
    // 直接指定扫描客户端的类,配置了就不会扫描
    Class<?>[] clients() default {

     };
}

FeignClientsRegistrar

@EnableFeignClients注解中,使用了@Import注解导入了FeignClientsRegistrar,学过Spring 的应该知道@Configuration类上使用@Import,可以注入Bean 对象。

FeignClientsRegistrar实现了以下几个接口:

  • ImportBeanDefinitionRegistrar:支持使用@Import注解注册 BeanDefinition
  • ResourceLoaderAware:读取文件
  • EnvironmentAware:获取环境及配置属性

启动项目后,进行BeanDefinition注册时,就会进入到registerBeanDefinitions方法,该方法会注册默认配置和注册Feign 客户端。

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

        this.registerDefaultConfiguration(metadata, registry);
        this.registerFeignClients(metadata, registry);
    }

registerDefaultConfiguration会进行注册客户端配置类的BeanDefinition

    // 参数为EnableFeignClients注解配置的类的信息、BeanDefinition注册器
    private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

        // 1. 获取EnableFeignClients注解上的配置属性
        Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {

            // 取个名字=》default.account.AccountApp
            String name;
            if (metadata.hasEnclosingClass()) {

                name = "default." + metadata.getEnclosingClassName();
            } else {

                name = "default." + metadata.getClassName();
            }
            // 2. 注册客户端配置
            this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
        }

    }

接着进入到registerClientConfiguration,注册客户端配置,会在注册器中添加一个名字为default.account.AccountApp.FeignClientSpecification,类型为FeignClientSpecificationBeanDefinition

    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {

        // 创建一个 BeanDefinition构建者,使用FeignClientSpecification(这里存放了客户端配置名称和配置类)
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
        // 添加FeignClientSpecification 构造函数的参数
        // name => default.account.AccountApp
        // configuration=> 注解上的配置类,这里没有
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        // 注册,名称为=》default.account.AccountApp.FeignClientSpecification
        // 类型为=》FeignClientSpecification
        registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
    }

接着注册Feign 客户端,会调用扫描器,扫描类路径下的@FeignClient标记的类,然后读取配置信息,将该类的配置项、类信息都注册到注册器中。

    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

        // BeanDefinition 不重复集合
        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet();
        // EnableFeignClients 注解配置信息
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        new AnnotationTypeFilter(FeignClient.class);
        Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
        // 查看是否配置了clients 属性,看来这个属性配置了以后,就不会进行包扫描
        if (clients != null && clients.length != 0) {

            Class[] var13 = clients;
            int var15 = clients.length;

            for(int var17 = 0; var17 < var15; ++var17) {

                Class<?> clazz = var13[var17];
                candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
            }
        } else {

            // 创建一个ClassPath扫描器
            ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
            // 设置ResourceLoader
            scanner.setResourceLoader(this.resourceLoader);
            // 设置需要扫描的类,也就是配置了FeignClient 注解的类
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            // 获取配置上的 这里没配置,就是扫描当前项目包下面的了
            Set<String> basePackages = this.getBasePackages(metadata);
            Iterator var9 = basePackages.iterator();
            // 扫描到Feign 接口类,放入集合中
            while(var9.hasNext()) {

                String basePackage = (String)var9.next();
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        }

        Iterator var14 = candidateComponents.iterator();

        while(var14.hasNext()) {

            // 循环扫描到的BeanDefinition  
            BeanDefinition candidateComponent = (BeanDefinition)var14.next();
            if (candidateComponent instanceof AnnotatedBeanDefinition) {

                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;
                // FeignClient注解上的元信息
                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());
                // BeanDefinition 名称=》order-service
                String name = this.getClientName(attributes);
                // 注册客户端配置=》configuration属性
                this.registerClientConfiguration(registry, name, attributes.get("configuration"));
                // 注册客户端
                this.registerFeignClient(registry, annotationMetadata, attributes);
            }
        }

    }

注册了客户端的配置后,就会注册客户端,调用的是registerFeignClient方法:

    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {

        // account.OrderFeign
        String className = annotationMetadata.getClassName();
        // 创建FeignClientFactoryBean 类型的BeanDefinition 
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
        this.validate(attributes);
        // 将这些FeignClient 注解上的配置信息都添加到definition中,
        definition.addPropertyValue("url", this.getUrl(attributes));
       // 省略....
        beanDefinition.setPrimary(primary);
        String qualifier = this.getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {

            alias = qualifier;
        }
        // 添加到注册器中
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{

     alias});
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

可以看到,最终每个Feign 接口注册到了 注册器中,名称为包名+接口名,Feign 接口实际生成的Bean对象为FeignClientFactoryBean,这是一个FactoryBean,由它最终生成的代理对象后续会讲解)。
&nbsp;

总结

&nbsp;