问题背景
前期针对一个应用微服务做了启动脚本的参数优化,在本地环境启动运行好好的,但是上了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应用启动脚本
关键启动脚本参数如下:
shell
# 设置java.ext.dirs JAVA_OPTS="$JAVA_OPTS -Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:$work_home/lib"
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加载资源文件的两种方法