从 Java 类路径加载资源的 URL [英] URL to load resources from the classpath in Java
问题描述
在 Java 中,您可以使用相同的 API 但使用不同的 URL 协议加载各种资源:
In Java, you can load all kinds of resources using the same API but with different URL protocols:
file:///tmp.txt
http://127.0.0.1:8080/a.properties
jar:http://www.foo.com/bar/baz.jar!/COM/foo/Quux.class
这很好地将资源的实际加载与需要该资源的应用程序分离,而且由于 URL 只是一个字符串,因此资源加载也很容易配置.
This nicely decouples the actual loading of the resource from the application that needs the resource, and since a URL is just a String, resource loading is also very easily configurable.
是否有使用当前类加载器加载资源的协议?这类似于 Jar 协议,只是我不需要知道资源来自哪个 jar 文件或类文件夹.
Is there a protocol to load resources using the current classloader? This is similar to the Jar protocol, except that I do not need to know which jar file or class folder the resource is coming from.
我可以使用 Class.getResourceAsStream("a.xml")
,当然,但这需要我使用不同的 API,因此需要更改现有代码.我希望能够在所有可以为资源指定 URL 的地方使用它,只需更新一个属性文件.
I can do that using Class.getResourceAsStream("a.xml")
, of course, but that would require me to use a different API, and hence changes to existing code. I want to be able to use this in all places where I can specify a URL for the resource already, by just updating a property file.
推荐答案
介绍和基本实现
首先,您至少需要一个 URLStreamHandler.这实际上会打开到给定 URL 的连接.请注意,这被简单地称为Handler
;这允许您指定 java -Djava.protocol.handler.pkgs=org.my.protocols
并且它会自动被拾取,使用简单"包名称作为支持的协议(在这种情况下类路径").
Intro and basic Implementation
First up, you're going to need at least a URLStreamHandler. This will actually open the connection to a given URL. Notice that this is simply called Handler
; this allows you to specify java -Djava.protocol.handler.pkgs=org.my.protocols
and it will automatically be picked up, using the "simple" package name as the supported protocol (in this case "classpath").
new URL("classpath:org/my/package/resource.extension").openConnection();
代码
package org.my.protocols.classpath;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
/** A {@link URLStreamHandler} that handles resources on the classpath. */
public class Handler extends URLStreamHandler {
/** The classloader to find resources from. */
private final ClassLoader classLoader;
public Handler() {
this.classLoader = getClass().getClassLoader();
}
public Handler(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
protected URLConnection openConnection(URL u) throws IOException {
final URL resourceUrl = classLoader.getResource(u.getPath());
return resourceUrl.openConnection();
}
}
启动问题
如果你和我一样,你不想依赖启动中设置的属性来帮助你到达某个地方(就我而言,我喜欢保留我的选择像 Java WebStart 一样打开 - 这就是为什么我需要所有这些).Launch issues
If you're anything like me, you don't want to rely on a property being set in the launch to get you somewhere (in my case, I like to keep my options open like Java WebStart - which is why I need all this).如果你控制了代码,你就可以做到
If you control the code, you can do
new URL(null, "classpath:some/package/resource.extension", new org.my.protocols.classpath.Handler(ClassLoader.getSystemClassLoader()))
这将使用您的处理程序打开连接.
and this will use your handler to open the connection.
但同样,这并不令人满意,因为您不需要 URL 来执行此操作 - 您想要执行此操作是因为某些您无法(或不想)控制的库需要 url...
But again, this is less than satisfactory, as you don't need a URL to do this - you want to do this because some lib you can't (or don't want to) control wants urls...
最终的选择是注册一个 URLStreamHandlerFactory
来处理整个 jvm 的所有 url:
The ultimate option is to register a URLStreamHandlerFactory
that will handle all urls across the jvm:
package my.org.url;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.HashMap;
import java.util.Map;
class ConfigurableStreamHandlerFactory implements URLStreamHandlerFactory {
private final Map<String, URLStreamHandler> protocolHandlers;
public ConfigurableStreamHandlerFactory(String protocol, URLStreamHandler urlHandler) {
protocolHandlers = new HashMap<String, URLStreamHandler>();
addHandler(protocol, urlHandler);
}
public void addHandler(String protocol, URLStreamHandler urlHandler) {
protocolHandlers.put(protocol, urlHandler);
}
public URLStreamHandler createURLStreamHandler(String protocol) {
return protocolHandlers.get(protocol);
}
}
要注册处理程序,请使用您配置的工厂调用 URL.setURLStreamHandlerFactory()
.然后像第一个示例一样执行 new URL("classpath:org/my/package/resource.extension")
就可以了.
To register the handler, call URL.setURLStreamHandlerFactory()
with your configured factory. Then do new URL("classpath:org/my/package/resource.extension")
like the first example and away you go.
请注意,每个 JVM 只能调用此方法一次,并注意 Tomcat 将使用此方法注册 JNDI 处理程序 (AFAIK).尝试码头(我会);最坏的情况是,您可以先使用该方法,然后它必须在您身边工作!
Note that this method may only be called once per JVM, and note well that Tomcat will use this method to register a JNDI handler (AFAIK). Try Jetty (I will be); at worst, you can use the method first and then it has to work around you!
我将其发布到公共领域,并询问如果您想修改您在某处启动 OSS 项目并在此处评论详细信息.更好的实现是有一个 URLStreamHandlerFactory
,它使用 ThreadLocal
s 为每个 Thread.currentThread().getContextClassLoader 存储
.我什至会给你我的修改和测试类.URLStreamHandler
s()
I release this to the public domain, and ask that if you wish to modify that you start a OSS project somewhere and comment here with the details. A better implementation would be to have a URLStreamHandlerFactory
that uses ThreadLocal
s to store URLStreamHandler
s for each Thread.currentThread().getContextClassLoader()
. I'll even give you my modifications and test classes.
这篇关于从 Java 类路径加载资源的 URL的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!