[英]URL to load resources from the classpath in 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只是一個String,因此資源加載也很容易配置。
是否有使用當前類加載器加載資源的協議? 這與Jar協議類似,不同之處在於我不需要知道資源來自哪個jar文件或類文件夾。
當然,我可以使用Class.getResourceAsStream("a.xml")
來做到這一點,但這需要我使用不同的API,因此需要更改現有代碼。 我希望能夠在所有我可以通過更新屬性文件指定資源URL的地方使用它。
首先,您至少需要一個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();
}
}
如果你控制代碼,你可以做到
new URL(null, "classpath:some/package/resource.extension", new org.my.protocols.classpath.Handler(ClassLoader.getSystemClassLoader()))
這將使用您的處理程序打開連接。
但同樣,這不太令人滿意,因為你不需要一個URL來做到這一點 - 你想這樣做是因為你不能(或不想)控制的某些lib需要url ...
最終的選擇是注冊一個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只能調用一次,並且請注意Tomcat將使用此方法注冊JNDI處理程序(AFAIK)。 試試碼頭(我會); 在最壞的情況下,您可以先使用該方法,然后它必須解決您的問題!
我將其發布到公共領域,並詢問您是否希望修改它在某處啟動OSS項目並在此處評論詳細信息。 更好的實現方法是使用一個URLStreamHandlerFactory
,它使用ThreadLocal
為每個Thread.currentThread().getContextClassLoader()
存儲URLStreamHandler
。 我甚至會給你我的修改和測試課程。
URL url = getClass().getClassLoader().getResource("someresource.xxx");
應該這樣做。
我認為這是值得的答案 - 如果你使用的是Spring,你已經擁有了它
Resource firstResource =
context.getResource("http://www.google.fi/");
Resource anotherResource =
context.getResource("classpath:some/resource/path/myTemplate.txt");
就像在spring文檔中解釋並在skaffman的評論中指出的那樣。
您還可以在啟動期間以編程方式設置屬性:
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將始終使用系統屬性中的當前值。
(類似於Azder的答案 ,但是機智略有不同。)
我不相信有來自類路徑的內容的預定義協議處理程序。 (所謂的classpath:
協議)。
但是,Java確實允許您添加自己的協議。 這是通過提供具體實現java.net.URLStreamHandler
和java.net.URLConnection
。
本文介紹如何實現自定義流處理程序: http://java.sun.com/developer/onlineTraining/protocolhandlers/ 。
我創建了一個類,它有助於減少設置自定義處理程序時的錯誤,並利用系統屬性,因此首先調用方法或不在正確的容器中沒有問題。 如果你弄錯了,還有一個例外類:
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 );
}
}
靈感來自@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()
只需將此類創建到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 。串)
注冊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;
}
從Java 9+及更高版本開始,您可以定義新的URLStreamHandlerProvider
。 URL
類使用服務加載器框架在運行時加載它。
創建提供者:
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");
我不知道是否已有,但你可以自己做到容易。
不同的協議示例在我看來就像一個外觀模式。 當每種情況有不同的實現時,您有一個通用接口。
您可以使用相同的原則,創建一個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:從字符串的開頭,然后決定加載資源的方式,並給你資源。
Dilums答案的延伸:
在不改變代碼的情況下,您可能需要像Dilum推薦的那樣追求URL相關接口的自定義實現。 為了簡化您的工作,我建議您查看Spring Framework資源的來源。 雖然代碼不是流處理程序的形式,但它的設計完全符合您的要求,並且符合ASL 2.0許可,使其足夠友好,可以在您的代碼中重復使用並獲得應有的信用。
在Spring Boot應用程序中,我使用以下內容來獲取文件URL,
Thread.currentThread().getContextClassLoader().getResource("PromotionalOfferIdServiceV2.wsdl")
如果你在類路徑上有tomcat,它就像這樣簡單:
TomcatURLStreamHandlerFactory.register();
這將注冊“war”和“classpath”協議的處理程序。
我試圖避免使用URL
類,而是依賴URI
。 因此,對於需要URL
東西,我想在Spring中執行Spring Resource,我會執行以下操作:
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解析為String來檢測方案。 我認為最好傳遞URI並使用它來解析。
我實際上已經提交了一個問題,Spring Source請求他們將他們的資源代碼從core
分離出來,這樣你就不需要所有其他Spring的東西了。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.