عنوان URL لتحميل الموارد من مسار الفصل في Java

StackOverflow https://stackoverflow.com/questions/861500

  •  21-08-2019
  •  | 
  •  

سؤال

في Java، يمكنك تحميل جميع أنواع الموارد باستخدام نفس واجهة برمجة التطبيقات ولكن باستخدام بروتوكولات 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"), بالطبع، ولكن هذا سيتطلب مني استخدام واجهة برمجة تطبيقات مختلفة، وبالتالي إجراء تغييرات على التعليمات البرمجية الحالية.أريد أن أكون قادرًا على استخدام هذا في جميع الأماكن حيث يمكنني تحديد عنوان URL للمورد بالفعل، وذلك فقط عن طريق تحديث ملف خاصية.

هل كانت مفيدة؟

المحلول

مقدمة وتنفيذ الأساسية

وأول ما يصل، وأنت تسير في حاجة إلى ما لا يقل عن URLStreamHandler. وهذا في الواقع فتح اتصال إلى عنوان معين. لاحظ أن هذا ما يسمى ببساطة 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();
    }
}

قضايا التشغيل

إذا كنت مثل أي شيء لي، وكنت لا تريد أن تعتمد على الممتلكات التي يجري وضعها في إطلاق لتحصل في مكان ما (في حالتي، وأنا أحب للحفاظ على خياراتي مفتوحة مثل جافا webstart و- وهذا هو السبب <م> أنا تحتاج الى كل هذا)

حلول / تحسينات

كود دليل مواصفات معالج

إذا يمكنك التحكم في التعليمات البرمجية، يمكنك أن تفعل

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

وهذا سوف تستخدم معالج لفتح الاتصال.

ولكن مرة أخرى، وهذا هو أقل من مرضية، وأنت لا تحتاج إلى URL للقيام بذلك - كنت أريد أن أفعل هذا لأن بعض ليب لا يمكنك (أو لا يريدون) السيطرة تريد عناوين ...

تسجيل JVM معالج

والخيار النهائي هو تسجيل URLStreamHandlerFactory التي من شأنها التعامل مع جميع عناوين المواقع عبر JVM:

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، ثم لاحظ جيدا أن القط سوف تستخدم هذه الطريقة لتسجيل معالج JNDI (AFAIK). محاولة جيتي (سأكون)؛ في أسوأ الأحوال، يمكنك استخدام الأسلوب أولا ثم عليه أن يعمل من حولك!

الترخيص

I إطلاق سراح هذا المجال العام، ونطلب إذا كنت ترغب في تعديل بأن تبدأ مشروع OSS في مكان ما والتعليق هنا مع التفاصيل. ومن شأن تنفيذ أفضل أن يكون لديك URLStreamHandlerFactory يستخدم لتخزين ThreadLocals URLStreamHandlers لكل Thread.currentThread().getContextClassLoader(). حتى سأعطيك بلدي تعديلات والطبقات الاختبار.

نصائح أخرى

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

وهذا ينبغي أن نفعل ذلك.

وأعتقد أن هذا هو يستحق الإجابة الخاصة به - إذا كنت تستخدم الربيع، لديك بالفعل هذا مع

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

وكما هو الحال في وثائق الربيع rel="noreferrer"> href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/resources.html" وأشار في تصريحات 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 دائما استخدام القيمة الحالية من خصائص النظام.

(مشابه ل إجابة أزدير, ، ولكن براعة مختلفة قليلاً.)

لا أعتقد أن هناك معالج بروتوكول محدد مسبقًا للمحتوى من مسار الفصل.(ما يسمى classpath: بروتوكول).

ومع ذلك، تسمح لك Java بإضافة البروتوكولات الخاصة بك.ويتم ذلك من خلال توفير تطبيقات ملموسة java.net.URLStreamHandler و java.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/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 وتشغيله في تنفيذ أوراكل 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.

ومعلوماتك: <وأ href = "http://docs.oracle.com/javase/7/docs/api/java/net/URL.html#URL(java.lang.String،٪20java.lang.String،٪20int،٪ 20java.lang.String) "يختلط =" نوفولو noreferrer "> http://docs.oracle.com/javase/7/docs/api/java/net/URL.html#URL (java.lang.String،٪ 20java .lang.String،٪ 20int،٪ 20java.lang.String)

والحل مع 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;
}

وأنا لا أعرف إذا كان هناك واحد بالفعل، ولكن هل يمكن أن تجعل من نفسك عسيلي.

وهذا بروتوكولات مختلفة سبيل المثال يبدو لي وكأنه نمط الواجهة. لديك واجهة مشتركة عندما تكون هناك تطبيقات مختلفة لكل حالة.

هل يمكن استخدام نفس المبدأ، وجعل فئة 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 ل:

ودون تغيير الرمز، هل من المحتمل تحتاج متابعة تطبيقات مخصصة من واجهات ذات الصلة URL كما توصي Dilum. لتبسيط الامور بالنسبة لك، ويمكنني أن أوصي تبحث في المصدر ل الموارد الإطار الربيع . في حين أن المدونة ليست في شكل معالج تيار، وقد تم تصميمه لتفعل بالضبط ما كنت تبحث القيام به، ويخضع لرخصة ASL 2.0، مما يجعلها ودية ما يكفي لإعادة استخدامها في التعليمات البرمجية مع الائتمان المناسب.

ومن جافا 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;
    }

}

وإنشاء ملف يسمى java.net.spi.URLStreamHandlerProvider في الدليل META-INF/services مع محتويات:

org.example.ClasspathURLStreamHandlerProvider

والآن فإن الطبقة URL استخدام مزود عندما ترى شيئا مثل:

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

في التطبيق التمهيد الربيع، واستخدمت التالية للحصول على URL الملف،

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

إذا كان لديك قط على CLASSPATH، انها بسيطة مثل:

TomcatURLStreamHandlerFactory.register();

وهذا سيتم تسجيل معالجات ل "الحرب" والبروتوكولات "CLASSPATH".

وأنا في محاولة لتجنب الطبقة 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 بحيث لا تحتاج كل الاشياء ربيع الآخر.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top