在Java中从类path加载资源的URL

在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只是一个string,所以资源加载也很容易configuration。

有使用当前类加载器加载资源的协议吗? 这与Jar协议类似,只是我不需要知道资源来自哪个jar文件或类文件夹。

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

介绍和基本实施

首先,你将需要至less一个URLStreamHandler。 这实际上会打开与给定URL的连接。 注意这只是简单的叫做Handler ; 这允许你指定java -Djava.protocol.handler.pkgs=org.my.protocols ,它会自动被选中,使用“simple”包名作为支持的协议(在本例中是“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一样打开我的选项 – 这就是为什么需要所有这些)。

解决方法/增强

手动代码处理程序规范

如果你控制代码,你可以做

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

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

但是,再次,这是不太令人满意的,因为你不需要一个URL来做到这一点 – 你想这样做,因为一些库你不能(或不想)控制想要的url…

JVM处理程序注册

最终的select是注册一个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); } } 

要注册处理程序,请使用您configuration的工厂调用URL.setURLStreamHandlerFactory() 。 然后像第一个例子那样做new URL("classpath:org/my/package/resource.extension") ,然后离开。

JVM处理程序注册问题

请注意,这个方法只能被每个JVM调用一次,并且注意Tomcat将使用这个方法来注册一个JNDI处理程序(AFAIK)。 尝试docker(我会); 在最坏的情况下,你可以先使用这个方法,然后就可以解决你的问题了!

执照

我将其发布到公共领域,并询问如果您希望修改,请在某处启动OSS项目,并在此处提供详细信息。 更好的实现是使用一个使用ThreadLocalURLStreamHandler来为每个Thread.currentThread().getContextClassLoader()存储URLStreamHandler 我甚至会给你我的修改和testing课程。

 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(); } } 

因此,您可以通过最简单的方式来执行此操作。 🙂 java.net.URL将始终使用系统属性中的当前值。

我认为这是值得的自己的答案 – 如果你使用Spring,你已经有了这个

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

就像春季文件中所解释的那样,在skaffman的评论中指出。

我已经创build了一个类来帮助减less设置自定义处理程序时的错误,并利用系统属性,所以在调用方法或者不在正确的容器中没有问题。 如果你弄错了,也有一个exception类:

 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 ); } } 

(类似于Azder的答案 ,但略有不同的机智。)

我不相信类path中的内容是预定义的协议处理程序。 (所谓的classpath:协议)。

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

本文介绍如何实现自定义stream处理程序: http : //java.sun.com/developer/onlineTraining/protocolhandlers/ 。

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

 /** * 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; } 

Dilums的答案的扩展:

在不改变代码的情况下,您可能需要按照Dilum的build议,追踪URL相关接口的自定义实现。 为了简化您的工作,我可以推荐查看Spring框架资源的来源。 虽然代码不是以stream处理程序的forms出现的,但它已经被devise成完全符合ASL 2.0许可证的要求,使其足够友好,足以在代码中重复使用,并具有适当的功劳。

启发@Stephen https://stackoverflow.com/a/1769454/980442和http://docstore.mik.ua/orelly/java/exp/ch09_06.htm

使用

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

只需将该类创build到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系统属性。

仅供参考: http : //docs.oracle.com/javase/7/docs/api/java/net/URL.html#URL( java.lang.String,%20java.lang.String,%20int,% 20java.lang 。串)

我不知道是否有一个,但你可以自己轻松。

不同的协议示例在我看来就像一个门面模式。 当每个案例有不同的实现时,你有一个共同的接口。

你可以使用相同的原理,创build一个从你的属性文件中获取string的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:从string的开始,然后决定以哪种方式加载资源,并只给你资源。

我尝试避免URL类,而是依赖于URI 。 因此,对于需要URL地方,我希望做Spring资源,比如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(); } } 

要创build一个URI,你可以使用URI.create(..) 。 这种方法也更好,因为您控制将执行资源查找的ClassLoader

我注意到一些其他的答案试图parsingURL作为一个string来检测scheme。 我认为它更好地传递URI并用它来parsing。

我刚才提出了一个问题,Spring Source恳求他们把他们的资源代码从core分离出来,这样你就不需要其他所有的Spring东西了。