Java의 클래스 경로에서 자원을로드하는 URL
-
21-08-2019 - |
문제
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이 필요하지 않기 때문에 만족스럽지 않습니다.
JVM 핸들러 등록
궁극적 인 옵션은 a를 등록하는 것입니다 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 핸들러 등록 문제
이 방법은 JVM 당 한 번만 호출 될 수 있으며 Tomcat 은이 방법을 사용하여 JNDI 핸들러 (AFAIK)를 등록합니다. 부두를 시험해보십시오 (내가 될 것입니다); 최악의 경우 먼저이 방법을 사용할 수 있으며 주변에서 작업해야합니다!
특허
나는 이것을 공개 도메인에 공개하고 당신이 어딘가에 OSS 프로젝트를 시작하고 세부 사항을 여기에 댓글을달라고 수정하고자하는지 묻습니다. 더 나은 구현은 URLStreamHandlerFactory
그것은 사용합니다 ThreadLocal
S를 보관할 URLStreamHandler
각각에 대한 s 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의 컨텐츠에 대한 사전 정의 된 프로토콜 핸들러가 있다고 생각하지 않습니다. (소위 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
포장 및 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
시스템 속성.
솔루션 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을 제거한 다음 문자열의 시작부터 리소스를로드 할 수있는 방법을 결정하고 리소스를 제공합니다.
연장 딜 룸의 대답:
코드를 변경하지 않으면 Dilum이 권장하는대로 URL 관련 인터페이스의 사용자 정의 구현을 추구해야 할 것입니다. 당신을 위해 물건을 단순화하려면 소스를 보는 것이 좋습니다. 봄 프레임 워크의 리소스. 코드는 스트림 핸들러의 형태가 아니지만, 원하는대로 정확하게 수행하도록 설계되었으며 ASL 2.0 라이센스에 따라 적절한 신용으로 코드를 재사용하기에 충분히 친숙합니다.
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");
Spring Boot 앱에서는 다음을 사용하여 파일 URL을 얻었습니다.
Thread.currentThread().getContextClassLoader().getResource("PromotionalOfferIdServiceV2.wsdl")
ClassPath에 Tomcat이 있다면 간단합니다.
TomcatURLStreamHandlerFactory.register();
이것은 "전쟁"및 "클래스 경로"프로토콜에 대한 핸들러를 등록합니다.
나는 피하려고 노력한다 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를 돌아 다니면서 대신 구문 분석하는 것이 낫다고 생각합니다.