SpringBoot打jar包或war包獲取不到資原始檔解決辦法

不學無數的程式設計師發表於2018-11-06

問題描述

在開發過程中我們經常會碰到要在程式碼中獲取資原始檔的情況,而我在最近將原有的Tomcat的原生專案遷移到SpringBoot專案中時碰到一個問題,就是在本地執行時,獲取本地的xml資原始檔是能夠獲取到的,但是專案打成war包然後將其部署到Tomcat中執行時,就會發生問題,報找不到資原始檔的錯誤。然後經過尋找排查確定了是下面程式碼通過ClassLoader獲取路徑的時候出錯了。

ExcelXmlModelFactory.class.getClassLoader().getResource("template/").getPath()

複製程式碼

我的資原始檔存放路徑如下

SpringBoot打jar包或war包獲取不到資原始檔解決辦法

在本地中列印的日誌路徑為

/Users/hupengfei/git/lap/lap-service/out/production/resources/template

複製程式碼

但是在將SpringBoot打包成war包部署到Tomcat中時列印的目錄為

/home/app/lap/app/lap-service-1.0.0-SNAPSHOT.war!/WEB-INF/classes!/template/

複製程式碼

可以看到在Linux中無法直接訪問未經解壓的檔案,所以就會找不到檔案。

解決辦法

通過ClassLoadergetResourceAsStream()方法獲取其流,就能夠獲取到

讀取jar裡面的檔案,我們只能用流去讀取,不能用File

獲取資源的兩種方式

通常在開發過程中會碰到讀取配置檔案的問題,一般有兩種方式進行讀取。一種是Class.getResource(String path),一種是ClassLoader.getResource(String path),這兩種雖然都能讀取檔案,但是在path的填寫上有一點點的不同。

Class.getResource

  • path以/開頭:則是從ClassPath根下獲取
  • path不以/開頭:預設是從此類所在的包下取資源

下面有個例子

public class Test {
    public static void main(String[] args) {
    	 System.out.println(Test.class.getResource("/"));
        System.out.println(Test.class.getResource(""));
    }
}

複製程式碼

輸出如下

file:/Users/hupengfei/git/Test/out/production/classes/
file:/Users/hupengfei/git/Test/out/production/classes/Practice/Day13/
複製程式碼

那麼如果在resource下有三個資原始檔

SpringBoot打jar包或war包獲取不到資原始檔解決辦法

那麼該怎麼獲取這三個檔案呢,因為在class資料夾中的目錄結構如下

-- classes
	-- Convience
	-- Practice
	-- Test
複製程式碼

所以如果想要獲取Test下的資原始檔,就如下的獲取方法

System.out.println(Test.class.getResource("../../Test/1.xml"));
System.out.println(Test.class.getResource("/Test/1.xml"));
複製程式碼

ClassLoader.getResource

ClassLoader.getResource的path中不能以/開頭,path是預設是從根目錄下進行讀取的

例子如下

System.out.println(Test.class.getClassLoader().getResource(""));
System.out.println(Test.class.getClassLoader().getResource("/"));
複製程式碼

列印如下

file:/Users/hupengfei/git/Test/out/production/classes/
null
複製程式碼

從上面例子我們可以看到

Test.class.getClassLoader().getResource("")=Test.class.getResource("/")

兩個獲取資原始檔的差別

其實檢視Class.getResource中可以看到

	public java.net.URL getResource(String name) {
        name = resolveName(name);
        ClassLoader cl = getClassLoader0();
        if (cl==null) {
            // A system class.
            return ClassLoader.getSystemResource(name);
        }
        return cl.getResource(name);
    }

複製程式碼

他最後呼叫的還是ClassLoader.getResource這個方法,那麼為什麼會有path的差別呢,因為其resolveName方法中對傳的/進行了解析,解析為了空字串。

    private String resolveName(String name) {
        if (name == null) {
            return name;
        }
        if (!name.startsWith("/")) {
            Class<?> c = this;
            while (c.isArray()) {
                c = c.getComponentType();
            }
            String baseName = c.getName();
            int index = baseName.lastIndexOf('.');
            if (index != -1) {
                name = baseName.substring(0, index).replace('.', '/')
                    +"/"+name;
            }
        } else {
            name = name.substring(1);
        }
        return name;
    }

複製程式碼

可以看到在這穿進去的為/

SpringBoot打jar包或war包獲取不到資原始檔解決辦法

傳出的是

SpringBoot打jar包或war包獲取不到資原始檔解決辦法

參考文章

相關文章