URL从Java中的类路径加载资源

在Java中,你可以使用相同的API但使用不同的URL协议加载各种资源:

file:///tmp.txt
http://127.0.0.1:8080/a.properties
jar:http://www.foo.com/bar/baz.jar!/COM/foo/Quux.class

这很好地将资源的实际加载与需要资源的应用程序分离开来,而且由于URL只是一个字符串,资源加载也非常容易配置。

是否存在使用当前类加载器加载资源的协议? 这类似于Jar协议,除了我不需要知道资源来自哪个Jar文件或类文件夹

当然,我可以使用Class.getResourceAsStream("a.xml")来做到这一点,但这需要我使用不同的API,因此需要对现有代码进行更改。我希望能够在所有可以为资源指定URL的地方使用它,只需更新属性文件即可。

329301 次浏览

我不知道是否已经有了,但你可以很容易地自己做。

在我看来,不同协议的例子就像外观模式。当每种情况都有不同的实现时,您就有了一个公共接口。

您可以使用相同的原理,创建一个ResourceLoader类,它从属性文件中获取字符串,并检查我们的自定义协议

myprotocol:a.xml
myprotocol:file:///tmp.txt
myprotocol:http://127.0.0.1:8080/a.properties
myprotocol:jar:http://www.foo.com/bar/baz.jar!/COM/foo/Quux.class

从字符串开始剥离myprotocol,然后决定用哪种方式加载资源,只给你资源。

(类似于Azder的回答,但策略略有不同。)

我认为对于类路径中的内容没有预定义的协议处理程序。(所谓的classpath:协议)。

但是,Java允许您添加自己的协议。这是通过提供具体的实现java.net.URLStreamHandlerjava.net.URLConnection来实现的。

这篇文章描述了如何实现一个自定义流处理程序: http://java.sun.com/developer/onlineTraining/protocolhandlers/ < / > . < / p >

Dilums的回答的扩展:

在不更改代码的情况下,您可能需要按照Dilum的建议,使用URL相关接口的自定义实现。为了简化事情,我建议查看Spring框架的资源的源代码。虽然代码不是流处理程序的形式,但它的设计完全是为了做您想做的事情,并且是在ASL 2.0许可下进行的,这使得它足够友好,可以在您的代码中重用。

介绍和基本实现

首先,您至少需要一个URLStreamHandler。这实际上会打开到给定URL的连接。注意,这被简单地称为Handler;这允许你指定java -Djava.protocol.handler.pkgs=org.my.protocols,它将自动被拾取,使用“简单”包名作为支持的协议(在本例中为“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();
}
}

__abc0如果你和我一样,你不想依赖于在启动时设置的属性来到达某个地方(在我的情况下,我喜欢像Java WebStart那样保持我的选项开放——这就是为什么需要所有这些)。

解决方案/增强

手动代码处理程序规范

如果你能控制代码,你就能做到

new URL(null, "classpath:some/package/resource.extension", new org.my.protocols.classpath.Handler(ClassLoader.getSystemClassLoader()))

这将使用您的处理程序打开连接。

但同样,这不是令人满意的,因为你不需要一个URL来做这件事——你想这样做是因为一些你不能(或不想)控制的库想要URL…

JVM Handler注册

最终的选项是注册一个URLStreamHandlerFactory,它将处理jvm中的所有url:

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"),然后继续。

JVM处理器注册问题

请注意,每个JVM只能调用该方法一次,并且请注意Tomcat将使用此方法注册JNDI处理程序(AFAIK)。试试Jetty(我会的);在最坏的情况下,您可以先使用方法,然后它必须在您周围工作!

许可证

我将此发布到公共领域,并询问如果您希望修改,您将在某处启动OSS项目并在这里评论详细信息。更好的实现是有一个URLStreamHandlerFactory,它使用__abc1为每个Thread.currentThread().getContextClassLoader()存储__abc2。我甚至会给你我的修改和测试课程。

URL url = getClass().getClassLoader().getResource("someresource.xxx");

这样就行了。

你也可以在启动时以编程方式设置属性:

final String key = "java.protocol.handler.pkgs";
String newValue = "org.my.protocols";
if (System.getProperty(key) != null) {
final String previousValue = System.getProperty(key);
newValue += "|" + previousValue;
}
System.setProperty(key, newValue);

使用这个类:

package org.my.protocols.classpath;


import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;


public class Handler extends URLStreamHandler {


@Override
protected URLConnection openConnection(final URL u) throws IOException {
final URL resourceUrl = ClassLoader.getSystemClassLoader().getResource(u.getPath());
return resourceUrl.openConnection();
}
}

这样你就得到了侵入性最小的方法。url将始终使用系统属性中的当前值。

我创建了一个类,它有助于减少设置自定义处理程序的错误,并利用系统属性,因此不存在首先调用方法或不在正确容器中的问题。如果你做错了,还有一个异常类:

CustomURLScheme.java:
/*
* The CustomURLScheme class has a static method for adding cutom protocol
* handlers without getting bogged down with other class loaders and having to
* call setURLStreamHandlerFactory before the next guy...
*/
package com.cybernostics.lib.net.customurl;


import java.net.URLStreamHandler;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
* Allows you to add your own URL handler without running into problems
* of race conditions with setURLStream handler.
*
* To add your custom protocol eg myprot://blahblah:
*
* 1) Create a new protocol package which ends in myprot eg com.myfirm.protocols.myprot
* 2) Create a subclass of URLStreamHandler called Handler in this package
* 3) Before you use the protocol, call CustomURLScheme.add(com.myfirm.protocols.myprot.Handler.class);
* @author jasonw
*/
public class CustomURLScheme
{


// this is the package name required to implelent a Handler class
private static Pattern packagePattern = Pattern.compile( "(.+\\.protocols)\\.[^\\.]+" );


/**
* Call this method with your handlerclass
* @param handlerClass
* @throws Exception
*/
public static void add( Class<? extends URLStreamHandler> handlerClass ) throws Exception
{
if ( handlerClass.getSimpleName().equals( "Handler" ) )
{
String pkgName = handlerClass.getPackage().getName();
Matcher m = packagePattern.matcher( pkgName );


if ( m.matches() )
{
String protocolPackage = m.group( 1 );
add( protocolPackage );
}
else
{
throw new CustomURLHandlerException( "Your Handler class package must end in 'protocols.yourprotocolname' eg com.somefirm.blah.protocols.yourprotocol" );
}


}
else
{
throw new CustomURLHandlerException( "Your handler class must be called 'Handler'" );
}
}


private static void add( String handlerPackage )
{
// this property controls where java looks for
// stream handlers - always uses current value.
final String key = "java.protocol.handler.pkgs";


String newValue = handlerPackage;
if ( System.getProperty( key ) != null )
{
final String previousValue = System.getProperty( key );
newValue += "|" + previousValue;
}
System.setProperty( key, newValue );
}
}




CustomURLHandlerException.java:
/*
* Exception if you get things mixed up creating a custom url protocol
*/
package com.cybernostics.lib.net.customurl;


/**
*
* @author jasonw
*/
public class CustomURLHandlerException extends Exception
{


public CustomURLHandlerException(String msg )
{
super( msg );
}


}

我认为这是值得回答的问题——如果您正在使用Spring,那么您已经有了这个功能

Resource firstResource =
context.getResource("http://www.google.fi/");
Resource anotherResource =
context.getResource("classpath:some/resource/path/myTemplate.txt");

就像在spring文档中解释的那样,并在skaffman的评论中指出。

当然,使用注册URLStreamHandlers的解决方案是最正确的,但有时需要最简单的解决方案。所以,我使用下面的方法:

/**
* Opens a local file or remote resource represented by given path.
* Supports protocols:
* <ul>
* <li>"file": file:///path/to/file/in/filesystem</li>
* <li>"http" or "https": http://host/path/to/resource - gzipped resources are supported also</li>
* <li>"classpath": classpath:path/to/resource</li>
* </ul>
*
* @param path An URI-formatted path that points to resource to be loaded
* @return Appropriate implementation of {@link InputStream}
* @throws IOException in any case is stream cannot be opened
*/
public static InputStream getInputStreamFromPath(String path) throws IOException {
InputStream is;
String protocol = path.replaceFirst("^(\\w+):.+$", "$1").toLowerCase();
switch (protocol) {
case "http":
case "https":
HttpURLConnection connection = (HttpURLConnection) new URL(path).openConnection();
int code = connection.getResponseCode();
if (code >= 400) throw new IOException("Server returned error code #" + code);
is = connection.getInputStream();
String contentEncoding = connection.getContentEncoding();
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip"))
is = new GZIPInputStream(is);
break;
case "file":
is = new URL(path).openStream();
break;
case "classpath":
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path.replaceFirst("^\\w+:", ""));
break;
default:
throw new IOException("Missed or unsupported protocol in path '" + path + "'");
}
return is;
}

灵感来自@Stephen https://stackoverflow.com/a/1769454/980442http://docstore.mik.ua/orelly/java/exp/ch09_06.htm < / p >

使用

new URL("classpath:org/my/package/resource.extension").openConnection()

只需要将这个类创建到sun.net.www.protocol.classpath包中,并将其运行到Oracle JVM实现中就可以了。

package sun.net.www.protocol.classpath;


import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;


public class Handler extends URLStreamHandler {


@Override
protected URLConnection openConnection(URL u) throws IOException {
return Thread.currentThread().getContextClassLoader().getResource(u.getPath()).openConnection();
}
}

如果您正在使用另一个JVM实现,请设置java.protocol.handler.pkgs=sun.net.www.protocol系统属性。

< p >供参考: URL http://docs.oracle.com/javase/7/docs/api/java/net/URL.html(以% 20以% 20 int, % 20以) < / p >

我试图避免URL类,而是依赖于URI。因此,对于需要URL的事情,我想要做Spring资源查找,我做以下工作:

public static URL toURL(URI u, ClassLoader loader) throws MalformedURLException {
if ("classpath".equals(u.getScheme())) {
String path = u.getPath();
if (path.startsWith("/")){
path = path.substring("/".length());
}
return loader.getResource(path);
}
else if (u.getScheme() == null && u.getPath() != null) {
//Assume that its a file.
return new File(u.getPath()).toURI().toURL();
}
else {
return u.toURL();
}
}

要创建URI,可以使用URI.create(..)。这种方式也更好,因为你控制了将进行资源查找的ClassLoader

我注意到其他一些答案试图将URL解析为字符串以检测该方案。我认为最好是传递URI并使用它来解析。

< a href = " https://jira.spring。io/browse/SPR-12508" rel="nofollow">我实际上在前一段时间与Spring Source一起提交了一个问题,请求他们将他们的资源代码从core中分离出来,这样你就不需要所有其他的Spring东西了

在Spring Boot应用程序中,我使用以下方法来获取文件URL,

Thread.currentThread().getContextClassLoader().getResource("PromotionalOfferIdServiceV2.wsdl")

如果你在类路径中有tomcat,它就像这样简单:

TomcatURLStreamHandlerFactory.register();

这将为“war”和“classpath”协议注册处理程序。

从Java 9+及以上版本开始,你可以定义一个新的URLStreamHandlerProviderURL类使用服务加载器框架在运行时加载它。

创建一个提供者:

package org.example;


import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.spi.URLStreamHandlerProvider;


public class ClasspathURLStreamHandlerProvider extends URLStreamHandlerProvider {


@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
if ("classpath".equals(protocol)) {
return new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return ClassLoader.getSystemClassLoader().getResource(u.getPath()).openConnection();
}
};
}
return null;
}


}

META-INF/services目录中创建一个名为java.net.spi.URLStreamHandlerProvider的文件,其内容如下:

org.example.ClasspathURLStreamHandlerProvider

现在URL类将在看到如下内容时使用提供程序:

URL url = new URL("classpath:myfile.txt");