最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于ClasspathResource路徑問題的解決

 更新時(shí)間:2021年08月11日 11:57:07   作者:夜晚在編程  
這篇文章主要介紹了ClasspathResource路徑問題的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

ClasspathResource路徑問題

前言

在項(xiàng)目中工程以springboot jar形式發(fā)布,跟之前容器比少了一個(gè)解壓目錄,這個(gè)過程中出現(xiàn)了ClasspathResource的文件獲取問題。具體如下:

故障情況

本地springboot工程打成jar包發(fā)布,在以下代碼r.getFile()獲取類目錄下模板Excel文件報(bào)錯(cuò):

cannot be resolved to absolute file path because it does not reside in the file system: jar

在這里插入圖片描述

解決方案

調(diào)整代碼,直接獲取對(duì)應(yīng)的文件流,進(jìn)行封裝。

在這里插入圖片描述

ClassPathResource詳解

ClassPathReource resource=new ClassPathResource("spring_beans.xml");

1:public class ClassPathResource extends AbstractFileResolvingResource

在ClassPathResource中,含參數(shù)String path的構(gòu)造函數(shù):

    public ClassPathResource(String path ) {
         this (path , (ClassLoader) null);
    }

2:上述構(gòu)造函數(shù)指向了另外一個(gè)構(gòu)造函數(shù):

    public ClassPathResource (String path , ClassLoader classLoader ) {
        Assert. notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
         if (pathToUse .startsWith("/")) {
             pathToUse = pathToUse .substring(1);
        }
         this .path = pathToUse;
         this .classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    }

能夠看到path由StringUtils的cleanPath方法返回了pathToUse。由此,我們找到StringUtils的cleanPath方法

3:public abstract class StringUtils

public static String cleanPath (String path ) {
         if (path == null) {
             return null ;
        }
        String pathToUse = replace( path , WINDOWS_FOLDER_SEPARATOR , FOLDER_SEPARATOR);
         int prefixIndex = pathToUse .indexOf(":" );
        String prefix = "" ;
         if (prefixIndex != -1) {
             prefix = pathToUse .substring(0, prefixIndex + 1);
             pathToUse = pathToUse .substring(prefixIndex + 1);
        }
         if (pathToUse .startsWith(FOLDER_SEPARATOR)) {
             prefix = prefix + FOLDER_SEPARATOR;
             pathToUse = pathToUse .substring(1);
        }
 
        String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR );
        List<String> pathElements = new LinkedList<String>();
         int tops = 0;
 
         for (int i = pathArray. length - 1; i >= 0; i --) {
            String element = pathArray [i ];
             if (CURRENT_PATH .equals(element)) {
                 // Points to current directory - drop it.
            }
             else if (TOP_PATH.equals(element)) {
                 // Registering top path found.
                 tops ++;
            }
             else {
                 if (tops > 0) {
                     // Merging path element with element corresponding to top path.
                     tops --;
                }
                 else {
                     // Normal path element found.
                     pathElements .add(0, element );
                }
            }
        }
 
         // Remaining top paths need to be retained.
         for (int i = 0; i < tops; i++) {
             pathElements .add(0, TOP_PATH);
        }
 
         return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR );
    }

4:StringUtils類中 replace方法

public static String replace (String inString , String oldPattern , String newPattern ) {
         if (!hasLength( inString ) || !hasLength(oldPattern) || newPattern == null ) {
             return inString ;
        }
        StringBuilder sb = new StringBuilder();
         int pos = 0; // our position in the old string
         int index = inString .indexOf(oldPattern );
         // the index of an occurrence we've found, or -1
         int patLen = oldPattern.length();
         while (index >= 0) {
             sb.append( inString .substring(pos , index ));
             sb.append( newPattern );
             pos = index + patLen;
             index = inString .indexOf(oldPattern, pos );
        }
         sb.append( inString .substring(pos ));
         // remember to append any characters to the right of a match
         return sb .toString();
    }

5:StringUtils類類中的hasLength方法

由此可以看出,同樣的方法名,不同的方法簽名,然后在其中一個(gè)方法中引用另外一個(gè)方法。好多類都是這么用的,就像開始的時(shí)候的構(gòu)造函數(shù)那樣,雖然不知道好處 是什么,但先記下來。

    public static boolean hasLength (String str ) {
         return hasLength((CharSequence) str);
    }
    public static boolean hasLength (CharSequence str) {
         return (str != null && str.length() > 0);
    }

跟蹤到這里,可以知道hasLength方法的目的就是str不為空且str的長度大于0。突然發(fā)現(xiàn)CharSequence這個(gè)類沒接觸過,來看一下它的源碼

6:public interface CharSequence{}

額,源碼沒看懂 就不粘貼過來了。

7:回到StringUtils的replace方法

首先判斷傳入的三個(gè)參數(shù),如果為空后者長度小于0,直接返回inString;那么我看一下這三個(gè)參數(shù)都是什么:

inString:path 這個(gè)就是我們傳入的文件名

oldPattern:private static final String WINDOWS_FOLDER_SEPARATOR = "\\";

newPattern:private static final String FOLDER_SEPARATOR = "/" ;這兩個(gè)是文件分隔符

然后給局部變量index賦值,通過查閱API:

public int indexOf(int ch)

返回指定字符在此字符串中第一次出現(xiàn)處的索引。

意思就是在path中查找"\\",例如我寫文件的絕對(duì)路徑是D:\\文件\\API\\JDK_API_1_6_zh_CN.CHM,我就需要循環(huán)的讀取“\\”,接下來while循環(huán)中出現(xiàn)了substring方法,繼續(xù)查閱API:

public String substring(int beginIndex)

返回一個(gè)新的字符串,它是此字符串的一個(gè)子字符串。該子字符串從指定索引處的字符開始,直到此字符串末尾。

public String substring(int beginIndex,
                        int endIndex)

返回一個(gè)新字符串,它是此字符串的一個(gè)子字符串。該子字符串從指定的 beginIndex 處開始,直到索引 endIndex - 1 處的字符。因此,該子字符串的長度為 endIndex-beginIndex

故此 ,第一次循環(huán)會(huì)把path路徑中從0索引開始,直到第一個(gè)"\\"之間的內(nèi)容添加到StringBuffer中,然后再在StringBuffer中添加“/”,接下來pos和index都需要改變,要往后挪。因?yàn)檠h(huán)需要往后走,我們要找到第二個(gè)“\\”,覺得這個(gè)有點(diǎn)算法的意思。返回sb.toString()。

總結(jié)一下replace方法,本意是根據(jù)傳入的路徑path,如果是D:\\文件\\API\\JDK_API_1_6_zh_CN.CHM這種格式的,給轉(zhuǎn)換成D:/文件/API/JDK_API_1_6_zh_CN.CHM這種格式。

8:StringUtils的cleanPath方法:

通過replace的返回值,我們得到了可以用的路徑pathToUse,然后我們要把這個(gè)路徑下“:”給找出來,正如代碼

int prefixIndex = pathToUse.indexOf(":" );

那樣,需要知道,indexOf方法只要沒找到相應(yīng)的字符,就會(huì)返回-1,所以在下面的判斷中才會(huì)以perfixIndex是否為-1來進(jìn)行判斷。如果路 徑中有“:”,接著以D:/文件/API/JDK_API_1_6_zh_CN.CHM舉例,prefix="D:" pathToUse="/文件/API/JDK_API_1_6_zh_CN.CHM ",這個(gè)很有意思,因?yàn)槌绦虿恢牢覀冚斎氲氖墙^對(duì)路徑 帶D:的這種 ,還是/開頭的這種,或者說相對(duì)路徑,程序直接全給你判斷了。

接下來會(huì)判斷pathToUse是否以“/"開頭,是的話prefix會(huì)加上“/”,現(xiàn)在的prefix有兩種情況,可能是"D:/"這種,也可能是"/"這種,而pathToUse肯定是“文件/API/JDK_API_1_6_zh_CN.CHM ”這種了。

String[] pathArray = delimitedListToStringArray( pathToUse, FOLDER_SEPARATOR );

看到這句代碼,我估計(jì)是把pathToUse給拆成字符串?dāng)?shù)組里,就像是這樣,文件 API ***的這種。接下來看看具體的代碼是不是這樣:

9:StringUtils的delimitedListToStringArray方法

public static String[] delimitedListToStringArray(String str, String delimiter) {
         return delimitedListToStringArray( str, delimiter, null );
    }
     
public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete ) {
         if (str == null) {
             return new String[0];
        }
         if (delimiter == null) {
             return new String[] {str};
        }
        List<String> result = new ArrayList<String>();
         if ("" .equals(delimiter)) {
             for (int i = 0; i < str.length(); i++) {
                 result.add(deleteAny( str.substring(i , i + 1), charsToDelete));
            }
        }
         else {
             int pos = 0;
             int delPos ;
             while ((delPos = str.indexOf(delimiter , pos )) != -1) {
                 result.add(deleteAny( str.substring(pos , delPos), charsToDelete ));
                 pos = delPos + delimiter.length();
            }
             if (str .length() > 0 && pos <= str.length()) {
                 // Add rest of String, but not in case of empty input.
                 result.add(deleteAny( str.substring(pos ), charsToDelete));
            }
        }
         return toStringArray( result);
    }

先看看傳入的參數(shù):

str:pathToUse,就是文件/API/JDK_API_1_6_zh_CN.CHM
                    delimiter:"/"
                    charsToDelete:null
    public static String deleteAny(String inString, String charsToDelete ) {
         if (!hasLength( inString) || !hasLength(charsToDelete)) {
             return inString ;
        }
        StringBuilder sb = new StringBuilder();
         for (int i = 0; i < inString.length(); i++) {
             char c = inString.charAt( i);
             if (charsToDelete .indexOf(c) == -1) {
                 sb.append( c);
            }
        }
         return sb .toString();
    }

如果說我們傳入的"/"等于""的話,顯然是不可能,我們所假如的話,會(huì)把pathTOUse倒著循環(huán),每個(gè)字符都摘出來,然后當(dāng)成字符串用,傳入deleteAny中,然后又是循環(huán),對(duì)每個(gè)字符而言,如果charsToDelete中沒有這個(gè)字符,就在StringBuilder中添加這個(gè)字符。返回值是String。當(dāng)然了,這個(gè)還沒用到。我們用到的是那個(gè)很復(fù)雜的else

我們遇到了一個(gè)循環(huán),對(duì)pathToUse而言,從索引0開始,如果pathToUse中有"/",就像文件/API/JDK_API_1_6_zh_CN.CHM 我們會(huì)得到“文件,然后還會(huì)進(jìn)入deleteAny這個(gè)方法,參數(shù)inString就是"文件",charsToDelete是null,突然發(fā)現(xiàn)charsToDelete的值為Null的話會(huì)直接返回InString,也就是“文件”。

返回到delimitedListToStringArray方法之后,接著往后循環(huán),最終的結(jié)果就是實(shí)現(xiàn)了把pathToUse給切割成若干個(gè)String的形式。

10:回到StringUtils的cleanPath方法

我們遇到了一個(gè)倒著的循環(huán),如果說我們這個(gè)是特別正常的路徑,就相當(dāng)于復(fù)制了,如果是以.或者..結(jié)尾的這些內(nèi)容,我們就把它給忽略了。

我得承認(rèn)上面這些過程真的好復(fù)雜,其實(shí)就是做了一件事,對(duì)輸入的路徑進(jìn)行了處理,只不過考慮的情況多了一點(diǎn)。所以我決定還是用debug來走一遍看看。

這時(shí)我用的是絕對(duì)路徑

終于熬到了這個(gè)方法的結(jié)束。

11:回到ClassPathResource的構(gòu)造函數(shù)

this .classLoader = ( classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());

如果傳入的classLoaser有值,就返回這個(gè)值,如果沒有,就獲取一個(gè)。

public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;
         try {
             cl = Thread.currentThread().getContextClassLoader();
        }
         catch (Throwable ex ) {
             // Cannot access thread context ClassLoader - falling back...
        }
         if (cl == null) {
             // No thread context class loader -> use class loader of this class.
             cl = ClassUtils.class .getClassLoader();
             if (cl == null) {
                 // getClassLoader() returning null indicates the bootstrap ClassLoader
                 try {
                     cl = ClassLoader.getSystemClassLoader();
                }
                 catch (Throwable ex ) {
                     // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
                }
            }
        }
         return cl ;
    }

寫到這,我們的ClassPathResouce resouce實(shí)例就有了path 和 classLoader這兩個(gè)關(guān)鍵屬性。

如果我們想獲取輸入流

    @Override
    public InputStream getInputStream() throws IOException {
        InputStream is;
         if (this .clazz != null) {
             is = this.clazz .getResourceAsStream(this. path);
        }
         else if (this.classLoader != null) {
             is = this.classLoader .getResourceAsStream(this. path);
        }
         else {
             is = ClassLoader.getSystemResourceAsStream( this.path );
        }
         if (is == null) {
             throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
        }
         return is ;
    }

會(huì)判斷clazz 有沒有值,classLoader有沒有值,然后再獲取輸入流。前兩天整理了java.lang.Class這個(gè)類的意思,現(xiàn)在就能用一點(diǎn)了,先看看定義的一些屬性

    private final String path ; 
    private ClassLoader classLoader; 
    private Class<?> clazz;

ClassPathResource有好幾個(gè)構(gòu)造函數(shù),有的構(gòu)造函數(shù)會(huì)傳入classLoader,有的會(huì)傳入clazz,這個(gè)clazz就是相應(yīng)的類在JVM上的實(shí)例,顯然上面的例子中并沒有這個(gè)東西,而classLoader是有的,所以通過classLoader獲取輸入流。

我覺得對(duì)ClassPathResource理解的更透徹了,雖然大部分時(shí)間都是在對(duì)path進(jìn)行處理。

近期還要看看ClassLoader,還不是很清楚它的工作機(jī)制。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 解決新版 Idea 中 SpringBoot 熱部署不生效的問題

    解決新版 Idea 中 SpringBoot 熱部署不生效的問題

    這篇文章主要介紹了解決新版 Idea 中 SpringBoot 熱部署不生效的問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • 一文秒懂Java中的樂觀鎖 VS 悲觀鎖

    一文秒懂Java中的樂觀鎖 VS 悲觀鎖

    這篇文章主要介紹了java樂觀鎖 VS 悲觀鎖的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2021-05-05
  • 使用Java8?Stream流的skip?+?limit實(shí)現(xiàn)批處理的方法

    使用Java8?Stream流的skip?+?limit實(shí)現(xiàn)批處理的方法

    Stream 作為 Java 8 的一大亮點(diǎn),它與 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念這篇文章主要介紹了使用Java8?Stream流的skip?+?limit實(shí)現(xiàn)批處理,需要的朋友可以參考下
    2022-07-07
  • SpringBoot中的@Import注解四種使用方式詳解

    SpringBoot中的@Import注解四種使用方式詳解

    這篇文章主要介紹了SpringBoot中的@Import注解四種使用方式詳解,@Import注解只可以標(biāo)注在類上,可以結(jié)合 @Configuration注解、ImportSelector、ImportBeanDefinitionRegistrar一起使用,也可以導(dǎo)入普通的類,需要的朋友可以參考下
    2023-12-12
  • Spring Boot 捕捉全局異常 統(tǒng)一返回值的問題

    Spring Boot 捕捉全局異常 統(tǒng)一返回值的問題

    這篇文章主要介紹了Spring Boot 捕捉全局異常 統(tǒng)一返回值,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-06-06
  • IDEA項(xiàng)目maven?project沒有出現(xiàn)plugins和Dependencies問題

    IDEA項(xiàng)目maven?project沒有出現(xiàn)plugins和Dependencies問題

    這篇文章主要介紹了IDEA項(xiàng)目maven?project沒有出現(xiàn)plugins和Dependencies問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • 微服務(wù)Spring?Cloud?Alibaba?的介紹及主要功能詳解

    微服務(wù)Spring?Cloud?Alibaba?的介紹及主要功能詳解

    Spring?Cloud?是一個(gè)通用的微服務(wù)框架,適合于多種環(huán)境下的開發(fā),而?Spring?Cloud?Alibaba?則是為阿里巴巴技術(shù)棧量身定制的解決方案,本文給大家介紹Spring?Cloud?Alibaba?的介紹及主要功能,感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • 解析JDK14中的java tools簡介

    解析JDK14中的java tools簡介

    這篇文章主要介紹了JDK14中的java tools簡介,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-04-04
  • 動(dòng)態(tài)代理模擬實(shí)現(xiàn)aop的示例

    動(dòng)態(tài)代理模擬實(shí)現(xiàn)aop的示例

    下面小編就為大家?guī)硪黄獎(jiǎng)討B(tài)代理模擬實(shí)現(xiàn)aop的示例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望對(duì)大家有所幫助
    2017-11-11
  • SpringBoot @ControllerAdvice 攔截異常并統(tǒng)一處理

    SpringBoot @ControllerAdvice 攔截異常并統(tǒng)一處理

    這篇文章主要介紹了SpringBoot @ControllerAdvice 攔截異常并統(tǒng)一處理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09

最新評(píng)論

麻栗坡县| 大安市| 修文县| 霸州市| 蕲春县| 拜城县| 栖霞市| 泰兴市| 扬中市| 灯塔市| 竹溪县| 佛坪县| 青浦区| 天长市| 准格尔旗| 临城县| 绥中县| 阿坝县| 宁南县| 民和| 庆城县| 新闻| 哈巴河县| 西丰县| 雅安市| 交城县| 敖汉旗| 茂名市| 南昌县| 淄博市| 八宿县| 拜城县| 河间市| 泸西县| 额济纳旗| 高碑店市| 哈密市| 尼勒克县| 灵台县| 富裕县| 南宁市|