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


197

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

답변:


348

소개 및 기본 구현

먼저 최소한 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()입니다. 나는 당신에게 나의 수정과 시험 수업을 줄 것이다.


1
@ Stephen 이것은 정확히 내가 찾고있는 것입니다. 업데이트 내용을 나와 공유해 주시겠습니까? com.github.fommil.common-utils패키지의 일부로 Sonatype을 통해 곧 업데이트 및 릴리스 할 계획을 포함시킬 수 있습니다.
fommil

5
System.setProperty()프로토콜 등록 에도 사용할 수 있습니다 . 처럼System.setProperty("java.protocol.handler.pkgs", "org.my.protocols");
tsauerwein

: 자바 9+는 쉬운 방법이있다 stackoverflow.com/a/56088592/511976
mhvelplund

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

그렇게해야합니다.


11
"물론 Class.getResourceAsStream ("a.xml ")을 사용하여이 작업을 수행 할 수 있지만 다른 API를 사용해야하므로 기존 코드를 변경해야합니다. 모든 위치에서 사용할 수 있습니다. 속성 파일 만 업데이트하면 이미 리소스의 URL을 지정할 수 있습니다. "
Thilo

3
-1 Thilo가 지적한 것처럼 이것은 OP가 고려하고 거부 한 것입니다.
sleske

13
getResource와 getResourceAsStream은 다른 메소드입니다. getResourceAsStream이 API와 맞지 않는다는 것에 동의했지만 getResource는 URL을 반환합니다. 이는 정확히 OP가 요청한 URL입니다.
romacafe

@romacafe : 네, 그렇습니다. 이것은 좋은 대안 솔루션입니다.
sleske

2
OP는 속성 파일 솔루션을 요청했지만 질문 제목으로 인해 다른 사람들도 여기에옵니다. 그리고 그들은이 역동적 인 솔루션을 좋아합니다 :)
Jarekczek

14

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

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

등이 설명 스프링 문서 및 skaffman에 의해 코멘트 지적했다.


IMHO Spring ResourceLoader.getResource()이 작업에 더 적합합니다 ( ApplicationContext.getResource()후드 아래에 위임)
Lu55

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지도가 이미 프로토콜에 대한 "표준"핸들러를 추가됩니다. 따라서 유일한 방법은이 변수를 JVM에 전달하는 것입니다.
dma_k 2014

6

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

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

그러나 Java를 사용하면 고유 한 프로토콜을 추가 할 수 있습니다. 이것은 구체적인 구현을 제공을 통해 이루어집니다 java.net.URLStreamHandlerjava.net.URLConnection.

이 기사에서는 커스텀 스트림 핸들러를 구현하는 방법을 설명합니다. http://java.sun.com/developer/onlineTraining/protocolhandlers/ .


4
JVM과 함께 제공되는 프로토콜 목록을 알고 있습니까?
Thilo

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/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 .끈)


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

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

2

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

다른 프로토콜 예제는 정면 패턴처럼 보입니다. 각 사례마다 다른 구현이있을 때 공통 인터페이스가 있습니다.

동일한 원칙을 사용하고 속성 파일에서 문자열을 가져 와서 사용자 정의 프로토콜을 확인하는 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을 제거 한 다음 리소스를로드하는 방법을 결정하고 리소스를 제공합니다.


써드 파티 lib가 URL을 사용하고 특정 프로토콜에 대한 자원 분석을 처리하려는 경우에는 작동하지 않습니다.
mP.

2

Dilums의 답변 확장 :

코드를 변경하지 않으면 Dilum이 권장하는대로 URL 관련 인터페이스의 사용자 지정 구현을 추구해야합니다. 당신을 위해 일을 단순화하기 위해 Spring Framework의 Resources 소스를 볼 것을 권장한다 . 이 코드는 스트림 처리기 형태가 아니지만 원하는 작업을 정확하게 수행하도록 설계되었으며 ASL 2.0 라이센스하에 있으므로 적절한 크레딧으로 코드에서 재사용 할 수있을 정도로 친절합니다.


이 페이지에서는 "클래스 경로에서 가져 오거나 ServletContext와 관련하여 필요한 리소스에 액세스하는 데 사용할 수있는 표준화 된 URL 구현이 없습니다"라고 언급하며 내 질문에 대답합니다.
Thilo

@ 노숙자 : 저기, 젊은이. 조금 더 경험이 있으면 곧 의견을 게시 할 수 있습니다.
Cuga

1

Spring Boot 앱에서 다음을 사용하여 파일 URL을 가져 왔습니다.

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

1

클래스 패스에 바람둥이가 있다면 다음과 같이 간단합니다.

TomcatURLStreamHandlerFactory.register();

이것은 "war"및 "classpath"프로토콜에 대한 핸들러를 등록합니다.


0

나는 URL수업 을 피하려고 노력 하고 대신에 의지한다 URI. 따라서 URLSpring을 사용하지 않고 조회와 같은 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를 전달하고 대신 구문 분석에 사용하는 것이 좋습니다.

실제로 Spring Source에서 리소스 코드를 분리 core하도록 요청하여 다른 Spring 항목이 필요하지 않도록 문제를 제기했습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.