IT

Java의 클래스 경로에서 자원을로드하는 URL

lottoking 2020. 5. 12. 08:25
반응형

Java의 클래스 경로에서 자원을로드하는 URL


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을 지정할 수있는 모든 곳에서 이것을 사용할 수 있기를 원합니다.


소개 및 기본 구현

먼저 최소한 URLStreamHandler가 필요합니다. 이것은 실제로 주어진 URL에 대한 연결을 엽니 다. 이것을 간단히 호출합니다 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();
    }
}

발사 문제

왜 - 당신이 어딘가에 당신을 얻기 위해 발사의 속성 인 세트에 의존하지 않으려는 저 같이 아무것도 당신이 경우 (제 경우에, 나는 내 옵션은 자바 웹 시작처럼 열 유지하려면 내가 이 모든 필요 ).

해결 방법 / 향상

수동 코드 처리기 사양

코드를 제어하면 할 수 있습니다

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

그러면 핸들러를 사용하여 연결을 엽니 다.

그러나 다시 말하지만 이것은 URL을 필요로하지 않기 때문에 만족스럽지 않습니다. 제어 할 수없는 (또는 원하지 않는) 일부 lib가 URL을 원하기 때문에이 작업을 원합니다 ...

JVM 핸들러 등록

궁극적 인 옵션은 URLStreamHandlerFactoryjvm의 모든 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은이 메소드를 사용하여 AFAIK (JNDI 핸들러)를 등록합니다. 부두를 시도하십시오 (나는 될 것이다); 최악의 경우, 먼저이 방법을 사용할 수 있으며 해결해야합니다!

특허

나는 이것을 공개 도메인에 공개하고 수정을 원한다면 어딘가에 OSS 프로젝트를 시작하고 여기에 세부 사항을 언급하십시오. 더 나은 구현은 URLStreamHandlerFactory을 사용 하여 각각 ThreadLocal을 저장 URLStreamHandler하는 것 Thread.currentThread().getContextClassLoader()입니다. 나는 당신에게 나의 수정과 시험 수업을 줄 것입니다.


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

그렇게해야합니다.


나는 이것이 자신의 대답의 가치가 있다고 생각합니다-Spring을 사용하고 있다면 이미 이것을 가지고 있습니다.

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

등이 설명 스프링 문서 및 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은 항상 시스템 특성의 현재 값을 사용합니다.


( Azder의 답변 과 비슷하지만 약간 다른 전술입니다.)

클래스 패스의 내용에 대해 사전 정의 된 프로토콜 핸들러가 있다고 생각하지 않습니다. (소위 classpath:프로토콜).

그러나 Java를 사용하면 고유 한 프로토콜을 추가 할 수 있습니다. 이것은 구체적인 구현을 제공을 통해 이루어집니다 java.net.URLStreamHandlerjava.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/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 .끈)


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

이미 있는지 모르겠지만 쉽게 만들 수 있습니다.

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

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.


An extension to Dilums's answer:

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.


From Java 9+ and up, you can define a new URLStreamHandlerProvider. The URL class uses the service loader framework to load it at run time.

Create a provider:

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

}

Create a file called java.net.spi.URLStreamHandlerProvider in the META-INF/services directory with the contents:

org.example.ClasspathURLStreamHandlerProvider

Now the URL class will use the provider when it sees something like:

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

In a Spring Boot app, I used the following to get the file URL,

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

If you have tomcat on the classpath, it's as simple as:

TomcatURLStreamHandlerFactory.register();

This will register handlers for "war" and "classpath" protocols.


I try to avoid the URL class and instead rely on URI. Thus for things that need URL where I would like to do Spring Resource like lookup with out Spring I do the following:

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

To create a URI you can use URI.create(..). This way is also better because you control the ClassLoader that will do the resource lookup.

I noticed some other answers trying to parse the URL as a String to detect the scheme. I think its better to pass around URI and use it to parse instead.

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.

참고URL : https://stackoverflow.com/questions/861500/url-to-load-resources-from-the-classpath-in-java

반응형