[踩雷紀錄] 在 build 成 jar 的 Java 中讀在 jar 中的檔案: URI is not hierarchical
2021, Apr 27
標題看起來有點繞口,不過這個問題倒真的令人困擾,主要困擾在於開發時完全沒有問題,但是到了 build 成 jar 之後,狀況才會出現,也造成要除錯十分麻煩,必須改完 code 然後 build 成 jar 才能確定問題,也沒辦法用 debugger
問題描述
- 問題主要發生於下面程式碼:
URL url = this.getClass().getResource("file/");
if (url == null) {
url = this.getClass().getClassLoader().getResource("file/");
}
File sourceDir = new File(url.toURI());
List<File> files = Files.walk(sourceDir.toPath())
.filter(p -> p.getFileName().toString().endsWith(".csv"))
.map(Path::toFile).collect(Collectors.toList());
主要需求為讀取專案 src/main/resources/file/
底下的所有 .csv
檔,以上程式碼在開發階段都沒有問題,但一旦 build 成 jar 就會吐錯誤 URI is not hierarchical
發生在 url.toURI()
查了幾篇網路文章有講到讀取 jar 中的 resources 不能用 getResource()
要用 getResourceAsStream()
的方式才能讀到檔案,測試過後的確可以使用,但 getResourceAsStream()
不能讀取 jar 中的目錄結構,對於我的需求是讀取整個目錄下的內容會有點問題,以此為出發點發展出下面解方
解決方案
List<File> files = new ArrayList<>();
URL jar = this.getClass().getProtectionDomain().getCodeSource().getLocation();
ZipInputStream zip = new ZipInputStream(jar.openStream());
ZipEntry ze;
while ((ze = zip.getNextEntry()) != null) {
String entryName = ze.getName();
if (entryName.endsWith(".csv")) {
String path = jar + entryName.split("classes/")[1];
InputStream inputStream = this.getClass().getResourceAsStream(path);
if (inputStream == null) {
inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
}
File file = File.createTempFile("temp", ".csv");
try (FileOutputStream out = new FileOutputStream(file)) {
IOUtils.copy(inputStream, out);
}
files.add(file);
inputStream.close();
}
}
有點土法煉鋼的方式,從上面得知 getResourceAsStream()
可以讀取 jar 中的檔案所以重點在於如何定位到裡面的檔案
因此用 this.getClass().getProtectionDomain().getCodeSource().getLocation()
去取得 jar 的位置,然後用 ZipInputStream
拿到裡面的目錄結構,再將 jar 的位置以及目錄結構組合成完整路徑,透過 getResourceAsStream()
拿到檔案的 InputStream
最後根據需求轉換成 File
,這才成功解掉這個問題