Java 中的递归资源收集






4.82/5 (3投票s)
Class.getResource(...)
的强大补充。
引言
在互联网上搜索 Class.getResource
会得到许多讨论 Java 资源收集函数问题的链接。本文提供的代码允许程序员通过扫描类路径(相对于根类或完全扫描),并构建与指定模式匹配的资源列表来定位他们的资源。
问题
Class.getResource
要求程序员知道他们需要的任何资源的路径和名称。然而,在某些情况下,需求可能需要通过递归扫描类路径来收集资源。
程序员很快就会发现 Java 没有现成的支持来递归扫描类路径,因为他们亲身体验到 Class.getResource
和 ClassLoader.getResources
的设计并非为了递归扫描。考虑到项目部署时,类路径是一个简单的文件系统、一组 JAR 文件或两者的混合,这个问题就变得更糟!
解决方案
Java 允许递归扫描的一种用例。当使用 java.io.FileFilter
接口在使用 java.io.File
类扫描文件系统时接受或丢弃文件时,就会发生这种情况。因此,让我们从一个类似的过滤器接口开始,但是这个接口将接受或丢弃 URL 而不是文件,因为资源通常被 Java 平台视为 URL(或当它们被打开时的 InputStreams
)。
ResourceURLFilter
接口非常简单
import java.net.URL;
public interface ResourceURLFilter {
public boolean accept(URL resourceUrl);
}
接下来,必须设计一个类来利用 ResourceURLFilter
实例来收集 URL
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import java.security.*;
public class Resources {
}
Resources
类将需要访问处理 I/O、URL 和 JAR 文件的 Java 库,还需要访问 Java Security 包中的一个类;稍后会详细介绍。
反向工作,可以将收集单个 URL 的方法添加到 Resources
类
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import java.security.*;
public class Resources {
private static void collectURL(ResourceURLFilter f, Set<URL> s, URL u) {
if (f == null || f.accept(u)) {
s.add(u);
}
}
}
该方法被声明为 private
和 static
,因为它是一个辅助方法,不应被程序员直接使用。这个想法是咨询 ResourceURLFilter
实例,如果没有提供过滤器或者过滤器接受 URL,则将其添加到提供的集合中。
以下两种方法需要更多的代码,因为它们迭代文件系统上的文件夹以及作为类路径一部分的 JAR 文件。 这是文件系统方法
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import java.security.*;
public class Resources {
private static void collectURL(ResourceURLFilter f, Set<URL> s, URL u) {
if (f == null || f.accept(u)) {
s.add(u);
}
}
private static void iterateFileSystem(File r, ResourceURLFilter f,
Set<URL> s) throws MalformedURLException, IOException {
File[] files = r.listFiles();
for (File file: files) {
if (file.isDirectory()) {
iterateFileSystem(file, f, s);
} else if (file.isFile()) {
collectURL(f, s, file.toURI().toURL());
}
}
}
}
}
接下来,我们添加迭代 JAR 文件条目的方法
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import java.security.*;
public class Resources {
private static void collectURL(ResourceURLFilter f, Set<URL> s, URL u) {
if (f == null || f.accept(u)) {
s.add(u);
}
}
private static void iterateFileSystem(File r, ResourceURLFilter f, Set<URL> s)
throws MalformedURLException, IOException {
File[] files = r.listFiles();
for (File file: files) {
if (file.isDirectory()) {
iterateFileSystem(file, f, s);
} else if (file.isFile()) {
collectURL(f, s, file.toURI().toURL());
}
}
}
}
private static void iterateJarFile(File file, ResourceURLFilter f, Set<URL> s)
throws MalformedURLException, IOException {
JarFile jFile = new JarFile(file);
for(Enumeration<JarEntry> je = jFile.entries(); je.hasMoreElements();) {
JarEntry j = je.nextElement();
if (!j.isDirectory()) {
collectURL(f, s, new URL("jar", "",
file.toURI() + "!/" + j.getName()));
}
}
}
}
这两种方法使用 collectURL(...)
方法来处理 JAR 文件条目或文件系统上的文件,但是还需要另一种方法来根据检查的条目将流程定向到其中一种方法
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import java.security.*;
public class Resources {
private static void collectURL(ResourceURLFilter f, Set<URL> s, URL u) {
if (f == null || f.accept(u)) {
s.add(u);
}
}
private static void iterateFileSystem(File r, ResourceURLFilter f, Set<URL> s)
throws MalformedURLException, IOException {
File[] files = r.listFiles();
for (File file: files) {
if (file.isDirectory()) {
iterateFileSystem(file, f, s);
} else if (file.isFile()) {
collectURL(f, s, file.toURI().toURL());
}
}
}
}
private static void iterateJarFile(File file, ResourceURLFilter f, Set<URL> s)
throws MalformedURLException, IOException {
JarFile jFile = new JarFile(file);
for(Enumeration<JarEntry> je = jFile.entries(); je.hasMoreElements();) {
JarEntry j = je.nextElement();
if (!j.isDirectory()) {
collectURL(f, s, new URL("jar", "",
file.toURI() + "!/" + j.getName()));
}
}
}
private static void iterateEntry(File p, ResourceURLFilter f, Set<URL> s)
throws MalformedURLException, IOException {
if (p.isDirectory()) {
iterateFileSystem(p, f, s);
} else if (p.isFile() && p.getName().toLowerCase().endsWith(".jar")) {
iterateJarFile(p, f, s);
}
}
}
我们现在可以添加一组 public
方法,这些方法将构成 Resources
类的 API
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import java.security.*;
public class Resources {
private static void collectURL(ResourceURLFilter f, Set<URL> s, URL u) {
if (f == null || f.accept(u)) {
s.add(u);
}
}
private static void iterateFileSystem(File r, ResourceURLFilter f,
Set<URL> s) throws MalformedURLException, IOException {
File[] files = r.listFiles();
for (File file: files) {
if (file.isDirectory()) {
iterateFileSystem(file, f, s);
} else if (file.isFile()) {
collectURL(f, s, file.toURI().toURL());
}
}
}
}
private static void iterateJarFile(File file, ResourceURLFilter f, Set<URL> s)
throws MalformedURLException, IOException {
JarFile jFile = new JarFile(file);
for(Enumeration<JarEntry> je = jFile.entries(); je.hasMoreElements();) {
JarEntry j = je.nextElement();
if (!j.isDirectory()) {
collectURL(f, s, new URL("jar", "",
file.toURI() + "!/" + j.getName()));
}
}
}
private static void iterateEntry(File p, ResourceURLFilter f, Set<URL> s)
throws MalformedURLException, IOException {
if (p.isDirectory()) {
iterateFileSystem(p, f, s);
} else if (p.isFile() && p.getName().toLowerCase().endsWith(".jar")) {
iterateJarFile(p, f, s);
}
}
public static Set<URL> getResourceURLs() throws IOException, URISyntaxException {
return getResourceURLs((ResourceURLFilter)null);
}
public static Set<URL> getResourceURLs(Class rootClass)
throws IOException, URISyntaxException {
return getResourceURLs(rootClass, (ResourceURLFilter)null);
}
public static Set<URL> getResourceURLs(ResourceURLFilter filter)
throws IOException, URISyntaxException {
Set<URL> collectedURLs = new HashSet<>();
URLClassLoader ucl = (URLClassLoader)ClassLoader.getSystemClassLoader();
for (URL url: ucl.getURLs()) {
iterateEntry(new File(url.toURI()), filter, collectedURLs);
}
return collectedURLs;
}
public static Set<URL> getResourceURLs(Class rootClass,
ResourceURLFilter filter) throws IOException, URISyntaxException {
Set<URL> collectedURLs = new HashSet<>();
CodeSource src = rootClass.getProtectionDomain().getCodeSource();
iterateEntry(new File(src.getLocation().toURI()), filter, collectedURLs);
return collectedURLs;
}
}
Using the Code
Resources
类现在可以扫描类路径并报告所有可用的 URL,或那些匹配指定过滤器的 URL。 但请注意,即使项目部署为一组 JAR 文件,也应仅在绝对必要时才执行类路径扫描。
要扫描整个类路径并将所有资源作为 URL 返回,只需调用 getResourseURLs
方法
for (URL u: Resources.getResourceURLs()) {
System.out.println(u);
}
要从加载特定类的位置开始扫描类路径,请向 getResourseURLs
方法提供根类
for (URL u: Resources.getResourceURLs(Resources.class)) {
System.out.println(u);
}
当您指定 ResourceURLFilter
时,事情会变得更有趣
for (URL u: Resources.getResourceURLs(Resources.class, new ResourceURLFilter() {
public @Override boolean accept(URL u) {
String s = u.getFile();
return s.endsWith(".class") && !s.contains("$");
}
})) {
System.out.println(u);
}
需要考虑的事项
- 由于扫描整个类路径的成本可能很高(即使您使用根类和过滤器来限制扫描也会发生这种情况),因此建议仅在需要时才使用此代码。
- 指定要从中扫描的根类将有效地将扫描限制为加载根类的 JAR 文件,前提是项目实际上是作为一组单独的 JAR 部署的。 这也意味着如果资源未与根类一起定位,则扫描可能找不到资源!
- 虽然无法将扫描限制为根类的包,但可以根据根类的包过滤 URL。
- 可以扩展代码以支持多个过滤器,每个过滤器都有自己的一组结果,但我目前将此作为留给读者的一项练习。
- 目前我不确定此代码在应用程序/Web 服务器内部如何运行,因此如果需要,我可能会进一步更新代码。
历史
- 2012 年 12 月 19 日 — 文章创建