1. 程式人生 > >Spring maven 專案中的classpath路徑問題

Spring maven 專案中的classpath路徑問題

    Spring中的classpath與classpath*一直是開發中的心病,沒有時間靜下心來研究下,終於一氣呵成!網上總結的也比較多,各種各樣的說法,還不如自己親自解讀一下spring中的原始碼,這樣下次再次使用心裡就安心多了,歡迎支援!

一、問題描述

使用spring時import資原始檔時路徑查詢順序不明(開發中的疑惑),或者載入資源失敗(不知道怎麼更改路徑)?

二、classpath程式碼解析

    直接上程式碼,這是程式碼中的主類 PathMatchingResourcePatternResolver 類,位於包 org.springframework.core.io.support 下,該類中的 getResources 函式是邏輯的核心,如下: 

public Resource[] getResources(String locationPattern)throws IOException {   
    if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {  
        if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {  
            // case 1:如果以classpath*開頭且包含?或者* ,例如查詢: classpath*: applicationContext-*.xml
            return findPathMatchingResources(locationPattern);  
        }  
        else {  
            // case 2: 不包含?或者*,直接全名查詢,例如查詢: classpath*: applicationContext-test.xml
            return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));  
        }  
    }  
    else {
        // 以 classpath:開頭
        int prefixEnd = locationPattern.indexOf(":") +1;  
        if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {  
            // case 3: 如果不是以classpath*開頭且包含?或者* ,例如查詢: classpath: applicationContext-*.xml
            return findPathMatchingResources(locationPattern);  
        }  
        else {  
            // case 4: 如果不是以classpath*開頭且不包含?或者* ,例如查詢: classpath: applicationContext-test.xml  
            returnnew Resource[] {getResourceLoader().getResource(locationPattern)};  
        }  
    }  
} 


2.1 如果以classpath*開頭且包含?或者*

例如查詢: classpath*: applicationContext-*.xml ,使用findPathMatchingResources函式,看下該函式:

                   
protected Resource[] findPathMatchingResources(String locationPattern)throws IOException {
    /*函式determineRootDir函式是拿到能夠確定的目錄,如 classpath*:/aaa/bbb/applicationContext-*.xml 則返回classpath*:/aaa/bbb/
                                                   classpath*:/aaa/*/applicationContext-*.xml,則返回 classpath*:/aaa/ 
    (程式碼就不再貼上了,有興趣的可以看下原始碼)*/
    String rootDirPath = determineRootDir(locationPattern);
    // 獲取字串locationPattern中後面不確定的內容
    String subPattern = locationPattern.substring(rootDirPath.length());
    // 遞迴載入已經確定的內容
    Resource[] rootDirResources = getResources(rootDirPath);
    Set result = new LinkedHashSet<>(16);
    for (Resource rootDirResource : rootDirResources) {
        rootDirResource = resolveRootDirResource(rootDirResource);
        URL rootDirURL = rootDirResource.getURL();
        if (equinoxResolveMethod !=null) {
            if (rootDirURL.getProtocol().startsWith("bundle")) {
                rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod,null, rootDirURL);
                rootDirResource = new UrlResource(rootDirURL);
            }
        }
        if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
            // is general JBoss VFS resource: "vfs" 
            result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher()));
        }
        elseif (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) {
            // is zip, jar, wsjar or vfszip的一種就載入
            result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern));
        }
        else {
            result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
        }
    }
    if (logger.isDebugEnabled()) {
        logger.debug("Resolved location pattern [" + locationPattern +"] to resources " + result);
    }
    return result.toArray(new Resource[result.size()]);
}

                 

總體來說:該函式把locationPattern拆分成兩部分:rootDirPath 和subPattern,rootDirPath是根目錄路徑,subPattern是子目錄路徑匹配規則字串。遍歷根目錄下的所有子目錄、並得到所有的子目錄在doFindPathMatchingFileResources(rootDirResource, subPattern)方法中,再根據子目錄逐個逐個去匹配subPattern。

2.2 如果以classpath*開頭且不包含?或者*,直接全名查詢

例如查詢: classpath*: applicationContext-test.xml,使用findAllClassPathResources函式,看下該函式:

protected Resource[] findAllClassPathResources(String location)throws IOException {
    String path = location;
    if (path.startsWith("/")) {
        path = path.substring(1);
    }
    Set result = doFindAllClassPathResources(path);
    if (logger.isDebugEnabled()) {
        logger.debug("Resolved classpath location [" + location +"] to resources " + result);
    }
    return result.toArray(new Resource[result.size()]);
}
                    

其實核心在doFindAllClassPathResources,該函式掃描該路徑下的所有資料,其函式體如下:

protected Set doFindAllClassPathResources(String path) throws IOException {
    Set result= new LinkedHashSet<>(16);
    ClassLoader cl = getClassLoader();
    Enumeration resourceUrls = (cl!= null? cl.getResources(path) : ClassLoader.getSystemResources(path));
    while (resourceUrls.hasMoreElements()) {
        URL url = resourceUrls.nextElement();
        result.add(convertClassLoaderURL(url));
    }
    if ("".equals(path)) {
        // 如果路徑為空的話,就找到所有jar包,加到result中(該函式不再深入)
        addAllClassLoaderJarRoots(cl, result);
    }
    return result;
}

                          

在該函式中重要:Enumeration resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path)); 即如果當前類載入器cl不為空的話,就呼叫cl.getResources(path),cl為空的話就使用系統類載入器去載入,事實上cl.getResources(path)內容使用的即是雙親委派模型(當前類載入器載入資源,如果存在父載入器,則用父載入器進行載入)。

2.3 如果不是以classpath*開頭且包含?或者*

例如查詢: classpath: applicationContext-*.xml,使用函式findPathMatchingResources,類似2.1情況。

2.4 如果不是以classpath*開頭且不包含?或者*

例如查詢: classpath: applicationContext-test.xml,直接“getResourceLoader().getResource(locationPattern)”,即直接使用當然的資源載入器去載入,這裡預設使用的是DefaultResourceLoader()。

三、總結及測試

寫了個小測試,測試程式碼如下:

private staticvoid output(String location)throws Exception {
    ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
    Resource[] source = resourceLoader.getResources(location);
//        System.out.println("source.size: " + source.length);
    for (int i =0; i < source.length; i++) {
        Resource resource = source[i];
        System.out.println(resource);

    }
}                 

很多個測試結果如下圖: