Feign源码深度刨析-(2)组件扫描器:ClassPathScanningCandidateComponentProvider

“不积跬步,无以至千里。”

这篇文章,一起来看看feign的核心机制,看看是如何扫描所有包下的@FeignClient注解的接口

入口就是这个registerFeignClients(metadata, registry)方法

public void registerFeignClients(AnnotationMetadata metadata,
                                 BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.class);
    final Class<?>[] clients = attrs == null ? null
        : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);
    }
    else {
        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"));

                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

ClassPathScanningCandidateComponentProvider scanner = getScanner();

首先在这里拿到一个Scanner,扫描器,注意看这个扫描器的名称,ClassPathScanningCandidateComponentProvider,顾名思义,就是从classpath下扫描候选的组件的一个Provider,你可以简单理解为一个扫描器,然后扫描的是classpath下的某个路径里面的组件

clients == null || clients.length == 0

下面有这么一个判断,这个判断一定是true,因为我们并没有给 EnableFeignClients 注解的clients属性赋值

scanner.addIncludeFilter(annotationTypeFilter);

给这个scanner添加一个注解过滤器,以@FeignClient为过滤条件,似乎是用来把指定路径下我们标注@FeignClient注解的接口给过滤出来的,后面可以通过断点来看一下

basePackages = getBasePackages(metadata);

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

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

    if (basePackages.isEmpty()) {
        basePackages.add(
            ClassUtils.getPackageName(importingClassMetadata.getClassName()));
    }
    return basePackages;
}

这个方法就是在解析最终要扫描的包,basePackages

因为在@EnableFeignClients中我们value、basePackages和basePackageClasses都没有配置

所以会走到 basePackages.isEmpty() 的逻辑里

ClassUtils.getPackageName(importingClassMetadata.getClassName())

这里会通过一个Utils工具类拿到启动类所在的包路径,并返回

basePackages

拿到 basePackages 之后,就开始了for循环里的逻辑

Set candidateComponents = scanner.findCandidateComponents(basePackage);

很关键的一行代码,这个代码做了什么事情?

就是从basePackages(启动类所在路径)路径下扫描出来标注了@FeignClient 注解的组件出来,并放在了一个set集合中

这个核心的一个方法肯定要进去看看

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    }
    else {
        return scanCandidateComponents(basePackage);
    }
}

继续走,会走到 scanCandidateComponents(basePackage) 的逻辑里

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                    if (isCandidateComponent(metadataReader)) {
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setResource(resource);
                        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;
}

这个packageSearchPath的值就是 classpath*:com/wc/resttemplate/**/*.class,指定扫描范围,classpath下,启动类所在包及其子包的所有.class文件

isCandidateComponent(metadataReader)

这个方法你注意,它的参数类型是 MetadataReader !!!

为什么我要强调这个,因为后面还会调用一个重载的同名方法,但是用的是getScanner() 里面覆盖的实现!!!

看到这里是不是有点疑惑,其实也没什么,因为我第一次看这里也是有点懵,直到后面反复看了几遍才理解,这一块先记住刚才的结论,后面可以翻回来在看看

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

这里excludeFilters是空,因为我们没有添加,所以上面那段代码直接过

includeFilters是有值的,我们之前添加过一个annotationTypeFilter,使用来过滤@FeignClient注解的

还记得那段代码吗? scanner.addIncludeFilter(annotationTypeFilter);

继续走,tf.match(metadataReader, getMetadataReaderFactory())

因为当前扫描的是我的XXXController,自然是没有@FeignClient注解的,所以返回了false

也就是 isCandidateComponent(metadataReader) 返回了false,所以不进行任何操作,直接进行下一个循环,扫描其他类

继续走,tf.match(metadataReader, getMetadataReaderFactory())

因为这一次扫描的是我打了FeignClient注解的类,所以这次match成功了

调用了 isConditionMatch(metadataReader) 方法,最终返回ture,这个方法就不进去细看了,属于spring-context比较底层的操作,主要是看看这个组件是否满足condition注解的一些条件,比如missingBean等,这是自动配置玩儿的那一套,我们业务组件一般返回的都是ture

也就是 isCandidateComponent(metadataReader) 这里返回了ture

往下走, isCandidateComponent(sbd) ,进入了一个重载的方法中,类型是AnnotatedBeanDefinition

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    AnnotationMetadata metadata = beanDefinition.getMetadata();
    return (metadata.isIndependent() && (metadata.isConcrete() ||
                                         (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

你以为会进入上面的方法吗?

No!

还记得registerFeignClients方法的第一行代码吗?

ClassPathScanningCandidateComponentProvider scanner = getScanner();

我们看看这个getScanner()的逻辑

protected ClassPathScanningCandidateComponentProvider getScanner() {
    return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            boolean isCandidate = false;
            if (beanDefinition.getMetadata().isIndependent()) {
                if (!beanDefinition.getMetadata().isAnnotation()) {
                    isCandidate = true;
                }
            }
            return isCandidate;
        }
    };
}

在想想我之前说的,在看看这个重载的方法的参数,AnnotatedBeanDefinition!!!

明白了吧,第二个方法实际上是进入到这个在getScanner()时覆盖的方法里来的

我们看断点,来验证一下

getScanner()

beanDefinition.getMetadata().isAnnotation()

这个方法里,会判断一下,这个扫描的组件是不是一个注解(是注解还怎么玩儿~),如果不是一个注解,就返回一个ture,我们的feignclient客户端当然不是一个注解了,而是一个接口,所以返回了ture

candidates.add(sbd);

然后就把这个@FeignClient放入到set集合中,最后把这个set返回了

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"));

        registerFeignClient(registry, annotationMetadata, attributes);
    }
}

if (candidateComponent instanceof AnnotatedBeanDefinition)

这个if的判断,是说你这个过滤出来的bean,必须是被注解标注的,这个肯定是成立的,ture

Assert.isTrue(annotationMetaCdata.isInterface(), “@FeignClient can only be specified on an interface”);

这个断言都说了,@FeignClient注解必须标注在接口上!!!

然后拿到@FeignClient注解里,我们配置的属性,主要是获取@FeignClient指定的要访问的服务名称

registerClientConfiguration(registry, name, attributes.get(“configuration”));

注册客户端配置,这里会将name(服务名称)和 @FeignClient的配置属性在 BeanDefinitionRegistry 中进行注册,跟之前的一样,这里不是核心的东西,直接过

registerFeignClient(registry, annotationMetadata, attributes);

最后对扫描出来的@FeignClient接口去进行注册,这里比较关键,后面会着重分析一下

ok,总结一下,registerFeignClients()方法里核心的东西是一个扫描器,把我们标注了@FeignClient注解的接口全部扫描出来,然后放进一个set,后续再使用 BeanDefinitionRegistry 注册。

feign核心机制

这篇文章可能有点绕,但核心的东西肯定是讲解的很清晰了,看完感觉还是有点迷的朋友可以多看几遍,不理解的地方也可以在文章下方留言,我看到也会及时解惑。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值