【Spring Cloud】OpenFeign之扫描FeignClient类信息

FeignClientRegistrar 做的第二件事是扫描指定包下的类文件,注册 @FeignClient 注解修饰的接口类信息:

//FeignClientsRegistrar#registerDefaultConfiguration
public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    //生成自定义的ClassPathScanningCandidateComponentProvider
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    //获取@EnableFeignClients所有属性的键值对
    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    //使用@FeignClient作为类型进行过滤,即只扫描出被@FeignClient修饰的类
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.class);
    //如果@EnableFeignClients没有设置clients属性,那么需要扫描basePackages,所以使用AnnotationTypeFilter从basePackages中扫描
    final Class<?>[] clients = attrs == null ? null
        : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        //设置TypeFilter
        scanner.addIncludeFilter(annotationTypeFilter);
        //获取basePackages
        basePackages = getBasePackages(metadata);
    }
    else {
        //如果配置了clients属性,则从clients指定的类的路径下查找
        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)));
    }

    //扫描包路径,获取使用@FeignClient注解修饰的接口类
    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");

                //获取@FeignClient注解的属性键值对
                Map<String, Object> attributes = annotationMetadata
                    .getAnnotationAttributes(
                    FeignClient.class.getCanonicalName());

                //获取client的名称
                String name = getClientName(attributes);
                //单独注册每个@FeignClient中的configuration配置类
                registerClientConfiguration(registry, name,
                                            attributes.get("configuration"));

                //注册@FeignClient的接口类的BeanDefinition
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

如上述代码所示, FeignClientsRegistrarregisterFeignClients 方法依据 @EnableFeignClients 的属性,通过getBasePackages获取要扫描的包路径信息,然后获取这些包下所有被 @FeignClient 注解修饰的接口类的 BeanDefinition, 最后调用 registerFeignClient 动态注册 BeanDefinition

可以看下getBasePackages方法:

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

    //获取values属性值
    Set<String> basePackages = new HashSet<>();
    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属性值
    for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
        basePackages.add(ClassUtils.getPackageName(clazz));
    }

    //获取@EnableFeignClients注解修饰的类的全路径
    if (basePackages.isEmpty()) {
        basePackages.add(
            ClassUtils.getPackageName(importingClassMetadata.getClassName()));
    }
    return basePackages;
}

它根据 @EnableFeignClients 注解的valuebasePackagesbasePackageClasses属性来判断要扫描的路径,如果有则加入到backPackages属性中,如果没有则获取使用 @EnableFeignClients 注解修饰的类的全路径加入basePackages

OpenFeign 使用 ClassPathScanningCandidateComponentProvider 和各类 TypeFilter 来过滤出被 @FeignClient 修饰的类,getScanner方法如下:

//FeignClientsRegistrar#getScanner
protected ClassPathScanningCandidateComponentProvider getScanner() {
    return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
        @Override
        protected boolean isCandidateComponent(
            AnnotatedBeanDefinition beanDefinition) {
            boolean isCandidate = false;
            //判断beanDefinition是否为内部类
            if (beanDefinition.getMetadata().isIndependent()) {
                //判断是否为接口类,并且该接口不为Annotation类型,则返回true,否则返回false
                if (!beanDefinition.getMetadata().isAnnotation()) {
                    isCandidate = true;
                }
            }
            return isCandidate;
        }
    };
}

ClassPathScanningCandidateComponentProvider 的作用是遍历指定路径的包下的所有类。比如指定包路径为 aaa/bbb/ccc ,它会找出 aaa.bbb.ccc 包下所有的类,将所有的类封装成 Resource 接口集合。Resource 接口是 Spring 对资源的封装,有 FileSystemResourceClassPathResourceUrlResource 等多种实现。 接着 ClassPathScanningCandidateComponentProvider 类会遍历 Resource 集合,通includeFiltersexcludeFilters两种过滤器进行过滤操作。 includeFiltersexcludeFiltersTypeFilter 接口类型实例的集合,TypeFilter 是一个用于判断类型是否满足要求的类型过滤器。 excludeFilters 中只要有一个 TypeFilter 满足条件,那么这个 Resource 就会被过滤掉;而 includeFilters 中只要有一个 TypeFilter 满足条件,这个 Resource 就不会被过滤。 如果一个 Resource 没有被过滤,它会被转换成 ScannedGenericBeanDefinition 添加到 BeanDefinition 集合中。

//ClassPathScanningCandidateComponentProvider#scanCandidateComponents
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
private String resourcePattern = DEFAULT_RESOURCE_PATTERN;

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        //根据basePackages生成需要搜索的路径
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        //将basePackages下所有的类封装成Resource类集合
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        
        //遍历Resource
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                    //使用excludeFilters和includeFilters进行过滤
                    if (isCandidateComponent(metadataReader)) {
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setSource(resource);
                        if (isCandidateComponent(sbd)) {
                            if (debugEnabled) {
                                logger.debug("Identified candidate component class: " + resource);
                            }
                            candidates.add(sbd);
                        }
                        else {
                            if (debugEnabled) {
                                logger.debug("Ignored because not a concrete top-level class: " + resource);
                            }
                        }
                    }
                    else {
                        if (traceEnabled) {
                            logger.trace("Ignored because not matching any filter: " + resource);
                        }
                    }
                }
                catch (Throwable ex) {
                    throw new BeanDefinitionStoreException(
                        "Failed to read candidate component class: " + resource, ex);
                }
            }
            else {
                if (traceEnabled) {
                    logger.trace("Ignored because not readable: " + resource);
                }
            }
        }
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
    }
    return candidates;
}

通过调试可以看到扫描的路径是basePackages,如下图:
扫描路径

而通过路径扫描到的类有如下:

扫描类

根据图上看到,scanCandidateComponents方法会扫描路径下的所有类文件(*.class),不管是 Bean 实例,还是普通的 Java 类,都会被扫描到。

接着方法会遍历 Resource 集合, 并通过includeFilterexcludeFilter属性进行过滤,方法为isCandidateComponent

//ClassPathScanningCandidateComponentProvider#isCandidateComponent
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    //排除
    for (TypeFilter tf : this.excludeFilters) {
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return false;
        }
    }
    //包括
    for (TypeFilter tf : this.includeFilters) {
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return isConditionMatch(metadataReader);
        }
    }
    return false;
}

通过isCandidateComponent方法,会过滤出满足条件的配置类,由此会筛选出带有 @FeignClient 注解修饰的FeignClient 接口类。

for (String basePackage : basePackages) {
    //内部调用ClassPathScanningCandidateComponentProvider#scanCandidateComponents
    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");

            //获取@FeignClient注解的属性键值对
            Map<String, Object> attributes = annotationMetadata
                .getAnnotationAttributes(
                FeignClient.class.getCanonicalName());

            //获取服务名
            String name = getClientName(attributes);
            //将@FeignClient上的configuration注册到服务名name上
            registerClientConfiguration(registry, name,
                                        attributes.get("configuration"));

            //注册FeignClient接口类
            registerFeignClient(registry, annotationMetadata, attributes);
        }
    }
}

循环遍历 BeanDefinition ,获取每个 FeignClient 接口类上的configuration属性,并进行注册。该注册方法同 @EnableFeignClients 注册默认配置类一样,都是调用同一个方法registerClientConfiguration,只不过此处会注册 @FeignClient 上的configuration对应的配置类,同样注册了一个类为 FeignClientSpecification 的 Bean 实例,Bean的namexxx.FeignClientSpecification,而xxx为使用 @FeignClient 注解的接口类名称,可以根据 @FeignClient 注解的contextIdvaluenameserviceId属性的其中一个获取。

接着调用registerFeignClient方法进行 FeignClient接口类对象的注册:

private void registerFeignClient(BeanDefinitionRegistry registry,
		AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    //构建FeignClientFactoryBean类型的bean
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    //依次顺序判断serviceId、name、value的值作为name
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    //如果配置了contextId属性,则使用contextId,否则使用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);

    //别名
    String alias = contextId + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

    //默认为true
    boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
    // null

    beanDefinition.setPrimary(primary);

    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
        alias = qualifier;
    }

    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                                                           new String[] { alias });
    //注册bean
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

registerFeignClient的功能就是解析 **@FeignClient **注解的属性,并对该 FeignClient 接口类进行注册,注册的 Bean 类型为 FeignClientFactoryBean,并依次将 **@FeignClient ** 属性设置到 FeignClientFactoryBean 实例中,用于后期实例的获取。

//BeanDefinitionReaderUtils#registerBeanDefinition
public static void registerBeanDefinition(
    BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
    throws BeanDefinitionStoreException {

    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // Register aliases for bean name, if any.
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }
}

微信公众号:Java知识集训

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值