जावा में क्लासपाथ से संसाधनों को लोड करने के लिए URL


197

जावा में, आप एक ही एपीआई का उपयोग करके सभी प्रकार के संसाधनों को लोड कर सकते हैं लेकिन विभिन्न URL प्रोटोकॉल के साथ:

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

यह अच्छी तरह से संसाधन की जरूरत है कि आवेदन से संसाधन के वास्तविक लोडिंग decouples, और चूंकि एक URL सिर्फ एक स्ट्रिंग है, संसाधन लोड करना भी बहुत आसानी से कॉन्फ़िगर करने योग्य है।

क्या वर्तमान क्लास लोडर का उपयोग करके संसाधनों को लोड करने के लिए एक प्रोटोकॉल है? यह जार प्रोटोकॉल के समान है, सिवाय इसके कि मुझे यह जानने की आवश्यकता नहीं है कि संसाधन किस जार फ़ाइल या क्लास फ़ोल्डर से आ रहा है।

मैं Class.getResourceAsStream("a.xml")निश्चित रूप से उस का उपयोग कर सकता हूं , लेकिन इसके लिए मुझे एक अलग एपीआई का उपयोग करना होगा, और इसलिए मौजूदा कोड में परिवर्तन। मैं सभी स्थानों पर इसका उपयोग करने में सक्षम होना चाहता हूं, जहां मैं संपत्ति फ़ाइल को अपडेट करके, पहले से ही संसाधन के लिए URL निर्दिष्ट कर सकता हूं।

जवाबों:


348

परिचय और बुनियादी कार्यान्वयन

सबसे पहले, आपको कम से कम 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();
    }
}

मुद्दों को लॉन्च करें

यदि आप मेरे जैसे कुछ भी हैं, तो आप कहीं पाने के लिए लॉन्च में सेट की जा रही संपत्ति पर भरोसा नहीं करना चाहते हैं (मेरे मामले में, मैं अपने विकल्पों को जावा वेबस्टार्ट की तरह खुला रखना चाहता हूं - यही कारण है कि मुझे यह सब चाहिए )।

समाधान / संवर्द्धन

मैनुअल कोड हैंडलर विनिर्देश

यदि आप कोड को नियंत्रित करते हैं, तो आप कर सकते हैं

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

और यह कनेक्शन खोलने के लिए आपके हैंडलर का उपयोग करेगा।

लेकिन फिर से, यह संतोषजनक से कम है, क्योंकि आपको ऐसा करने के लिए URL की आवश्यकता नहीं है - आप ऐसा इसलिए करना चाहते हैं क्योंकि कुछ काम जो आप नहीं कर सकते हैं (या नहीं करना चाहते हैं) नियंत्रण urls चाहते हैं ...

जेवीएम हैंडलर पंजीकरण

अंतिम विकल्प यह है 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) को पंजीकृत करने के लिए इस पद्धति का उपयोग करेगा। जेट्टी की कोशिश करो (मैं होगा); सबसे खराब तरीके से, आप पहले विधि का उपयोग कर सकते हैं और फिर इसे आपके आस-पास काम करना होगा!

लाइसेंस

मैं इसे सार्वजनिक डोमेन पर जारी करता हूं, और पूछता हूं कि यदि आप संशोधित करना चाहते हैं कि आप कहीं ओएसएस परियोजना शुरू करते हैं और विवरण के साथ यहां टिप्पणी करते हैं। एक बेहतर कार्यान्वयन प्रत्येक के लिए एस को स्टोर करने के URLStreamHandlerFactoryलिए ThreadLocalएस का उपयोग करने वाला होगा । मैं आपको अपने संशोधन और परीक्षण कक्षाएं भी दूंगा।URLStreamHandlerThread.currentThread().getContextClassLoader()


1
@ स्टीफन यह वही है जिसकी मुझे तलाश है। क्या आप कृपया अपने अपडेट मेरे साथ साझा कर सकते हैं? मैं अपने com.github.fommil.common-utilsपैकेज के हिस्से के रूप में शामिल कर सकता हूं जिसे मैं सोनाटाइप के माध्यम से जल्द ही अपडेट और रिलीज करने की योजना बना रहा हूं।
fommil

5
ध्यान दें कि आप System.setProperty()प्रोटोकॉल को पंजीकृत करने के लिए भी उपयोग कर सकते हैं । जैसेSystem.setProperty("java.protocol.handler.pkgs", "org.my.protocols");
tsauerwein

Java 9+ में एक आसान तरीका है: stackoverflow.com/a/56088592/511976
mhvelplund

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

इससे हो जाना चाहिए।


11
"मैं निश्चित रूप से Class.getResourceAsStream (" a.xml ") का उपयोग कर सकता हूं, लेकिन इसके लिए मुझे एक अलग API का उपयोग करना होगा, और इसलिए मौजूदा कोड में परिवर्तन करना होगा। मैं सभी जगहों पर इसका उपयोग करने में सक्षम होना चाहता हूं। मैं एक संपत्ति फ़ाइल को अपडेट करके, पहले से ही संसाधन के लिए एक URL निर्दिष्ट कर सकता हूं। "
थिलो

3
-1 जैसा कि थिलो ने बताया, यह कुछ ऐसा है जिसे ओपी ने माना और खारिज कर दिया।
13:15 बजे sleske

13
getResource और getResourceAsStream अलग-अलग विधियाँ हैं। सहमत है कि getResourceAsStream एपीआई के साथ फिट नहीं है, लेकिन getResource एक URL लौटाता है, जो कि ओपी ने पूछा है।
romacafe

@romacafe: हाँ, आप सही कह रहे हैं। यह एक अच्छा वैकल्पिक उपाय है।
sleske

2
ओपी ने संपत्ति फ़ाइल समाधान के लिए कहा, लेकिन अन्य लोग भी सवाल के शीर्षक के कारण यहां आते हैं। और वे इस गतिशील समाधान को पसंद करते हैं :)
जारेक्ज़ेक

14

मुझे लगता है कि यह अपने स्वयं के जवाब के लायक है - यदि आप स्प्रिंग का उपयोग कर रहे हैं, तो आपके पास पहले से ही यह है

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

जैसे स्प्रिंग डॉक्यूमेंटेशन में बताया गया है और स्कैम्फैन द्वारा टिप्पणियों में बताया गया है।


IMHO स्प्रिंग ResourceLoader.getResource()कार्य के लिए अधिक उपयुक्त है ( ApplicationContext.getResource()हुड के नीचे इसे
दर्शाता है

11

आप प्रॉपर्टी प्रोग्राम को स्टार्टअप के दौरान भी सेट कर सकते हैं:

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 हमेशा सिस्टम गुणों से वर्तमान मान का उपयोग करेगा।


1
कोड जो java.protocol.handler.pkgsसिस्टम चर को देखने के लिए अतिरिक्त पैकेज जोड़ता है, इसका उपयोग केवल तभी किया जा सकता है जब हैंडलर का लक्ष्य अभी तक "ज्ञात" प्रोटोकॉल जैसे प्रक्रिया न हो gopher://। यदि इरादा "लोकप्रिय" प्रोटोकॉल को ओवरराइड करना है, जैसे, file://या http://, इसे करने में बहुत देर हो सकती है, क्योंकि java.net.URL#handlersउस प्रोटोकॉल के लिए मैप में पहले से ही "मानक" हैंडलर जोड़ा गया है। इसलिए जेवीएम के लिए इस चर को पारित करने का एकमात्र तरीका है।
dma_k

6

( एज्डर के उत्तर के समान , लेकिन थोड़ा अलग चातुर्य।)

मुझे विश्वास नहीं होता कि क्लासपैथ से सामग्री के लिए एक पूर्वनिर्धारित प्रोटोकॉल हैंडलर है। (तथाकथित classpath:प्रोटोकॉल)।

हालाँकि, जावा आपको अपने प्रोटोकॉल जोड़ने की अनुमति देता है। यह ठोस कार्यान्वयन प्रदान करने java.net.URLStreamHandlerऔर के माध्यम से किया जाता है java.net.URLConnection

यह आलेख बताता है कि एक कस्टम स्ट्रीम हैंडलर कैसे लागू किया जा सकता है: http://java.sun.com/developer/onlineTraining/protocolhandlers/


4
क्या आपको पता है कि JVM के साथ कौन से प्रोटोकॉल जहाज जाते हैं?
थिलो

5

मैंने एक वर्ग बनाया है जो कस्टम हैंडलर स्थापित करने में त्रुटियों को कम करने में मदद करता है और सिस्टम संपत्ति का लाभ उठाता है, इसलिए पहले फोन करने या सही कंटेनर में नहीं होने के साथ कोई समस्या नहीं है। अगर आपको चीजें गलत मिलती हैं तो एक अपवाद वर्ग भी है:

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 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पैकेज में बनाएँ और इसे एक आकर्षण की तरह काम करने के लिए ओरेकल जेवीएम कार्यान्वयन में चलाएं।

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सिस्टम गुण सेट करें ।

FYI करें: http://docs.oracle.com/javase/7/docs/api/java/net/URL.html#URL(java.lang.String,%20java.lang.String,%20intava/%20java.lang .String)


3

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

3

जावा 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;
    }

}

सामग्री के साथ निर्देशिका java.net.spi.URLStreamHandlerProviderमें नामक एक फ़ाइल बनाएँ META-INF/services:

org.example.ClasspathURLStreamHandlerProvider

अब URL वर्ग प्रदाता का उपयोग करेगा जब वह कुछ ऐसा देखेगा:

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

2

मैं नहीं जानता कि अगर वहाँ पहले से ही एक है, लेकिन आप इसे अपने आप को आसान बना सकते हैं।

यह अलग-अलग प्रोटोकॉल उदाहरण मुझे एक मुखौटा पैटर्न की तरह दिखता है। आपके पास एक सामान्य इंटरफ़ेस है जब प्रत्येक मामले के लिए अलग-अलग कार्यान्वयन हैं।

आप एक ही सिद्धांत का उपयोग कर सकते हैं, एक रिसोर्सलैडर क्लास बनाएं, जो आपकी प्रॉपर्टीज़ फ़ाइल से स्ट्रिंग लेता है, और हमारे कस्टम प्रोटोकॉल के लिए जाँच करता है।

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

दिलुम्स के उत्तर का विस्तार :

कोड को बदले बिना, आपको URL से संबंधित इंटरफेस के कस्टम कार्यान्वयन को आगे बढ़ाने की आवश्यकता है क्योंकि Dilum अनुशंसा करता है। आपके लिए चीजों को सरल बनाने के लिए, मैं स्प्रिंग फ्रेमवर्क के संसाधनों के स्रोत को देखने की सिफारिश कर सकता हूं । हालांकि कोड एक स्ट्रीम हैंडलर के रूप में नहीं है, यह आपको वही करने के लिए डिज़ाइन किया गया है जो आप करना चाह रहे हैं और एएसएल 2.0 लाइसेंस के तहत है, जिससे यह आपके क्रेडिट में उचित क्रेडिट के साथ फिर से उपयोग करने के लिए अनुकूल है।


वह पृष्ठ जो आपको संदर्भित करता है कि "कोई मानकीकृत URL कार्यान्वयन नहीं है, जिसका उपयोग उस संसाधन तक पहुंचने के लिए किया जा सकता है जिसे क्लासपैथ से प्राप्त करने की आवश्यकता है, या एक सर्वलेटकोटेक्स्ट के सापेक्ष", जो मेरे प्रश्न का उत्तर देता है, मुझे लगता है।
थिलो

@Homeless: वहीं लटक जाओ, जवान आदमी। थोड़े और अनुभव के साथ, आप जल्द ही कुछ ही समय में टिप्पणियाँ पोस्ट करेंगे।
दोपहर '14

1

स्प्रिंग बूट ऐप में, मैंने फ़ाइल URL प्राप्त करने के लिए निम्नलिखित का उपयोग किया,

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

1

यदि आपके पास क्लासपैथ पर टॉमकैट है, तो यह उतना ही सरल है:

TomcatURLStreamHandlerFactory.register();

यह "युद्ध" और "क्लासपैथ" प्रोटोकॉल के लिए हैंडलर पंजीकृत करेगा।


0

मैं URLक्लास से बचने की कोशिश करता हूं और बदले में भरोसा करता हूं URI। इस प्रकार उन चीजों के लिए URLजहां मुझे स्प्रिंग रिसोर्स की तरह काम करना होता है जैसे स्प्रिंग के साथ लुकअप निम्नलिखित में से मैं करता हूं:

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 के चारों ओर से गुजरना बेहतर है और इसके बजाय इसे पार्स करना है।

मैंने वास्तव में स्प्रिंग स्रोत के साथ कुछ समय पहले एक मुद्दा दायर किया है ताकि वे अपने संसाधन कोड को अलग करने के लिए उनसे भीख मांगें coreताकि आपको अन्य सभी स्प्रिंग सामान की आवश्यकता न हो।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.