我发现列出资源文件夹内文件的唯一选项是使用nio的Filesystem概念,因为它可以将jar文件作为文件系统加载。但这带来了两个主要问题:
- java.nio使用java Stream API,我无法从scala代码中收集:
Collectors.toList()
无法编译,因为它无法确定正确的类型。
- 文件系统需要不同的基本路径用于操作系统文件系统和基于jar文件的文件系统。因此,我需要手动区分这两种情况并进行测试和基于jar的运行。
如果需要,请首先惰性加载jar文件系统
private static FileSystem jarFileSystem;
static synchronized private FileSystem getJarFileAsFilesystem(String drg_file_root) throws URISyntaxException, IOException {
if (jarFileSystem == null) {
jarFileSystem = FileSystems.newFileSystem(ConfigFiles.class.getResource(drg_file_root).toURI(), Collections.emptyMap());
}
return jarFileSystem;
}
接下来,我们需要通过检查URL的协议并返回路径来判断是否在jar文件内部。 (jar文件内部的协议将为
jar:
)
static Path getPathForResource(String resourceFolder, String filename) throws IOException, URISyntaxException {
URL url = ConfigFiles.class.getResource(resourceFolder + "/" + filename);
return "file".equals(url.getProtocol())
? Paths.get(url.toURI())
: getJarFileAsFilesystem(resourceFolder).getPath(resourceFolder, filename);
}
最后,将其列出并收集到Java列表中。
static List<Path> listPathsFromResource(String resourceFolder, String subFolder) throws IOException, URISyntaxException {
return Files.list(getPathForResource(resourceFolder, subFolder))
.filter(Files::isRegularFile)
.sorted()
.collect(toList());
}
只有这样我们才能回到 Scala 并获取它
class SpecReader {
def readSpecMessage(spec: String): String = {
List("CN", "DO", "KF")
.flatMap(ConfigFiles.listPathsFromResource(s"/spec_$spec", _).asScala.toSeq)
.flatMap(path ⇒ Source.fromInputStream(Files.newInputStream(path), "UTF-8").getLines())
.reduce(_ + " " + _)
}
}
object Main {
def main(args: Array[String]): Unit = {
System.out.println(new SpecReader().readSpecMessage(args.head))
}
}
我在这里放了一个运行中的小项目,以证明它的可行性:
https://github.com/kurellajunior/list-files-from-resource-directory
但当然,这还远非最佳方案。我希望消除上述两个缺点,即:
- 仅限于scala文件
- 在我的生产库中没有额外的测试代码
Files.list(…)
方法失败是因为toURU
中的 URI 实际上指向了协议jar:…
。 - JangetClass.getResource("/").toURI
一直返回file:/opt/spark/conf/
(我正在使用spark),这使得FileSystem抛出异常。我设法使用了这种方法:https://dev59.com/1FzUa4cB1Zd3GeqP7umu#32557217。有点丑陋,但似乎可以工作。 - TrebledJ