此文要从SpringBoot打包后不能读取classpath下文件说起

举报
码农飞哥 发表于 2021/05/29 00:06:50 2021/05/29
【摘要】 您好,我是码农飞哥,感谢您阅读本文!如果此文对您有所帮助,请毫不犹豫的给个一键三连吧。 此文要从SpringBoot打成jar之后不能读取classpath下文件说起,并由此作为一个切入点,思考如何正确的读取jar包中的文件。 文章目录 问题复现问题思考问题解决测试结果总结结尾彩蛋源码 问题复现 事情是这样的,昨天快下班了时候,测试小姐姐突然说(P...

您好,我是码农飞哥,感谢您阅读本文!如果此文对您有所帮助,请毫不犹豫的给个一键三连吧。

此文要从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]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

很明显这是一个无效的文件路径,根据这个错误路径程序肯定不能找到指定的文件咯。
再回头定位到报错的代码

  //读取文件的路径 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));

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

这段代码在本地调试的时候明明是没有问题的呀,单元测试都跑过了,本地调试时输入的路径如下:。
在这里插入图片描述
打印的路径地址是: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中文件,那么该通过何种方式来获取呢?这里有两种写法。

  1. 通过ClassPathResource获取输入流的方式
 InputStream is = new ClassPathResource("img/qr_code-icon.png").getInputStream();

  
 
  • 1
  1. 通过getResourceAsStream方法获取输入流
  InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream("img/qr_code-icon.png");

  
 
  • 1
  • 2

可以看出上面两种都是直接获取文件流的方式获取文件,那么问题来了,为啥这种方式可以呢?因为在jar文件中不能直接通过资源路径的方式获取文件,但是可以在jar包中拿到文件流。
在这里插入图片描述

测试结果

在这里插入图片描述

总结

本文从SpringBoot打成jar之后不能读取classpath下文件说起,介绍了为啥打成jar之后不能通过路径的方式访问classpath下的文件,接着说明了如何处理这个问题,最后介绍了通过流的方式来处理这个问题。

结尾彩蛋

  1. Thread.currentThread().getContextClassLoader().getResource("").getPath() 这种写法在通过War运行的项目(比如一个Sping MVC项目)中获取classpath下的文件有没有问题呢?欢迎知道的小伙伴积极留言。
  2. 通过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(); } } }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

文章来源: feige.blog.csdn.net,作者:码农飞哥,版权归原作者所有,如需转载,请联系作者。

原文链接:feige.blog.csdn.net/article/details/115936771

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。