SpringCloud 源码系列(13)— 服务调用Feign 之 扫描@FeignClient注解接口

1,903 阅读8分钟

专栏系列文章:SpringCloud系列专栏

系列文章:

SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化

SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约

SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表

SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制

SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群

SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇

SpringCloud 源码系列(7)— 负载均衡Ribbon 之 RestTemplate

SpringCloud 源码系列(8)— 负载均衡Ribbon 之 核心原理

SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置

SpringCloud 源码系列(10)— 负载均衡Ribbon 之 HTTP客户端组件

SpringCloud 源码系列(11)— 负载均衡Ribbon 之 重试与总结篇

SpringCloud 源码系列(12)— 服务调用Feign 之 基础使用篇

Feign

Feign 是一个伪 Java HTTP 客户端,Feign 不做任何的请求处理,它只是简化API调用的开发,开发人员只需定义客户端接口,按照 springmvc 的风格开发声明式接口。然后在使用过程中我们只需要依赖注入Bean,然后调用对应的方法传递参数即可。

这里就有个问题,我们开发的是一个接口,然后使用 @FeignClient 注解标注,那又是如何能够注入这个接口的Bean对象的呢?其实很容易就能想到,一定是生成了接口的动态代理并注入到Spring容器中了,才能依赖注入这个客户端接口。

FeignClient 动态注册组件

FeignClientsRegistrar

再看下 @EnableFeignClients 注解,它使用 @Import 导入了 FeignClientsRegistrar,FeignClient 注册者。从名字就可以看出,FeignClientsRegistrar 就是完成 FeignClient 接口注册的核心组件。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// FeignClient 注册处理类
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    //...
}

FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware 三个接口。

  • ResourceLoaderAware 是为了注入资源加载器 ResourceLoader
  • EnvironmentAware 是为了注入当前环境组件 Environment
  • ImportBeanDefinitionRegistrar 是 Spring 动态注册 bean 的接口,就是利用这个接口来动态注册 FeignClient 动态代理对象的
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    // patterned after Spring Integration IntegrationComponentScanRegistrar
    // and RibbonClientsConfigurationRegistgrar

    // 资源加载器
    private ResourceLoader resourceLoader;
    // 当前环境组件
    private Environment environment;

    //....
}

ImportBeanDefinitionRegistrar

1、ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar 主要包含一个接口方法 registerBeanDefinitions,就是用来动态注册 BeanDefinition 的。平时我们一般就使用 @Service、@Component、@Bean 等注解向 Spring 容器注册对象,我们也可以实现 ImportBeanDefinitionRegistrar 接口来动态注册 BeanDefinition。

所有实现了 ImportBeanDefinitionRegistrar 接口的类的都会被 ConfigurationClassPostProcessor 处理,ConfigurationClassPostProcessor 实现了 BeanFactoryPostProcessor 接口,所以 ImportBeanDefinitionRegistrar 中动态注册的bean是优先于依赖它的bean初始化的,也能被aop、validator 等机制处理。ImportBeanDefinitionRegistrar 实现类写好之后,还要使用 @Import 注解导入实现类。

public interface ImportBeanDefinitionRegistrar {

    /**
     * Register bean definitions as necessary based on the given annotation metadata of
     * the importing {@code @Configuration} class.
     * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
     * registered here, due to lifecycle constraints related to {@code @Configuration}
     * class processing.
     * <p>The default implementation is empty.
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry current bean definition registry
     */
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    }
}

2、BeanDefinition

BeanDefinition 又是什么呢?从注释可以了解到,BeanDefinition 就是用来描述 bean 实例的,BeanDefinition 包含了实例的属性值、构造函数参数等。其实就是通过这个 BeanDefinition 来获取实例对象。

/**
 * A BeanDefinition describes a bean instance, which has property values,
 * constructor argument values, and further information supplied by
 * concrete implementations.
 *
 * <p>This is just a minimal interface: The main intention is to allow a
 * {@link BeanFactoryPostProcessor} to introspect and modify property values
 * and other bean metadata.
 */
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
}

3、注册 BeanDefinition

FeignClientsRegistrar 实现的 registerBeanDefinitions 方法中,主要有两步:

  • 注册FeignClient默认配置对象,就是根据 @EnableFeignClients 的 defaultConfiguration 配置类注入默认配置,这个一般就是全局配置。
  • 之后就是扫描 @FeignClient 注解的接口,封装成 BeanDefinition,然后用 BeanDefinitionRegistry 来注册。

因此,FeignClientsRegistrar 就是扫描 @FeignClient 注解的接口,并注册 FeignClient 的核心组件。

// 根据注解元数据注册bean定义
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 注册 FeignClient 默认配置类,根据 @EnableFeignClients 的 defaultConfiguration 注入默认配置
    registerDefaultConfiguration(metadata, registry);
    // 扫描 FeignClient 接口,注册 FeignClient
    registerFeignClients(metadata, registry);
}

扫描 @FeignClient 注解接口

接着看 registerFeignClients 方法,这个方法主要就是完成扫描 @FeignClient 注解的接口并完成 FeignClient 注册的工作。

主要的流程如下:

  • 首先得到一个类路径扫描器 ClassPathScanningCandidateComponentProvider,就是用这个组件来扫描包路径获取到 @FeignClient 注解的接口。
  • 如果 @EnableFeignClients 没有配置 clients 属性,扫描的包路径就是 @EnableFeignClients 配置的 value、basePackages、basePackageClasses 配置的包路径。并且根据注解过滤器来筛选有 @FeignClient 注解的接口。
  • 如果 @EnableFeignClients 配置了 clients 属性,就只扫描 clients 配置的接口类。
  • 之后就遍历扫描包路径,获取到 @FeignClient 注解的接口。可以看到 @FeignClient 注解的类型必须是一个接口,否则断言会抛出异常。
  • 最后两步就是注册配置类和注册 FeignClient了,配置类就是 @FeignClient 的 configuration 属性配置的客户端配置类,这个配置类将覆盖 @EnableFeignClients 配置的全局配置类。
**
 * 注册 FeignClient
 *
 * @param metadata @EnableFeignClients 注解的元数据
 * @param registry BeanDefinition 注册器
 */
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // ClassPath 扫描器
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    // @EnableFeignClients 注解的属性
    Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
    // 注解类型过滤器,过滤 @FeignClient 注解的接口
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
    final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");

    // 如果 @EnableFeignClients 没有配置 clients,就取 value、basePackages、basePackageClasses 基础包
    if (clients == null || clients.length == 0) {
        // @FeignClient 注解过滤器
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);
    }
    // 如果 @EnableFeignClients 中配置了 clients
    else {
        final Set<String> clientClasses = new HashSet<>();
        basePackages = new HashSet<>();
        for (Class<?> clazz : clients) {
            // 基础包取配置的 client 类所在的包
            basePackages.add(ClassUtils.getPackageName(clazz));
            // 根据名称过滤
            clientClasses.add(clazz.getCanonicalName());
        }
        // 类过滤器
        AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
            @Override
            protected boolean match(ClassMetadata metadata) {
                String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                // 根据名称过滤
                return clientClasses.contains(cleaned);
            }
        };
        // 必须类名在 clientClasses 中且类上有 @FeignClient 注解
        scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    }

    // 扫描基础包
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                // @FeignClient 注解的类型必须是一个接口
                Assert.isTrue(annotationMetadata.isInterface(),
                        "@FeignClient can only be specified on an interface");

                // @FeignClient 注解的属性
                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(FeignClient.class.getCanonicalName());
                // Feign 客户端名称,就是服务名
                String name = getClientName(attributes);
                // 注解客户端配置类
                registerClientConfiguration(registry, name, attributes.get("configuration"));
                // 注册 FeignClient
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

看下 getBasePackages 方法,可以看出,要扫描的包路径包含 @EnableFeignClients 配置的 value、basePackages、basePackageClasses 类所在的包,这里是取的多个配置的并集。

还有个需要注意的是,从最后一步可以看出,如果配置了 value、basePackages、basePackageClasses 时,就不会扫描 @EnableFeignClients 所在的包路径了,如果要扫描,需配置到 value 等属性中。

protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
    Map<String, Object> attributes = importingClassMetadata
            .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

    Set<String> basePackages = new HashSet<>();
    // 先取 value
    for (String pkg : (String[]) attributes.get("value")) {
        if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
        }
    }
    // 再取 basePackages
    for (String pkg : (String[]) attributes.get("basePackages")) {
        if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
        }
    }
    // 再从 basePackageClasses 的 Class 获取包
    for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
        basePackages.add(ClassUtils.getPackageName(clazz));
    }

    // 只有当没有配置 value、basePackages、basePackageClasses 时,才会扫描 @EnableFeignClients 所在的包路径
    if (basePackages.isEmpty()) {
        basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
    }
    return basePackages;
}

Feign客户端BeanDefinition构造并注册

registerFeignClients 中扫描了包路径下的 @FeignCient 注解的接口,然后调用了 registerFeignClient 注册 FeignClient 接口的 BeanDefinition

主要的流程如下:

  • 首先创建了 BeanDefinitionBuilder,要构建的类型是 FeignClientFactoryBean,从名字可以看出就是创建 FeignClient 代理对象的工厂类。FeignClientFactoryBean 就是生成 FeignClient 接口动态代理的核心组件
  • 接着就是将 @FeignClient 注解的属性设置到 definition 中,它这里还设置了回调类 fallback 和回调工厂 fallbackFactory,但是有没有用呢?这个后面再分析。
  • 然后是 bean 的名称,默认为 服务名称 + "FeignClient",例如 "demo-consumerFeignClient";如果设置了 qualifier 属性,名称就是 qualifier 设置的值。
  • 之后用 BeanDefinitionBuilder 获取 BeanDefinition,并设置了对象类型为 FeignClient 接口的全限定名。
  • 最后,将 BeanDefinition 等信息封装到 BeanDefinitionHolder,然后调用 BeanDefinitionReaderUtils.registerBeanDefinition 将 BeanDefinition 注册到Spring IoC 容器中。
private void registerFeignClient(BeanDefinitionRegistry registry,
        AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    // FeignClientFactoryBean 就是用来生成 FeignClient 接口代理类的核心组件
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    // 从 @FeignClient 中得到的属性,并设置到 BeanDefinitionBuilder
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    String contextId = getContextId(attributes);
    definition.addPropertyValue("contextId", contextId);
    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);

    // bean 的别名,demo-consumerFeignClient
    String alias = contextId + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    // bean 的类型,就是 FeignClient 接口
    beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

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

    // 自定义的别名标识
    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
        alias = qualifier;
    }

    // 将信息都封装到 BeanDefinitionHolder
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
    // 注册bean
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

一张图总结 @FeignClient 接口扫描流程

下面用一张图来总结下 @FeignClient 接口是如何被扫描并注册到容器中的。

  • 首先我们在代码中开发了 FeignClient 客户端调用接口,并用 @FeignClient 注解,注意 @FeignClient 只能加到接口上面
  • 之后我们需要在启动类或配置类中加一个 @EnableFeignClients 注解来启用 FeignClien。@EnableFeignClients 其实就是导入了 FeignClient 注册器 FeignClientsRegistrar
  • FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,在 registerBeanDefinitions 实现中,主要有两步:
    • 注册全局配置配置类,就是 @EnableFeignClients 中指定的 defaultConfiguration
    • 接着就是扫描注册 FeignClient
  • 注册客户端时,先用 ClassPathScanningCandidateComponentProvider 扫描器扫描出配置的包下的 @FeignClient 注解的接口
  • 扫描到 @FeignClient 接口后,先注册客户端特定的配置,就是 @FeignClient 配置的 configuration
  • 接着注册客户端:
    • 先构建一个 BeanDefinitionBuilder,要创建的 BeanDefinition 类型是 FeignClientFactoryBean
    • 然后就是将 @FeignClient 中的配置设置到 BeanDefinitionBuilder,其实就是设置给 FeignClientFactoryBean。
    • 之后解析出 FeignClient 的别名,默认是 服务名+“FeignClient”
    • 再用 BeanDefinitionBuilder 构建出 BeanDefinition,并将相关信息封装到 BeanDefinitionHolder 中。
    • 最后使用 BeanDefinitionReaderUtils 完成 BeanDefinition 的注册。
    • 将 BeanDefinition 注入容器后,就会调用 FeignClientFactoryBean 的 getObject 方法来创建动态代理。