Troubleshooting系列-找不到资源文件问题分析及解决

问题背景

前期针对一个应用微服务做了启动脚本的参数优化,在本地环境启动运行好好的,但是上了DEV环境后,跑到一个获取资源文件的时候报资源找不到,类似于下面的报错

java 复制代码
java.io.FileNotFoundException: aa.txt
	at com.hyw.util.ResourceUtil.getTextContent(ResourceUtil.java:27)
	at com.toby.sharding.jdbc.source.start.PointShardingApplication.main(PointShardingApplication.java:41)

启动脚本优化前期有文章讲过,具体可以参考这篇落地实践之JAVA应用启动脚本

关键启动脚本参数如下:

setjvment.sh

shell 复制代码
# 设置java.ext.dirs JAVA_OPTS="$JAVA_OPTS -Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:$work_home/lib"

start.sh

shell 复制代码
CLASSPATH=$CLASSPATH:$work_home/lib/conf

问题重现

编写相关问题代码,新增ResourceUtil用来获取资源文件

java 复制代码
public class ResourceUtil {
    private static ResourceUtil resourceUtil = new ResourceUtil();

    public static ResourceUtil getInstance() {
        return resourceUtil;
    }

    public String getTextContent(String fileName) throws FileNotFoundException {
        URL url = ResourceUtil.class.getClassLoader().getResource(fileName);
        System.out.println("default class loader=" + ResourceUtil.class.getClassLoader());
        if (Objects.isNull(url)) {
            System.out.println("default not found");
            url = ClassLoader.getSystemResource(fileName);
            System.out.println("app not found,class loader=" + ResourceUtil.class.getClassLoader());
        }
        if (Objects.isNull(url)) {
            throw new FileNotFoundException(fileName);
        }
        InputStream in = null;
        try {
            in = url.openStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        BufferedReader bf = new BufferedReader(new InputStreamReader(in));
        StringBuilder st = new StringBuilder();
        String line = "";
        try {
            while ((line = bf.readLine()) != null) {
                st.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bf.close();
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println(fileName + " context=" + st.toString());
        return st.toString();
    }

    @Test
    public void testCase01() throws FileNotFoundException {
        String content = ResourceUtil.getInstance().getTextContent("a.txt");
        System.out.println(content);
    }


}

新增了两个资源文件

直接在启动类中获取这两个资源文件

java 复制代码
try {
    ResourceUtil.getInstance().getTextContent("a.txt");
    ResourceUtil.getInstance().getTextContent("conf/b.txt");
} catch (FileNotFoundException e) {
    e.printStackTrace();
}

打包上传后在windows下运行结果如下:

ini 复制代码
default class loader=sun.misc.Launcher$ExtClassLoader@4b85612c
a.txt context=123
default class loader=sun.misc.Launcher$ExtClassLoader@4b85612c
conf/b.txt context=b.txt

在windows下运行是ok的,但是在unix下确实不行,unix下的截图无法考出来,大概运行结果如下

ini 复制代码
default class loader=sun.misc.Launcher$ExtClassLoader@4b85612c
a.txt context=123
default class loader=sun.misc.Launcher$ExtClassLoader@4b85612c
default not found
app not found,class loader=sun.misc.Launcher$AppClassLoader@4b85612c
conf/b.txt context=b.txt

使用这个class默认的classloader获取不到资源文件,打印出class loader可知是ExtClassLoader

ExtClassLoader是Java中的一个类加载器,它是Java类加载器层次结构中的一部分。其全名为"Extension Class Loader",主要用于加载Java的扩展类库。

因此,按照上述所说,ExtClassLoader不应该加载业务jar,同时可以看出a.txt其实是在另一个jar包中的,也读取不出来,说明ExtClassLoader不适合加载资源文件,具体JDK源码还没细看

修改方案

修改启动脚本,将业务使用的jar都放在app class loader下,即用-cp关联业务jar
setjvment.sh 删除设置java.ext.dirs start.sh修改如下

shell 复制代码
CLASSPATH=$CLASSPATH:$work_home/lib/conf
for file in $work_home/lib/*.jar
do
  if test -f $file
  then
    CLASSPATH=$CLASSPATH:$file
  fi
done

修改完之后运行正常,能够获取资源文件

回顾

为什么本地IDEA运行OK?

抛开系统问题,启动本地利用jps查看启动参数

ruby 复制代码
7968 com.toby.sharding.jdbc.source.start.PointShardingApplication -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:D:\tools\JetBrains\IntelliJ IDEA 2021.3.3\lib\idea_rt.jar=53161:D:\tools\JetBrains\IntelliJ IDEA 2021.3.3\bin -Dfile.encoding=UTF-8

利用arthas追踪jvm参数,应用jar和resource应该都在class-path下

因此不会有问题

加载资源的方式

有两种方式一个是Class类,另一个是ClassLoader类 具体可以参考这篇文章 Java加载资源文件的两种方法

相关推荐
康康的AI博客7 分钟前
工业数据中台:PLC、SCADA、MES的实时协同架构
java·服务器·网络
それども19 分钟前
为什么要加@ResponseBody
java·开发语言·spring boot
一只专注api接口开发的技术猿30 分钟前
微服务架构下集成淘宝商品 API 的实践与思考
java·大数据·开发语言·数据库·微服务·架构
2501_9444241240 分钟前
Flutter for OpenHarmony游戏集合App实战之记忆翻牌配对消除
android·java·开发语言·javascript·windows·flutter·游戏
李慕婉学姐40 分钟前
【开题答辩过程】以《基于Spring Boot和大数据的医院挂号系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
大数据·spring boot·后端
鹿角片ljp1 小时前
Java网络编程入门:从Socket到多线程服务器
java·服务器·网络
走进IT1 小时前
DDD项目分层结构说明
java
橙露1 小时前
嵌入式实时操作系统 FreeRTOS:任务调度与信号量的核心应用
java·大数据·服务器
愚公移码1 小时前
蓝凌EKP产品:关联机制浅析
java·服务器·前端
阿蒙Amon1 小时前
C#每日面试题-is和as的区别
java·开发语言·c#