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

在這裏插入圖片描述

把過濾後的類返回,就完成了掃描。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章