前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Feign源码分析之EnableFeignClients

Feign源码分析之EnableFeignClients

作者头像
克虏伯
发布2019-10-08 10:47:53
1.1K0
发布2019-10-08 10:47:53
举报

    springcloud-openfeign-core-2.1.1.release.

    在springcloud中使用feign的时候,如下List-1,FeignClient的name是服务名称,会自动从Eureka中获取物理地址。

List-1

代码语言:javascript
复制
@FeignClient(name="UserProvider")
public interface UserProvider {
...
}

    Springcloud中这个是如何实现的?

    这个要从@EnableFeignClients入手,如下List-2,Import注解引入了FeignClientsRegistrar——实现了ImportBeanDefinitionRegistrar接口,这样springboot会处理这个FeignClientsRegistrar。

List-2

代码语言:javascript
复制
@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 {};
}

    如下图1所示,FeignClientsRegistrar并没有复杂的继承关系,重点类看ImportBeanDefinitionRegistrar的registerBeanDefinitions实现。

                                                                                          图1

    如下List-3,分俩个步骤,首先是registerDefaultConfiguration方法,将EnableFeignClients的defaultConfiguration注册到Spring容器中;之后是registerFeignClients方法将FeignClient注解的接口注册到Spring容器中。

List-3

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

    来看registerDefaultConfiguration方法,如下List-4,

  1. 获取EnableFeignClients的所有属性,之后如果含有defaultConfiguration,则将defaultConfiguration注册到Spring容器中
  2. 方法registerClientConfiguration中,用Builder模式,构造FeignClientSpecification类型的BeanDefinition。FeignClientSpecification实现了NamedContextFactory.Specification接口,属性有个name和Class<?>类型的configuration。

List-4

代码语言:javascript
复制
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"));
    }
}

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

    registerFeignClients方法中,实现则较为复杂,如下List-5

List-5

代码语言:javascript
复制
public void registerFeignClients(AnnotationMetadata metadata,
    BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();//1
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    Map<String, Object> attrs = metadata
            .getAnnotationAttributes(EnableFeignClients.class.getName());//2
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
            FeignClient.class);//3
    final Class<?>[] clients = attrs == null ? null
            : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {//4
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);
    }
    else {//5
        final Set<String> clientClasses = new HashSet<>();
        basePackages = new HashSet<>();
        for (Class<?> clazz : clients) {
            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);
            }
        };
        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();
                Assert.isTrue(annotationMetadata.isInterface(),
                        "@FeignClient can only be specified on an interface");

                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(
                                FeignClient.class.getCanonicalName());

                String name = getClientName(attributes);
                registerClientConfiguration(registry, name,
                        attributes.get("configuration"));//6

                registerFeignClient(registry, annotationMetadata, attributes);//7
            }
        }
    }
}
  1. 1处获取ClassPathScanner,用于扫描类路径
  2. 2处获取EnableFeignClients的所有属性
  3. 3处构造一个AnnotationTypeFilter,构造方法参数是FeignClient,这个用于过滤出只含有FeignClient的类
  4. 获得EnableFeignClients的clients属性值,4处如果是空,则获得EnableFeignClients所在的package路径(如果没有设置basePackageClasses)
  5. 5处,即EnableFeignClients的clients属性不是空,则遍历,放入集合中,同时获取client所在的package路面,加入到basePacakges中;构造AbstractClassTestingTypeFilter,这是增加一个过滤条件,即标FeignClient注解的接口,必须在EnableFeignClients的clients中
  6. 遍历basePackages,获取每个package下的符合条件的类,得到对应的beanDefinition,6处得到FeignClient的configuration值,通过FeignClientSpecification其注册到spring容器中,有意思的是这里检查了FeignClient注解的类须是接口,不然会报错。
  7. 7处将FeignClient注解的接口封装到FeignClientFactoryBean中,FactoryBean大家懂的,Spring中接口都封装到这个里面。

    有个问题,FeignClient是可以设置Interceptor的,是如何实现的?

    这个在FeignClientFactoryBean中。

Reference

  1. 源码,github地址略
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Reference
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档