Spring是如何扫描包的 Spring5.x(源码向)

Spring是如何扫描包的 Spring5.x

锁定这个方法,Spring会在ClassPathScanningCandidateComponentProvider类的这个方法中进行扫描

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    LinkedHashSet candidates = new LinkedHashSet();

    try {
        //这是一个处理路径的步骤替换点.为/,后缀添加his.resourcePattern: "**/*.class"
        String packageSearchPath = "classpath*:" + this.resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        //这里获取所有的类
        Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);
        boolean traceEnabled = this.logger.isTraceEnabled();
        boolean debugEnabled = this.logger.isDebugEnabled();
        Resource[] var7 = resources;
        int var8 = resources.length;

        //这里Spring对所有的资源进行过滤,读取文件,然后读取该类的接口,父类,父类的接口,验证有没有包含@Component注解(被作为了接口)
        for(int var9 = 0; var9 < var8; ++var9) {
            Resource resource = var7[var9];
            if (traceEnabled) {
                this.logger.trace("Scanning " + resource);
            }

            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = this.getMetadataReaderFactory().getMetadataReader(resource);
                    if (this.isCandidateComponent(metadataReader)) {
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setResource(resource);
                        sbd.setSource(resource);
                        if (this.isCandidateComponent((AnnotatedBeanDefinition)sbd)) {
                            if (debugEnabled) {
                                this.logger.debug("Identified candidate component class: " + resource);
                            }

                            candidates.add(sbd);
                        } else if (debugEnabled) {
                            this.logger.debug("Ignored because not a concrete top-level class: " + resource);
                        }
                    } else if (traceEnabled) {
                        this.logger.trace("Ignored because not matching any filter: " + resource);
                    }
                } catch (Throwable var13) {
                    throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, var13);
                }
            } else if (traceEnabled) {
                this.logger.trace("Ignored because not readable: " + resource);
            }
        }

        return candidates;
    } catch (IOException var14) {
        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", var14);
    }
}

获取所有类的步骤

修改后的路径

可以看到当前的的目录变成了/分隔的**/*.class结尾的字符串

在这里插入图片描述

Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);

Resource是Spring定义的一种表示资源的类型,如下

package org.springframework.core.io;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import org.springframework.lang.Nullable;

public interface Resource extends InputStreamSource {
    boolean exists();

    default boolean isReadable() {
        return this.exists();
    }

    default boolean isOpen() {
        return false;
    }

    default boolean isFile() {
        return false;
    }

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String var1) throws IOException;

    @Nullable
    String getFilename();

    String getDescription();
}

1.使用AbstractApplicationContext获取资源组

public Resource[] getResources(String locationPattern) throws IOException {
    return this.resourcePatternResolver.getResources(locationPattern);
}

2.使用ResourcePatternResolver来进一步处理,筛选路径的开头

public Resource[] getResources(String locationPattern) throws IOException {
    //一个检查是否为空的警报类
    Assert.notNull(locationPattern, "Location pattern must not be null");
    //因为我们是classpath开头的,所以会进入这里
    if (locationPattern.startsWith("classpath*:")) {
        return this.getPathMatcher().isPattern(locationPattern.substring("classpath*:".length())) ? this.findPathMatchingResources(locationPattern) : this.findAllClassPathResources(locationPattern.substring("classpath*:".length()));
    } else {
        int prefixEnd = locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(58) + 1;
        return this.getPathMatcher().isPattern(locationPattern.substring(prefixEnd)) ? this.findPathMatchingResources(locationPattern) : new Resource[]{this.getResourceLoader().getResource(locationPattern)};
    }
}

这里有一些似乎迷惑的操作,讲解一下

this.getPathMatcher()这里是获取了Spring的一个工具类,是一个路径匹配的工具类,

isPattern方法是为了检查该路径是否为一个path路径,如下,包含下面几种字符就被认为是path,为了安全,这样是应该的

public boolean isPattern(@Nullable String path) {
    if (path == null) {
        return false;
    } else {
        boolean uriVar = false;

        for(int i = 0; i < path.length(); ++i) {
            char c = path.charAt(i);
            if (c == '*' || c == '?') {
                return true;
            }

            if (c == '{') {
                uriVar = true;
            } else if (c == '}' && uriVar) {
                return true;
            }
        }

        return false;
    }
}

现在是重头戏,正式开始找类

执行this.findPathMatchingResources(locationPattern)方法

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
    //对路径进行处理,保证路径的正常
    String rootDirPath = this.determineRootDir(locationPattern);
    //取一下结尾的类型信息,其实就是**/*.class
    String subPattern = locationPattern.substring(rootDirPath.length());
    //获取资源根目录,
    //URL [file:/E:/你的项目文件夹/target/classes/com/hhcat/hhcat/],
    //涉及的不是很复杂,想了解自己debug一下就行
    Resource[] rootDirResources = this.getResources(rootDirPath);
    Set<Resource> result = new LinkedHashSet(16);
    Resource[] var6 = rootDirResources;
    int var7 = rootDirResources.length;

    for(int var8 = 0; var8 < var7; ++var8) {
        Resource rootDirResource = var6[var8];
        Resource rootDirResource = this.resolveRootDirResource(rootDirResource);
        URL rootDirUrl = ((Resource)rootDirResource).getURL();
        if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
            URL resolvedUrl = (URL)ReflectionUtils.invokeMethod(equinoxResolveMethod, (Object)null, new Object[]{rootDirUrl});
            if (resolvedUrl != null) {
                rootDirUrl = resolvedUrl;
            }

            rootDirResource = new UrlResource(rootDirUrl);
        }
		//一连串判断,是为了查看是什么类型的文件我们是file开头的
        if (rootDirUrl.getProtocol().startsWith("vfs")) {
            result.addAll(PathMatchingResourcePatternResolver.VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, this.getPathMatcher()));
        } 
        //走了这里,这里是实际上的扫描包,使用了File类,扫描目录下所有的以之前那个**/*.class格式的文件,然后加入在result中,完成了扫描
        else if (!ResourceUtils.isJarURL(rootDirUrl) && !this.isJarResource((Resource)rootDirResource)) {
            result.addAll(this.doFindPathMatchingFileResources((Resource)rootDirResource, subPattern));
        } else {
            result.addAll(this.doFindPathMatchingJarResources((Resource)rootDirResource, rootDirUrl, subPattern));
        }
    }

    if (logger.isTraceEnabled()) {
        logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
    }

    return (Resource[])result.toArray(new Resource[0]);
}

在这里插入图片描述

把过滤后的类返回,就完成了扫描。

猜你喜欢

转载自blog.csdn.net/weixin_44494373/article/details/105926266