SpringBoot 讀取jar包下resource中的資料夾
前段時間,在基於springboot開發過程中,遇到一個問題:程式需要讀取resource下的某個目錄的全部檔案,而且需要能以File的方式讀取。但是, 打包後,springboot專案就成了jar包了,讀取檔案,會報錯: ... cannot be resolved to absolute file path because it does not reside in the file system:jar:file: .... 。
一般情況下,我們讀取resource下的某個檔案,可以這樣通過IO流的方式讀取:
Resource resource =new ClassPathResource(fileName); InputStream is = resource.getInputStream();
當時,有些時候,需要使用File:resource.getFile() ,這時,在jar包下就會報上面的錯誤。可是,如果是資料夾怎麼辦呢?
經過網上的一番查詢,確定了一個方案:把jar包中的檔案,先存到一個臨時資料夾下,然後通過臨時資料夾,可以實現以File的方式讀取,檔案讀取完成後,刪除臨時檔案目錄。
檔案複製程式碼如下:
import lombok.extern.log4j.Log4j2; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import java.io.*; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @Log4j2 public class FileUtils { /** * 複製檔案到目標目錄 * @param resourcePath resource的資料夾路徑 * @param tmpDir 臨時目錄 * @param fileType 檔案型別 */ public static void copyJavaResourceDir2TmpDir(String resourcePath, String tmpDir, FileType fileType) { Map<String, Object> fileMap = new HashMap<>(); if (resourcePath.endsWith("/")) { resourcePath = resourcePath.substring(0, resourcePath.lastIndexOf("/")); } try { Enumeration resources = null; try { resources = Thread.currentThread().getContextClassLoader().getResources(resourcePath); } catch (Exception ex) { ex.printStackTrace(); } if (resources == null || !resources.hasMoreElements()) { resources = FileUtils.class.getClassLoader().getResources(resourcePath); } while (resources.hasMoreElements()) { URL resource = (URL) resources.nextElement(); if (resource.getProtocol().equals("file")) { // resource是檔案 continue; } String[] split = resource.toString().split(":"); String filepath = split[2]; if (OperatingSystem.isWindows()) { filepath = filepath + ":" + split[3]; } String[] split2 = filepath.split("!"); String zipFileName = split2[0]; ZipFile zipFile = new ZipFile(zipFileName); Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = (ZipEntry) entries.nextElement(); String entryName = entry.getName(); if (entry.isDirectory()) { continue; } if (entryName.contains(resourcePath) && entryName.endsWith(fileType.toString().toLowerCase())) { String dir = entryName.substring(0, entryName.lastIndexOf("/")); if (!dir.endsWith(resourcePath)) { // 目標路徑含有子目錄 dir = dir.substring(dir.indexOf(resourcePath) + resourcePath.length() + 1); if (dir.contains("/")) { // 多級子目錄 String[] subDir = dir.split("/"); Map<String, Object> map = fileMap; for (String d : subDir) { map = makeMapDir(map, d); } map.putAll(readOneFromJar(zipFile.getInputStream(entry), entryName)); } else { //一級子目錄 if (fileMap.get(dir) == null) { fileMap.put(dir, new HashMap<String, Object>()); } ((Map<String, Object>) fileMap.get(dir)) .putAll(readOneFromJar(zipFile.getInputStream(entry), entryName)); } } else { // 目標路徑不含子目錄 fileMap.putAll(readOneFromJar(zipFile.getInputStream(entry), entryName)); } } } } } catch (Exception e) { log.error("讀取resource檔案異常:", e); } finally { try { // 寫到目標快取路徑 createFile(fileMap, tmpDir); } catch (Exception e) { log.error("建立臨時檔案異常:", e); } } } private static void createFile(Map<String, Object> fileMap, String targetDir) { fileMap.forEach((key, value) -> { if (value instanceof Map) { createFile((Map<String, Object>) value, targetDir + File.separator + key); } else { createNewFile(targetDir + File.separator + key, value.toString()); } }); } public static void createNewFile(String filePath, String value) { try { File file = new File(filePath); if (!file.exists()) { File parentDir = file.getParentFile(); if (!parentDir.exists()) { parentDir.mkdirs(); } file.createNewFile(); } PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "utf-8"))); out.write(value); out.flush(); out.close(); } catch (Exception e) { log.error("建立檔案異常:", e); } } private static Map<String, Object> makeMapDir(Map<String, Object> fileMap, String d) { if (fileMap.get(d) == null) { fileMap.put(d, new HashMap<String, Object>()); } return (Map<String, Object>) fileMap.get(d); } public static Map<String, String> readOneFromJar(InputStream inputStream, String entryName) { Map<String, String> fileMap = new HashMap<>(); try (Reader reader = new InputStreamReader(inputStream, "utf-8")) { StringBuilder builder = new StringBuilder(); int ch = 0; while ((ch = reader.read()) != -1) { builder.append((char) ch); } String filename = entryName.substring(entryName.lastIndexOf("/") + 1); fileMap.put(filename, builder.toString()); } catch (Exception ex) { log.error("讀取檔案異常:", ex); } return fileMap; } public enum FileType { XSD, TXT, XLS, XLSX, DOC, DOCX, XML, SQL, PROPERTIES, SH } }
程式呼叫程式碼如下:
String tmpDir = System.getProperty("user.dir") + File.separator +"tmp"; try { FileUtils.copyJavaResourceDir2TmpDir("xsd", tmpDir, FileUtils.FileType.XSD); loadXsd(new File(tmpDir)); } catch (Exception e) { logger.error("載入xsd檔案異常:", e); } finally { FileUtils.delete(tmpDir); }
這樣,就不用每次部署例項的時候,都把需要檔案和jar一起部署了,方便多了。
OperatingSystem 是Filnk中copy過來的程式碼