从 Java 类路径加载资源的 URL

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/861500/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 20:21:24  来源:igfitidea点击:

URL to load resources from the classpath in Java

javaurlclassloader

提问by Thilo

In Java, you can load all kinds of resources using the same API but with different URL protocols:

在 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

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.

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

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.

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

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.

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

采纳答案by Stephen

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.protocolsand it will automatically be picked up, using the "simple" package name as the supported protocol (in this case "classpath").

首先,您至少需要一个 URLStreamHandler。这实际上将打开到给定 URL 的连接。请注意,这只是简单地称为Handler; 这允许您指定java -Djava.protocol.handler.pkgs=org.my.protocols并自动选取,使用“简单”包名称作为支持的协议(在本例中为“类路径”)。

Usage

用法

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

Code

代码

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

Launch issues

启动问题

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

Workarounds/Enhancements

解决方法/增强功能

Manual code Handler specification

手动代码 Handler 规范

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.

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

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...

但同样,这并不令人满意,因为您不需要 URL 来执行此操作 - 您想要这样做是因为某些您无法(或不想)控制的库需要 url...

JVM Handler registration

JVM 处理程序注册

The ultimate option is to register a URLStreamHandlerFactorythat will handle all urls across the jvm:

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

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.

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

JVM Handler Registration Issue

JVM 处理程序注册问题

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!

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

License

执照

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 URLStreamHandlerFactorythat uses ThreadLocals to store URLStreamHandlers for each Thread.currentThread().getContextClassLoader(). I'll even give you my modifications and test classes.

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

回答by Azder

I dont know if there is one already, but you can make it yourself easilly.

我不知道是否已经有一个,但你可以很容易地自己制作。

That different protocols example looks to me like a facade pattern. You have a common interface when there are different implementations for each case.

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

You could use the same principle, make a ResourceLoader class which takes the string from your properties file, and checks for a custom protocol of ours

您可以使用相同的原理,创建一个 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

strips the myprotocol: from the start of the string and then makes a decision of which way to load the resource, and just gives you the resource.

从字符串的开头剥离 myprotocol: ,然后决定以哪种方式加载资源,然后只为您提供资源。

回答by Dilum Ranatunga

(Similar to Azder's answer, but a slightly different tact.)

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

I don't believe there is a predefined protocol handler for content from the classpath. (The so-called classpath:protocol).

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

However, Java does allow you to add your own protocols. This is done through providing concrete implementations java.net.URLStreamHandlerand java.net.URLConnection.

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

This article describes how a custom stream handler can be implemented: http://java.sun.com/developer/onlineTraining/protocolhandlers/.

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

回答by DavidValeri

An extension to Dilums's answer:

Dilums 答案的扩展:

Without changing code, you likely need pursue custom implementations of URL related interfaces as Dilum recommends. To simplify things for you, I can recommend looking at the source for Spring Framework's Resources. While the code is not in the form of a stream handler, it has been designed to do exactly what you are looking to do and is under the ASL 2.0 license, making it friendly enough for re-use in your code with due credit.

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

回答by Rasin

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

That should do it.

那应该这样做。

回答by subes

You can also set the property programmatically during startup:

您还可以在启动期间以编程方式设置属性:

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

Using this class:

使用这个类:

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

Thus you get the least intrusive way to do this. :) java.net.URL will always use the current value from the system properties.

因此,您可以获得最少侵入性的方式来做到这一点。:) java.net.URL 将始终使用系统属性中的当前值。

回答by Jason Wraxall

I've created a class which helps to reduce errors in setting up custom handlers and takes advantage of the system property so there are no issues with calling a method first or not being in the right container. There's also an exception class if you get things wrong:

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

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

}

回答by eis

I think this is worth its own answer - if you're using Spring, you already have this with

我认为这值得自己回答 - 如果您使用的是 Spring,那么您已经拥有了

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

Like explained in the spring documentationand pointed out in the comments by skaffman.

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

回答by domax

Solution with registering URLStreamHandlers is most correct, of course, but sometimes the simplest solution is needed. So, I use the following method for that:

当然,注册 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+):.+$", "").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;
}

回答by Daniel De León

Inspire by @Stephen https://stackoverflow.com/a/1769454/980442and http://docstore.mik.ua/orelly/java/exp/ch09_06.htm

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

To use

使用

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

just create this class into sun.net.www.protocol.classpathpackage and run it into Oracle JVM implementation to work like a charm.

只需将这个类创建到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();
    }
}

In case you are using another JVM implementation set the java.protocol.handler.pkgs=sun.net.www.protocolsystem property.

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

FYI: http://docs.oracle.com/javase/7/docs/api/java/net/URL.html#URL(java.lang.String,%20java.lang.String,%20int,%20java.lang.String)

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