簡體   English   中英

用於從Java中的類路徑加載資源的URL

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

發布問題

如果你和我一樣,你不想依賴於在發布中設置的屬性來讓你到達某個地方(就我而言,我喜歡像Java WebStart一樣保持我的選擇 - 這就是為什么需要這一切)。

解決方法/增強

手動代碼處理程序規范

如果你控制代碼,你可以做到

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

這將使用您的處理程序打開連接。

但同樣,這不太令人滿意,因為你不需要一個URL來做到這一點 - 你想這樣做是因為你不能(或不想)控制的某些lib需要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)。 試試碼頭(我會); 在最壞的情況下,您可以先使用該方法,然后它必須解決您的問題!

執照

我將其發布到公共領域,並詢問您是否希望修改它在某處啟動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.URLStreamHandlerjava.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/980442http://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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM