java从jar包中读取资源文件

刺骨的言语ヽ痛彻心扉 2022-02-28 13:36 750阅读 0赞

楔子

一个JAVA项目需要在应用启动时读取一个文件,在这遇到了一个坑,就是在Idea 中,应用启动时可以正常读取这个文件,但应用打成jar包后直接运行就读取不到。
要读取的文件位于/src/main/resources目录下,其相对路径为/src/main/resources/HotleAllCity.json如下图所示:
在这里插入图片描述

IDE中读取

IDE中的读取方式是,先获取文件的路径,然后读取文件

  1. // path = Resources.getResource(fileName).getPath();
  2. try
  3. {
  4. // classpath:fileName
  5. path = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + fileName)
  6. .getAbsoluteFile().getPath();
  7. }
  8. catch (FileNotFoundException e)
  9. {
  10. BaseLog.getDailyLog().info(fileName + "文件不存在:", e);
  11. throw new ServiceException(PublicErrorCodeEnum.ERR_SERVER_ERROR.getCode(), fileName + "文件不存在");
  12. }
  13. JsonUtil.readFileToList(path , City.class);

IDE中编译后的目录为:
在这里插入图片描述
从上图我们可以看到,开发时期的项目里,src/main/下面的java和resources文件夹都被(编译)打包到了生产包的classes/目录下.

这个时候,我们可以看到classes这个文件夹,它就是我们要找的classpath

举例:

  • classpath:Mapper.xml使用classpath:这种前缀,就只能代表一个文件,表示只会到你的class路径中查找第一个匹配的文件。
  • classpath*:**/mapper/mapping/*Mapper.xml,使用classpath*:这种前缀,则可以代表多个匹配的文件,不仅包含class路径,还包括jar文件中(class路径)进行查找。**/mapper/mapping/*Mapper.xml,双星号**表示在任意目录下,也就是说在classes/下任意层的目录,只要符合后面的文件路径,都会被作为资源文件找到。
    classpath的讲解详解下小结。

以上代码在IDE中能够完美的运行。但是在jar包中就会如下的错误:
在这里插入图片描述
错误的意思是在文件系统中找不到json文件,但是文件的路径是对的,该文件在lib中的jar包中,jar包中的文件如下所示:
在这里插入图片描述
代码之所以不能读取Jar包中的文件,是因为整个jar包时一个文件,所以里边的东西你是无法再文件系统中访问的。

什么是classpath

在java项目中,你一定碰到过classpath,通常情况下,我们是用它来指定配置/资源文件的路径。
顾名思义,classpath就是class的path,也就是类文件(*.class的路径),CLASSPATH环境变量的作用是用来指定Java程序搜索类的路径的。
安装完JDK后我们通常需要配置环境变量CLASSPATH,通常配置为.;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar
,它包含了当前目录.
在这里插入图片描述

  • dt.jar:运行环境类库,主要是Swing包,这一点通过用压缩软件打开dt.jar也可以看到。如果在开发时候没有用到Swing包,那么可以不用将dt.jar添加到CLASSPATH变量中。
    在这里插入图片描述
  • tools.jar:工具类库,它跟我们程序中用到的基础类库没有关系。我们注意到在Path中变量值bin目录下的各个exe工具的大小都很小,一般都在27KB左右,这是因为它们实际上仅仅相当于是一层代码的包装,这些工具的实现所要用到的类库都在tools.jar中,用压缩软件打开tools.jar,你会发现有很多文件是和bin目录下的exe工具相对性的,如下图:
    在这里插入图片描述
    在这里插入图片描述

读取jar包中的文件

之所以不能读取Jar包中的文件,这主要是因为jar包是一个单独的文件而非文件夹,绝对不可能通过file:/e:/.../ResourceJar.jar/resource /res.txt这种形式的文件URL来定位res.txt。所以即使是相对路径,也无法定位到jar文件内的txt文件。
我们可以用类装载器(ClassLoader)来读取jar包中的文件。
我们需要利用this.getClass().getResourceAsStream方法,以流的形式拿到Jar包中的文件,正确写法如下所示:

  1. BufferedReader in = new BufferedReader(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream(path)));
  2. StringBuffer buffer = new StringBuffer();
  3. String line = "";
  4. while ((line = in.readLine()) != null){
  5. buffer.append(line);
  6. }
  7. String input = buffer.toString();

ClassLoader简介

ClassLoader是一个抽象类,我们用它的实例对象来装载类 ,它负责将 Java 字节码装载到 JVM 中 , 并使其成为 JVM 一部分。 JVM 的类动态装载技术能够在运行时刻动态地加载或者替换系统的某些功能模块,而不影响系统其他功能模块的正常运行。
类装载就是寻找一个类或是一个接口的字节码文件并通过解析该字节码来构造代表这个类或是这个接口的 class 对象的过程 。

同时支持IDE与JAR包读取

在IEA中支持读取资源文件能够方便我们调试,在jar包中读取资源文件能够方便我们部署,如何两者同时支持?
一般我们在Windows下调试,在Linux下部署,所以我们可以通过判断操作系统类型来同时支持。
例如:

  1. public static List<City> getCriteriaHotCity()
  2. {
  3. if (CollectionUtils.isEmpty(criteriaHotCity))
  4. {
  5. // 通过不同的OS来进行不同方式的读取
  6. if (System.getProperty("os.name").toLowerCase().startsWith("win"))
  7. {
  8. String filePath = getCriteriaFilePath(FileName.CRITERIA_HOT_CITY_JSON);
  9. criteriaHotCity = JsonUtil.readFileToList(filePath, City.class);
  10. }
  11. else
  12. {
  13. String content = readFile(FileName.CRITERIA_HOT_CITY_JSON);
  14. criteriaHotCity = JSONObject.parseArray(content, City.class);
  15. }
  16. }
  17. return criteriaHotCity;
  18. }
  19. private static String getCriteriaFilePath(String fileName)
  20. {
  21. String path;
  22. // path = Resources.getResource(fileName).getPath();
  23. try
  24. {
  25. path = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + fileName)
  26. .getAbsoluteFile().getPath();
  27. }
  28. catch (FileNotFoundException e)
  29. {
  30. BaseLog.getDailyLog().info(fileName + "文件不存在:", e);
  31. throw new ServiceException(PublicErrorCodeEnum.ERR_SERVER_ERROR.getCode(), fileName + "文件不存在");
  32. }
  33. BaseLog.getDailyLog().info(fileName + " path: " + path);
  34. return path;
  35. }
  36. private static String readFile(String fileName)
  37. {
  38. String ret;
  39. try
  40. {
  41. ret = readJarFile(fileName);
  42. }
  43. catch (IOException e)
  44. {
  45. BaseLog.getDailyLog().error(fileName + "文件读取异常:", e);
  46. throw new ServiceException(PublicErrorCodeEnum.ERR_SERVER_ERROR.getCode(), e.getMessage());
  47. }
  48. return ret;
  49. }
  50. /** * 读取jar包中的资源文件 * @param fileName 文件名 * @return 文件内容 * @throws IOException 读取错误 */
  51. private static String readJarFile(String fileName) throws IOException
  52. {
  53. BufferedReader in = new BufferedReader(
  54. new InputStreamReader(PreloadData.class.getClassLoader().getResourceAsStream(fileName)));
  55. StringBuilder buffer = new StringBuilder();
  56. String line;
  57. while ((line = in.readLine()) != null)
  58. {
  59. buffer.append(line);
  60. }
  61. return buffer.toString();
  62. }

参考文献

  • 深入jar包:从jar包中读取资源文件
  • java项目中的classpath到底是什么

发表评论

表情:
评论列表 (有 0 条评论,750人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java读取jar文件

    java代码读取文件问题解决 问题背景 通过java代码读取文件的方式有很多种,那么我们应该能理解,不同的场景应该使用不同的读取方式,我在前段时间开发项目的时候遇到

    相关 jar读取文件

    项目打包成jar包,在代码层面获取jar包内的一些文件时,传统的获取方法因为路径或加载器的不同导致无法获取文件,参考网上一些方法,只能从jar包获取文件流,再从流中获取相关的内