您好,我是碼農飛哥,感謝您閱讀本文!如果此文對您有所幫助,請毫不猶豫的給個一鍵三連吧,
此文要從SpringBoot打成jar之后不能讀取classpath下檔案說起,并由此作為一個切入點,思考如何正確的讀取jar包中的檔案,
文章目錄
- 問題復現
- 問題思考
- 問題解決
- 測驗結果
- 總結
- 結尾彩蛋
- 原始碼
問題復現
事情是這樣的,昨天快下班了時候,測驗小姐姐突然說(PS: 有些測驗小姐姐上班的時候不提啥BUG,下班的時候給你提一堆BUG,不知道大家有沒有這種感覺),

有個圖片下載的介面不能用了,害,本想6點鐘下班走人的我瞬間懵逼了,這下走不了,老規矩,先查錯誤日志,
還是那個熟悉的FileNotFoundException例外,
2021-04-21 09:43:59.715 INFO 16896 --- [nio-8383-exec-1] com.jay.ImgDownloadController : qr_code-icon path is: file:/D:/workspace/file_dow
nload_demo/target/file_download_demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/img/qr_code-icon.png
java.io.FileNotFoundException: file:\D:\workspace\file_download_demo\target\file_download_demo-0.0.1-SNAPSHOT.jar!\BOOT-INF\classes!\img\qr_code-icon.
png (檔案名、目錄名或卷標語法不正確,)
at java.io.FileInputStream.open0(Native Method) ~[na:1.8.0_60]
at java.io.FileInputStream.open(Unknown Source) ~[na:1.8.0_60]
at java.io.FileInputStream.<init>(Unknown Source) ~[na:1.8.0_60]
at com.jay.ImgDownloadController.downloadImage(ImgDownloadController.java:35) ~[classes!/:0.0.1-SNAPSHOT]
很明顯這是一個無效的檔案路徑,根據這個錯誤路徑程式肯定不能找到指定的檔案咯,
再回頭定位到報錯的代碼
//讀取檔案的路徑
String path = Thread.currentThread().getContextClassLoader().getResource("").getPath()
+ "img/qr_code-icon.png";
LOGGER.info("qr_code-icon path is: " + path);
InputStream is = new FileInputStream(new File(path));
這段代碼在本地除錯的時候明明是沒有問題的呀,單元測驗都跑過了,本地除錯時輸入的路徑如下:,

列印的路徑地址是:D:/workspace/file_download_demo/target/classes/img/qr_code-icon.png
這個路徑是一個有效的路徑,
這又是一個我本地明明沒問題,到服務器就有問題了,鍋是甩不出去了

問題思考
我們都知道JAVA是一門靜態語言,先編譯再運行也就是先將java檔案編譯成class檔案,然后在用虛擬機來執行class檔案的,SpringBoot在編譯打包后會生成target目錄,class檔案,資源檔案還有jar包都會被放在這個目錄下,如下圖所示:

其中所有的class檔案以及資源檔案都放在了classes檔案夾中,在本地運行時 Thread.currentThread().getContextClassLoader().getResource("").getPath()
其中Thread.currentThread().getContextClassLoader()回傳的是當前執行緒的類加載器(默認是AppClassLoader類加載器),類加載器可以加載類也可以加載資源,類加載器有很多,具體可以參考雙親委派模型以及SpringFactoriesLoader詳解(最全最簡單的介紹)
讀取到的路徑是D:/workspace/file_download_demo/target/classes,classes檔案夾所在的路徑也就是我們熟悉的classpath 路徑 ,
而通過jar包來運行時,上面的代碼讀取的是jar的絕對路徑,而jar是一個壓縮包,直接讀取其包內的絕對路徑是有問題的, 也就是會報上面的錯誤,
問題解決
既然不能通過路徑的方式來獲取jar中檔案,那么該通過何種方式來獲取呢?這里有兩種寫法,
- 通過ClassPathResource獲取輸入流的方式
InputStream is = new ClassPathResource("img/qr_code-icon.png").getInputStream();
- 通過getResourceAsStream方法獲取輸入流
InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("img/qr_code-icon.png");
可以看出上面兩種都是直接獲取檔案流的方式獲取檔案,那么問題來了,為啥這種方式可以呢?因為在jar檔案中不能直接通過資源路徑的方式獲取檔案,但是可以在jar包中拿到檔案流,

測驗結果

總結
本文從SpringBoot打成jar之后不能讀取classpath下檔案說起,介紹了為啥打成jar之后不能通過路徑的方式訪問classpath下的檔案,接著說明了如何處理這個問題,最后介紹了通過流的方式來處理這個問題,
結尾彩蛋
Thread.currentThread().getContextClassLoader().getResource("").getPath()這種寫法在通過War運行的專案(比如一個Sping MVC專案)中獲取classpath下的檔案有沒有問題呢?歡迎知道的小伙伴積極留言,- 通過ResouceUtils.getFile()的方式能不能獲取到classpath下檔案呢?
原始碼
@RestController
public class ImgDownloadController {
private static final Logger LOGGER = LoggerFactory.getLogger(ImgDownloadController.class);
/**
* 圖片下載介面
*
* @param response
* @Author xiang.wei
*/
@RequestMapping("/download/image")
public void errorDownloadImage(HttpServletResponse response) throws IOException {
//讀取檔案的路徑
String path = Thread.currentThread().getContextClassLoader().getResource("").getPath()
+ "img/qr_code-icon.png";
LOGGER.info("qr_code-icon path is: " + path);
InputStream is = new FileInputStream(new File(path));
downloadFile(is, "qr_code-icon.png", response);
}
@RequestMapping("/correct/download/image1")
public void correctDownloadImage1(HttpServletResponse response) throws IOException {
InputStream is = new ClassPathResource("img/qr_code-icon.png").getInputStream();
downloadFile(is, "qr_code-icon.png", response);
}
@RequestMapping("/correct/download/image2")
public void correctDownloadImage2(HttpServletResponse response) throws IOException {
InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("img/qr_code-icon.png");
downloadFile(is, "qr_code-icon.png", response);
}
private void downloadFile(InputStream is, String name, HttpServletResponse response) throws IOException {
//設定回應頭 "application/octet-stream"
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader("Content-Disposition", "attachment;filename=" + java.net.URLDecoder.decode(name, "ISO-8859-1"));
//輸出流自動關閉
try (OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
os.flush();
}
} finally {
if (is != null) {
is.close();
}
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/278848.html
標籤:java
上一篇:【每日藍橋】60、一八年省賽Java組真題“全球變暖”
下一篇:GUI三種布局管理器(java)
