URL для загрузки ресурсов из classpath в Java
в 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-адрес для ресурса уже, просто обновив файл свойств.
12 ответов:
введение и базовая реализация
во-первых, вам понадобится по крайней мере 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(); } }запуск проблемы
если вы похожи на меня, вы не хотите полагаться на свойство, установленное в запуске, чтобы получить вас где - то (в моем случае мне нравится держать мои параметры открытыми, как Java WebStart-вот почему я все это нужно).Обходные Пути/Повышений
спецификация обработчика ручного кода
если вы контролируете код, вы можете сделать
new URL(null, "classpath:some/package/resource.extension", new org.my.protocols.classpath.Handler(ClassLoader.getSystemClassLoader()))и это будет использовать ваш обработчик, чтобы открыть соединение.
но опять же, это менее чем удовлетворительно, так как вам не нужен URL - адрес для этого-вы хотите сделать это, потому что некоторые lib, которые вы не можете (или не хотите) контролировать, хотят URL-адреса...
Регистрация обработчика JVM
конечным вариантом является регистрация
URLStreamHandlerFactoryэто будет обрабатывать все URL-адреса через 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, и обратите внимание, что Tomcat будет использовать этот метод для регистрации обработчика JNDI (AFAIK). Попробуйте Jetty (я буду); в худшем случае, вы можете использовать метод сначала, а затем он должен работать вокруг вас!
лицензия
я выпускаю это в общественное достояние и спрашиваю, что если вы хотите изменить, что вы начинаете проект OSS где-то и комментируете здесь с подробностями. Бест реализация будет иметь
URLStreamHandlerFactoryиспользуетThreadLocals в магазинеURLStreamHandlerС каждогоThread.currentThread().getContextClassLoader(). Я даже дам вам свои модификации и тестовые классы.
Я думаю, что это стоит своего собственного ответа - если вы используете 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(); } }
Я создал класс, который помогает уменьшить ошибки при настройке пользовательских обработчиков и использует системное свойство, поэтому нет проблем с вызовом метода первым или не находящимся в правильном контейнере. Есть также класс исключений, если вы ошибаетесь:
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 ); } }
(аналог Azder это!--5-->, но немного другой тактичности.)
Я не верю, что существует предопределенный обработчик протокола для контента из classpath. (Так называемые
classpath:протокола).тем не менее, Java позволяет добавлять свои собственные протоколы. Это делается путем предоставления конкретных реализаций
java.net.URLStreamHandlerиjava.net.URLConnection.В этой статье описывается, как пользовательский обработчик потока может быть выполненный: http://java.sun.com/developer/onlineTraining/protocolhandlers/.
решение с регистрацией 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+):.+$", "").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; }
вдохновить @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системное свойство.к вашему сведению: http://docs.oracle.com/javase/7/docs/api/java/net/URL.html#URL(java.lang.String,%20java.lang.String,%20int,%20java.lang.String)
расширение Dilums это:
без изменения кода, Вы, вероятно, нужно использовать пользовательские реализации интерфейса, связанных с URL-адрес, как Дилум рекомендует. Чтобы упростить вещи для вас, я могу порекомендовать посмотреть на источник для ресурсы Spring Framework. Хотя код не является обработчиком потока, он был разработан для выполнения именно того, что вы хотите сделать, и находится под лицензией ASL 2.0, что делает его достаточно удобным для повторного использования в вашем коде с должным кредитом.
Я не знаю, есть ли он уже, но вы можете сделать это самостоятельно легко.
этот пример разных протоколов выглядит для меня как шаблон фасада. У вас есть общий интерфейс, когда существуют различные реализации для каждого случая.
вы можете использовать тот же принцип, создать класс 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: с самого начала строка, а затем принимает решение о том, каким образом загрузить ресурс, и просто дает вам ресурс.
Я стараюсь избегать
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-адрес как строку для обнаружения схемы. Я думаю, что его лучше передать вокруг Ури и использовать его для разбора вместо этого.
в приложении Spring Boot я использовал следующее, чтобы получить URL-адрес файла,
Thread.currentThread().getContextClassLoader().getResource("PromotionalOfferIdServiceV2.wsdl")
Comments