Spring ClasspathResource FileNotFoundException 的原因
Causes of Spring ClasspathResource FileNotFoundException
我的网络应用程序使用 Spring 4.3.5。我们需要从 jar 本身加载一个存储在我们的 JAR 类路径之一中的 Freemarker 模板。
让我尽量概括一下我的场景(稍后我会提供代码)
Jar 结构
- 来源
- /com/acme/package/ComponentUsingResource.java
- 元信息
- 资源
- template1.ftl.html
- 模板1.ft2.html
jar 包含在 WAR 应用程序的 WEB-INF/lib 中。
来自 ComponentUsingResource.java 我使用以下语句加载资源:new ClasspathResource("META-INF/templates/template1.ftl.html").getInputStream()
.
完全相同的 WAR 应用程序在我的笔记本电脑上运行(JDK 8 121,Tomcat 8.0.43),在我们的 SIT 环境中(JDK 8 >100 , Tomcat 8.0.39), 在 Tomcat 8.0.36 上安装在我的笔记本电脑上,但它在我们的客户网站上不起作用 (JDK 8 96, Tomcat 8.0.36).加载该 ClasspathResource
时由于 FileNotFoundException 而不起作用
当应用程序启动时,@Autowired 依赖链进入我的基于资源的 bean 的初始化
Arrays.asList("ftt-mail-natixclose-it", "ftt-mail-natixflussi-it", "ftt-mail-processreport-en", "ftt-mail-processreport-it",
"ftt-mail-regclosed-en", "ftt-mail-regclosed-it", "ftt-mail-regnotclosed-multi-en", "ftt-mail-regnotclosed-multi-it",
"ftt-mail-regnotclosed-single-en", "ftt-mail-regnotclosed-single-it", "ftt-mail-timestamps-en", "ftt-mail-timestamps-it",
"ftt-mail-missingdata-multi-en", "ftt-mail-missingdata-multi-it", "ftt-mail-missingdata-single-en",
"ftt-mail-missingdata-single-it", "ftt-mail-natixflussi-it")
.parallelStream().forEach(templateName -> {
ClassPathResource classpathResource = new ClassPathResource("META-INF/templates/" + templateName + ".ftl.html");
try (Reader reader = new InputStreamReader(classpathResource.getInputStream(), Charset.forName("utf-8")))
{
freemarker.template.Template tmpl = new freemarker.template.Template(templateName, reader, configuration);
cacheTemplate.put(templateName, tmpl);
}
catch (IOException e)
{
throw new RuntimeException("Errror processing template " + templateName, e);
}
});
我知道在没有适当的 Java 同步的情况下从 ParallelStream
写入缓存是有问题的做法,稍后会解决。
问题是在所有这些资源中,代码找不到一个:"ftt-mail-regclosed-en.ftl.html"。它在客户的 SIT 以外的任何地方加载资源。由于这是并行流,我猜这是唯一有问题的资源。
根例外是:java.io.FileNotFoundException: class path resource [META-INF/templates/ftt-mail-regclosed-en.ftl.html] cannot be opened because it does not exist
我无法提供完整的堆栈跟踪,因为技术上我无法在此处复制和粘贴片段。
我已经对 jar 文件的内容进行了三重检查,所有预期的资源都已到位。我要求客户(请记住,WAR 是二进制相同的!)进行相同的检查,但他们没有解压缩它,而是向我提供了 jar 文件的部分二进制转储的屏幕截图,我可以在其中看到该文件名字。
我知道该应用程序已经过Black Duck 扫描程序针对软件漏洞进行了处理。它经常报告可疑的依赖关系,但我(也不是客户)没有关于 Black Duck 剥离资源的证据或知识。我这样说是因为在我们的线程中,一位技术人员怀疑 BD 可能已经从包中剥离了资源。我不太可能。
我还可以采取哪些其他步骤来调查此问题?是什么导致仅在单一环境中出现此类 FileNotFoundException?
请注意,这一点非常重要:与早期版本的软件的其他部分使用的语法完全相同 new ClasspathResource("META-INF/something")
。以前的版本包含这种加载资源的方法,工作正常。这意味着它从包中嵌入的其他 jar 加载了自己的类路径资源
这是一个潜在的答案。我无法重现该问题。
当您使用 Spring 调用 new ClassPathResource
时,您没有设置 class 加载程序。我假设 Spring 使用系统 classloader (ClassLoader.getSystemClassLoader()
).
错了。 Spring 使用内部实用程序方法获取绑定到线程的当前上下文的 class 加载程序。由于我不知道的原因,绑定到 bean 初始化方法的 class 加载程序(碰巧是由 Apache Tomcat 赞助的 WebApplicationClassLoader
)可能与系统 class 加载程序。
Oracle系统class加载器,实际上是无法从Jar文件中加载资源的。至少因为我自己调试过,找到了答案。
Maybe,我说maybe,去掉并行,不节省2秒的一次性启动处理,使得Lambda表达式能够得到web应用classloader 实例而不是 Oracle JDK.
但是,这并不能解释为什么这只发生在客户现场而不是我们所有的环境中,包括 PROD。
解决方案 2 是将 getClass().getClassLoader()
显式传递给 ClassPathResource
的构造函数
我的网络应用程序使用 Spring 4.3.5。我们需要从 jar 本身加载一个存储在我们的 JAR 类路径之一中的 Freemarker 模板。
让我尽量概括一下我的场景(稍后我会提供代码)
Jar 结构
- 来源
- /com/acme/package/ComponentUsingResource.java
- 元信息
- 资源
- template1.ftl.html
- 模板1.ft2.html
- 资源
jar 包含在 WAR 应用程序的 WEB-INF/lib 中。
来自 ComponentUsingResource.java 我使用以下语句加载资源:new ClasspathResource("META-INF/templates/template1.ftl.html").getInputStream()
.
完全相同的 WAR 应用程序在我的笔记本电脑上运行(JDK 8 121,Tomcat 8.0.43),在我们的 SIT 环境中(JDK 8 >100 , Tomcat 8.0.39), 在 Tomcat 8.0.36 上安装在我的笔记本电脑上,但它在我们的客户网站上不起作用 (JDK 8 96, Tomcat 8.0.36).加载该 ClasspathResource
时由于 FileNotFoundException 而不起作用当应用程序启动时,@Autowired 依赖链进入我的基于资源的 bean 的初始化
Arrays.asList("ftt-mail-natixclose-it", "ftt-mail-natixflussi-it", "ftt-mail-processreport-en", "ftt-mail-processreport-it",
"ftt-mail-regclosed-en", "ftt-mail-regclosed-it", "ftt-mail-regnotclosed-multi-en", "ftt-mail-regnotclosed-multi-it",
"ftt-mail-regnotclosed-single-en", "ftt-mail-regnotclosed-single-it", "ftt-mail-timestamps-en", "ftt-mail-timestamps-it",
"ftt-mail-missingdata-multi-en", "ftt-mail-missingdata-multi-it", "ftt-mail-missingdata-single-en",
"ftt-mail-missingdata-single-it", "ftt-mail-natixflussi-it")
.parallelStream().forEach(templateName -> {
ClassPathResource classpathResource = new ClassPathResource("META-INF/templates/" + templateName + ".ftl.html");
try (Reader reader = new InputStreamReader(classpathResource.getInputStream(), Charset.forName("utf-8")))
{
freemarker.template.Template tmpl = new freemarker.template.Template(templateName, reader, configuration);
cacheTemplate.put(templateName, tmpl);
}
catch (IOException e)
{
throw new RuntimeException("Errror processing template " + templateName, e);
}
});
我知道在没有适当的 Java 同步的情况下从 ParallelStream
写入缓存是有问题的做法,稍后会解决。
问题是在所有这些资源中,代码找不到一个:"ftt-mail-regclosed-en.ftl.html"。它在客户的 SIT 以外的任何地方加载资源。由于这是并行流,我猜这是唯一有问题的资源。
根例外是:java.io.FileNotFoundException: class path resource [META-INF/templates/ftt-mail-regclosed-en.ftl.html] cannot be opened because it does not exist
我无法提供完整的堆栈跟踪,因为技术上我无法在此处复制和粘贴片段。
我已经对 jar 文件的内容进行了三重检查,所有预期的资源都已到位。我要求客户(请记住,WAR 是二进制相同的!)进行相同的检查,但他们没有解压缩它,而是向我提供了 jar 文件的部分二进制转储的屏幕截图,我可以在其中看到该文件名字。
我知道该应用程序已经过Black Duck 扫描程序针对软件漏洞进行了处理。它经常报告可疑的依赖关系,但我(也不是客户)没有关于 Black Duck 剥离资源的证据或知识。我这样说是因为在我们的线程中,一位技术人员怀疑 BD 可能已经从包中剥离了资源。我不太可能。
我还可以采取哪些其他步骤来调查此问题?是什么导致仅在单一环境中出现此类 FileNotFoundException?
请注意,这一点非常重要:与早期版本的软件的其他部分使用的语法完全相同 new ClasspathResource("META-INF/something")
。以前的版本包含这种加载资源的方法,工作正常。这意味着它从包中嵌入的其他 jar 加载了自己的类路径资源
这是一个潜在的答案。我无法重现该问题。
当您使用 Spring 调用 new ClassPathResource
时,您没有设置 class 加载程序。我假设 Spring 使用系统 classloader (ClassLoader.getSystemClassLoader()
).
错了。 Spring 使用内部实用程序方法获取绑定到线程的当前上下文的 class 加载程序。由于我不知道的原因,绑定到 bean 初始化方法的 class 加载程序(碰巧是由 Apache Tomcat 赞助的 WebApplicationClassLoader
)可能与系统 class 加载程序。
Oracle系统class加载器,实际上是无法从Jar文件中加载资源的。至少因为我自己调试过,找到了答案。
Maybe,我说maybe,去掉并行,不节省2秒的一次性启动处理,使得Lambda表达式能够得到web应用classloader 实例而不是 Oracle JDK.
但是,这并不能解释为什么这只发生在客户现场而不是我们所有的环境中,包括 PROD。
解决方案 2 是将 getClass().getClassLoader()
显式传递给 ClassPathResource