Java'da sınıf yolundan kaynak yüklemek için URL


197

Java'da, aynı API'yı kullanarak ancak farklı URL protokolleriyle her türlü kaynağı yükleyebilirsiniz:

file:///tmp.txt
http://127.0.0.1:8080/a.properties
jar:http://www.foo.com/bar/baz.jar!/COM/foo/Quux.class

Bu, kaynağın asıl yüklenmesini kaynağa ihtiyaç duyan uygulamadan güzel bir şekilde ayırır ve bir URL yalnızca bir Dize olduğundan, kaynak yüklemesi de kolayca yapılandırılabilir.

Geçerli sınıf yükleyiciyi kullanarak kaynakları yüklemek için bir protokol var mı? Kaynağın hangi jar dosyası veya sınıf klasöründen geldiğini bilmem dışında, bu Jar protokolüne benzer.

Bunu Class.getResourceAsStream("a.xml")elbette kullanarak yapabilirim , ancak bu farklı bir API kullanmamı gerektirecek ve dolayısıyla mevcut kodda değişiklikler yapacaktır. Bunu, yalnızca bir özellik dosyasını güncelleyerek, kaynak için bir URL belirtebildiğim tüm yerlerde kullanabilmek istiyorum.

Yanıtlar:


348

Giriş ve temel uygulama

Öncelikle, en azından bir URLStreamHandler'a ihtiyacınız olacak. Bu aslında belirli bir URL'ye bağlantıyı açar. Bunun basit bir şekilde adlandırıldığına dikkat edin Handler; bu, belirtmenize izin verir java -Djava.protocol.handler.pkgs=org.my.protocolsve desteklenen protokol olarak "basit" paket adı kullanılarak (bu durumda "sınıf yolu") otomatik olarak alınacaktır.

kullanım

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

kod

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

Başlatma sorunları

Neden -, bir yere almak için piyasaya bir özellik olmaktan sette güvenmek istemiyorum benim gibiyseniz şey sen ise (benim durumumda, benim seçenekleri Java WebStart gibi açık tutmak ister ben bütün bu ihtiyaç ).

Geçici Çözümler / Geliştirmeleri

Manuel kod İşleyici özellikleri

Kodu kontrol ederseniz,

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

ve bu bağlantıyı açmak için işleyicinizi kullanır.

Ama yine de, bunu yapmak için bir URL'ye ihtiyacınız olmadığından, bu tatmin edici olmaktan daha az - bunu yapmak istiyorsunuz çünkü kontrol edemediğiniz (veya istemediğiniz) bazı lib'ler URL istiyor ...

JVM İşleyici kaydı

Nihai seçenek, URLStreamHandlerFactoryjvm'deki tüm URL'leri işleyecek bir kayıt yapmaktır:

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

İşleyiciyi kaydetmek URL.setURLStreamHandlerFactory()için yapılandırılmış fabrika ile arayın . O zaman new URL("classpath:org/my/package/resource.extension")ilk örneği beğen ve git.

JVM İşleyici Kayıt Sorunu

Bu yöntemin JVM başına yalnızca bir kez çağrılabileceğini ve Tomcat'in bir JNDI işleyicisini (AFAIK) kaydetmek için bu yöntemi kullanacağını unutmayın. İskele deneyin (ben olacağım); en kötüsü, önce yöntemi kullanabilirsiniz ve daha sonra etrafınızda çalışmak zorunda!

Lisans

Bunu kamu malı olarak yayınlıyorum ve değiştirmek istediğinizde bir yerde bir OSS projesi başlatmanızı ve burada ayrıntılarla yorum yapmanızı rica ediyorum. Daha iyi bir uygulama bir var olacaktır URLStreamHandlerFactorythat use ThreadLocalmağaza için s URLStreamHandlerler her biri için Thread.currentThread().getContextClassLoader(). Size modifikasyonlarımı ve test sınıflarımı bile vereceğim.


1
@Stephen tam da aradığım şey bu. Güncellemelerinizi benimle paylaşır mısınız? com.github.fommil.common-utilsSonatype aracılığıyla yakında güncellemeyi ve yayınlamayı planladığım paketimin bir parçası olarak ekleyebilirim.
fommil

5
System.setProperty()Protokolü kaydetmek için de kullanabileceğinizi unutmayın . BeğenSystem.setProperty("java.protocol.handler.pkgs", "org.my.protocols");
tsauerwein

Java 9+ daha kolay bir yönteme sahiptir: stackoverflow.com/a/56088592/511976
mhvelplund

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

Bunu yapmalı.


11
"Bunu tabii ki Class.getResourceAsStream (" a.xml ") kullanarak yapabilirim, ama bu farklı bir API kullanmamı gerektirir ve bu nedenle mevcut kod değişiklikleri. Bunu tüm yerlerde kullanabilmek istiyorum Yalnızca bir özellik dosyasını güncelleyerek kaynak için bir URL belirtebilirim. "
Thilo

3
-1 Thilo'nun işaret ettiği gibi, bu OP'nin düşündüğü ve reddettiği bir şeydir.
sleske

13
getResource ve getResourceAsStream farklı yöntemlerdir. GetResourceAsStream öğesinin API'ye uymadığını kabul etti ancak getResource, OP'nin tam olarak istediği bir URL döndürüyor.
romacafe

@romacafe: Evet, haklısın. Bu iyi bir alternatif çözümdür.
sleske

2
OP özellik dosyası çözümü istedi, ancak diğerleri de sorunun başlığı nedeniyle buraya geliyor. Ve bu dinamik çözümü
seviyorlar

14

Bunun kendi cevabına değer olduğunu düşünüyorum - eğer Spring kullanıyorsanız, zaten

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

Bahar belgelerinde açıklandığı gibi ve skaffman tarafından yorumlarda belirtildiği gibi.


IMHO Spring ResourceLoader.getResource()görev için daha uygun ( ApplicationContext.getResource()başlık altında delegeler)
Lu55

11

Özelliği başlatma sırasında programlı olarak da ayarlayabilirsiniz:

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

Bu sınıfı kullanma:

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

Böylece bunu yapmanın en az müdahaleci yolunu elde edersiniz. :) java.net.URL her zaman sistem özelliklerinden geçerli değeri kullanır.


1
java.protocol.handler.pkgsSistem değişkenine arama için ekstra paket ekleyen kod , yalnızca işleyici henüz "bilinen" protokolü işlemeyi amaçlamıyorsa kullanılabilir gopher://. Amaç, "popüler" protokolü geçersiz kılmaksa, file://veya haritaya bu protokol için "standart" bir işleyici zaten eklendiğinden http://, bunu yapmak için çok geç olabilir java.net.URL#handlers. Yani tek çıkış yolu bu değişkeni JVM'ye geçirmektir.
dma_k

6

( Azder'in cevabına benzer , ancak biraz farklı bir dokunuş.)

Sınıf yolundan içerik için önceden tanımlanmış bir protokol işleyicisi olduğuna inanmıyorum. (Sözde classpath:protokol).

Ancak, Java kendi protokollerinizi eklemenize izin verir. Bu somut bir uygulamasını sağlamak aracılığıyla yapılır java.net.URLStreamHandlerve java.net.URLConnection.

Bu makalede, özel bir akış işleyicisinin nasıl uygulanabileceği açıklanmaktadır: http://java.sun.com/developer/onlineTraining/protocolhandlers/ .


4
JVM ile hangi protokollerin gönderildiğini biliyor musunuz?
Thilo

5

Özel işleyicileri kurma hatalarını azaltmaya yardımcı olan ve ilk önce bir yöntemi çağırmak veya doğru kapsayıcıda olmamakla ilgili hiçbir sorun olmaması için sistem özelliğinden yararlanan bir sınıf oluşturdum. İşleri yanlış anlarsanız bir istisna sınıfı da vardır:

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

}

5

@Stephen'dan ilham alın https://stackoverflow.com/a/1769454/980442 ve http://docstore.mik.ua/orelly/java/exp/ch09_06.htm

Kullanmak

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

bu sınıfı sun.net.www.protocol.classpathpakete oluşturun ve bir çekicilik gibi çalışmak için Oracle JVM uygulamasında çalıştırın.

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

Başka bir JVM uygulaması kullanıyorsanız, java.protocol.handler.pkgs=sun.net.www.protocolsystem özelliğini ayarlayın .

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


3

URLStreamHandlers'ı kaydetmeyle ilgili çözüm elbette en doğru olanıdır, ancak bazen en basit çözüm gereklidir. Yani, bunun için aşağıdaki yöntemi kullanın:

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

3

Java 9+ ve sonraki sürümlerden yeni bir tanımlayabilirsiniz URLStreamHandlerProvider. URLSınıf çalışma zamanında yüklemek için hizmet yükleyici çerçevesini kullanır.

Bir sağlayıcı oluşturun:

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

}

Adlı bir dosya oluşturun java.net.spi.URLStreamHandlerProvideriçinde META-INF/servicesiçeriklerle dizinde:

org.example.ClasspathURLStreamHandlerProvider

Şimdi URL sınıfı sağlayıcıyı aşağıdaki gibi bir şey gördüğünde kullanacaktır:

URL url = new URL("classpath:myfile.txt");

2

Zaten bir tane olup olmadığını bilmiyorum, ama bunu kendiniz kolayca yapabilirsiniz.

Bu farklı protokol örneği bana bir cephe modeli gibi görünüyor. Her vaka için farklı uygulamalar olduğunda ortak bir arayüze sahipsiniz.

Aynı prensibi kullanabilir, dizeyi özellikler dosyanızdan alan bir ResourceLoader sınıfı oluşturabilir ve özel bir protokolümüzü kontrol edebilirsiniz.

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: dizginin başından itibaren şeritler ve sonra kaynağı yüklemek için hangi yolu karar verir ve sadece kaynağı verir.


Üçüncü taraf bir lib'in URL'leri kullanmasını istiyorsanız ve belki de belirli bir protokol için kaynakların çözümünü işlemek istiyorsanız bu işe yaramaz.
mP.

2

Dilums'un cevabına bir uzantı :

Kodu değiştirmeden, Dilum'un önerdiği gibi URL ile ilgili arayüzlerin özel uygulamalarını izlemeniz gerekebilir. Sizin için işleri basitleştirmek için Spring Framework'ün Kaynakları kaynağına bakmanızı tavsiye ederim . Kod bir akış işleyici biçiminde olmasa da, tam olarak ne yapmak istediğinizi yapmak için tasarlanmıştır ve ASL 2.0 lisansı altındadır ve kodunuzda gerekli krediyi tekrar kullanmak için yeterince kolay hale getirir.


Başvurduğunuz bu sayfada, sanırım sorumu yanıtlayan "sınıf yolundan veya bir ServletContext'e göre alınması gereken bir kaynağa erişmek için kullanılabilecek standart bir URL uygulaması yok" ifadesini kullandı.
Thilo

@ Evsiz: orada durun genç adam. Biraz daha tecrübeyle, yakında kısa sürede yorum göndereceksiniz.
Cuga

1

Bir Spring Boot uygulamasında, dosya URL'sini almak için aşağıdakileri kullandım,

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

1

Sınıf yolunda tomcat varsa, bu kadar basit:

TomcatURLStreamHandlerFactory.register();

Bu, "savaş" ve "sınıf yolu" protokolleri için işleyicileri kaydedecektir.


0

Sınıftan kaçınmaya çalışıyorum URLve bunun yerine güveniyorum URI. Böylece URLBahar Kaynak ile arama gibi Bahar Kaynak yapmak istiyorum gereken şeyler için aşağıdakileri yaparım:

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

Bir URI oluşturmak için kullanabilirsiniz URI.create(..). Bu şekilde daha iyidir, çünkü ClassLoaderkaynak aramasını yapacak olanı kontrol edersiniz .

Şemayı algılamak için URL'yi bir String olarak ayrıştırmaya çalışan başka bazı cevaplar fark ettim. Bence URI'den geçmek ve ayrıştırmak için kullanmak daha iyi.

Aslında bir süre önce Spring Source ile kaynak kodlarını ayırmak için yalvarması için bir sorun yayınladım, coreböylece diğer tüm Spring şeylerine ihtiyacınız yok.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.