【问题标题】:URL to load resources from the classpath in Java从 Java 中的类路径加载资源的 URL
【发布时间】:2010-10-26 01:54:18
【问题描述】:

在 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 的所有地方使用它,只需更新属性文件。

【问题讨论】:

    标签: java url classloader


    【解决方案1】:

    我不知道是否已经有,但你可以轻松地自己制作。

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

    您可以使用相同的原理,创建一个 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:,然后决定以哪种方式加载资源,然后只为您提供资源。

    【讨论】:

    • 如果您希望第三方库使用 URL,并且您可能希望处理特定协议的资源解析,则此方法不起作用。
    【解决方案2】:

    (类似于Azder's answer,但手法略有不同。)

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

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

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

    【讨论】:

    • 您知道 JVM 附带的协议列表吗?
    【解决方案3】:

    Dilums's answer 的扩展:

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

    【讨论】:

    • 您引用的那个页面指出“没有标准化的 URL 实现可用于访问需要从类路径或相对于 ServletContext 获取的资源”,这回答了我的问题,我猜。
    • @Homeless:坚持住,年轻人。有了更多经验,您很快就会发布 cmets。
    【解决方案4】:

    介绍和基本实现

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

    用法

    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 处理程序注册

    最终的选择是注册一个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 项目并在此处评论详细信息。更好的实现是使用ThreadLocals 为每个Thread.currentThread().getContextClassLoader() 存储URLStreamHandlers 的URLStreamHandlerFactory。我什至会给你我的修改和测试类。

    【讨论】:

    • @Stephen 这正是我正在寻找的。你能和我分享你的更新吗?我可以将其作为我计划通过 Sonatype 更新和发布的 com.github.fommil.common-utils 软件包的一部分。
    • 注意,也可以使用System.setProperty()注册协议。喜欢System.setProperty("java.protocol.handler.pkgs", "org.my.protocols");
    • Java 9+ 有一个更简单的方法:stackoverflow.com/a/56088592/511976
    【解决方案5】:
    URL url = getClass().getClassLoader().getResource("someresource.xxx");
    

    应该这样做。

    【讨论】:

    • "当然,我可以使用 Class.getResourceAsStream("a.xml") 来做到这一点,但这需要我使用不同的 API,因此需要更改现有代码。我想成为只需更新属性文件,就可以在我已经可以为资源指定 URL 的所有地方使用它。”
    • -1 正如 Thilo 所指出的,这是 OP 考虑并拒绝的。
    • getResource 和 getResourceAsStream 是不同的方法。同意 getResourceAsStream 不适合 API,但 getResource 返回一个 URL,这正是 OP 要求的。
    • @romacafe:是的,你是对的。这是一个很好的替代解决方案。
    • OP 要求提供属性文件解决方案,但其他人也因为问题的标题而来到这里。他们喜欢这种动态解决方案:)
    【解决方案6】:

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

    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 将始终使用系统属性中的当前值。

    【讨论】:

    • 只有在处理程序旨在处理尚未“已知”的协议(例如gopher://)时,才能使用添加额外包以查找java.protocol.handler.pkgs 系统变量的代码。如果打算覆盖“流行”协议,如file://http://,那么这样做可能为时已晚,因为java.net.URL#handlers map 已经为该协议添加了一个“标准”处理程序。所以唯一的出路就是把这个变量传递给JVM。
    【解决方案7】:

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

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

    【讨论】:

      【解决方案8】:

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

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

      就像spring documentation 中解释的那样,并在 skaffman 的 cmets 中指出。

      【讨论】:

      【解决方案9】:

      注册 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;
      }
      

      【讨论】:

        【解决方案10】:

        灵感来自@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.String)

        【讨论】:

          【解决方案11】:

          我尽量避免使用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 解析为字符串以检测方案的答案。我认为最好传递 URI 并使用它来解析。

          I have actually filed an issue a while ago with Spring Source begging them to separate out their Resource code from core so that you don't need all the other Spring stuff.

          【讨论】:

            【解决方案12】:

            在 Spring Boot 应用程序中,我使用以下内容获取文件 URL,

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

            【讨论】:

            • 这也适用于纯 Java(使用 Java14 测试,在 Maven 项目中,资源位于 src/main/resources)。
            【解决方案13】:

            如果你的类路径上有tomcat,就这么简单:

            TomcatURLStreamHandlerFactory.register();
            

            这将为“战争”和“类路径”协议注册处理程序。

            【讨论】:

              【解决方案14】:

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

              【讨论】:

                猜你喜欢
                • 2011-03-27
                • 2011-04-22
                • 1970-01-01
                • 1970-01-01
                • 2020-09-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-09-23
                相关资源
                最近更新 更多