Регистрация и использование пользовательского протокола java.net.URL

Я пытался вызвать custom url из моей java-программы, поэтому я использовал что-то вроде этого:

URL myURL;
try {
   myURL = new URL("CustomURI:");
   URLConnection myURLConnection = myURL.openConnection();
   myURLConnection.connect();
} catch (Exception e) {
   e.printStackTrace();
}

Я получил следующее исключение:

java.net.MalformedURLException: неизвестный протокол: CustomURI     на java.net.URL. (Неизвестный источник)     на java.net.URL. (Неизвестный источник)     на java.net.URL. (Неизвестный источник)     на com.demo.TestDemo.main(TestDemo.java:14)

Если я запускаю URI из браузера, то он работает так, как ожидалось, но если я попытаюсь вызвать его из Java Program, то я получаю исключение выше.

EDIT:

Ниже приведены шаги, которые я пробовал (я что-то пропустил точно, сообщите мне об этом):

Шаг 1: добавление пользовательского URI в java.protocol.handler.pkgs

Шаг 2. Запуск пользовательского URI из URL

Код:

public class CustomURI {

public static void main(String[] args) {

    try {
        add("CustomURI:");
        URL uri = new URL("CustomURI:");
        URLConnection uc = uri.openConnection();            
        uc.connect();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public static void add( String handlerPackage ){

    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 );
    System.out.println(System.getProperty("java.protocol.handler.pkgs"));

}

}

Когда я запускаю этот код, я получаю CustomURI:, напечатанный на моей консоли (из метода add), но затем я получаю это исключение, когда URL инициализируется CustomURI: в качестве конструктора:

Exception in thread "main" java.lang.StackOverflowError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at java.net.URL.getURLStreamHandler(Unknown Source)
at java.net.URL.<init>(Unknown Source)
at java.net.URL.<init>(Unknown Source)
at sun.misc.URLClassPath$FileLoader.getResource(Unknown Source)
at sun.misc.URLClassPath.getResource(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.net.URL.getURLStreamHandler(Unknown Source)
at java.net.URL.<init>(Unknown Source)
at java.net.URL.<init>(Unknown Source)

Пожалуйста, советую, как это сделать.

Ответы

Ответ 1

  • Создайте пользовательскую URLConnection реализацию, которая выполняет задание в методе connect().

    public class CustomURLConnection extends URLConnection {
    
        protected CustomURLConnection(URL url) {
            super(url);
        }
    
        @Override
        public void connect() throws IOException {
            // Do your job here. As of now it merely prints "Connected!".
            System.out.println("Connected!");
        }
    
    }
    

    Не забудьте переопределить и реализовать другие методы, например getInputStream(). Более подробная информация об этом не может быть предоставлена, так как эта информация отсутствует в вопросе.


  • Создайте пользовательскую URLStreamHandler реализацию, которая вернет ее в openConnection().

    public class CustomURLStreamHandler extends URLStreamHandler {
    
        @Override
        protected URLConnection openConnection(URL url) throws IOException {
            return new CustomURLConnection(url);
        }
    
    }
    

    Не забудьте переопределить и реализовать другие методы, если это необходимо.


  • Создайте пользовательский URLStreamHandlerFactory, который создает и возвращает его на основе протокола.

    public class CustomURLStreamHandlerFactory implements URLStreamHandlerFactory {
    
        @Override
        public URLStreamHandler createURLStreamHandler(String protocol) {
            if ("customuri".equals(protocol)) {
                return new CustomURLStreamHandler();
            }
    
            return null;
        }
    
    }
    

    Обратите внимание, что протоколы всегда в нижнем регистре.


  • Наконец, зарегистрируйте его во время запуска приложения через URL#setURLStreamHandlerFactory()

    URL.setURLStreamHandlerFactory(new CustomURLStreamHandlerFactory());
    

    Обратите внимание, что Javadoc явно говорит, что вы можете установить его не более одного раза. Поэтому, если вы намерены поддерживать несколько настраиваемых протоколов в одном приложении, вам нужно будет создать пользовательскую реализацию URLStreamHandlerFactory, чтобы охватить все их внутри метода createURLStreamHandler().


    В качестве альтернативы, если вам не нравится Закон Деметры, бросьте все это вместе в анонимных классах для сокращения кода:

    URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
        public URLStreamHandler createURLStreamHandler(String protocol) {
            return "customuri".equals(protocol) ? new URLStreamHandler() {
                protected URLConnection openConnection(URL url) throws IOException {
                    return new URLConnection(url) {
                        public void connect() throws IOException {
                            System.out.println("Connected!");
                        }
                    };
                }
            } : null;
        }
    });
    

    Если вы уже на Java 8, замените функциональный интерфейс URLStreamHandlerFactory на лямбда для дальнейшего уточнения:

    URL.setURLStreamHandlerFactory(protocol -> "customuri".equals(protocol) ? new URLStreamHandler() {
        protected URLConnection openConnection(URL url) throws IOException {
            return new URLConnection(url) {
                public void connect() throws IOException {
                    System.out.println("Connected!");
                }
            };
        }
    } : null);
    

Теперь вы можете использовать его следующим образом:

URLConnection connection = new URL("CustomURI:blabla").openConnection();
connection.connect();
// ...

Или с нижним протоколом согласно спецификации:

URLConnection connection = new URL("CustomURI:blabla").openConnection();
connection.connect();
// ...

Ответ 2

Вы сделали рекурсию/бесконечный цикл.

Класс Loader ищет класс по-разному.

Элемент stacktrace (URLClassPath) выглядит следующим образом:

  • Загрузите ресурс.
  • Я загрузил каждый протокол? Нет!
  • Загрузите все обработчики протоколов, я не могу найти файл «your java.protocol.handler.pkgs-package».CustomURI.Handler.
  • Класс - это ресурс! Я загружал каждый протокол? Нет!
  • Загрузите все обработчики протоколов, я не могу найти файл «your java.protocol.handler.pkgs-package».CustomURI.Handler.
  • Класс - это ресурс! Я загружал каждый протокол? Нет!
  • Загрузите все обработчики протоколов, я не могу найти файл «your java.protocol.handler.pkgs-package».CustomURI.Handler.
  • Класс - это ресурс! Я загружал каждый протокол? Нет!
  • Загрузите все обработчики протоколов, я не могу найти файл «your java.protocol.handler.pkgs-package».CustomURI.Handler.
  • Класс - это ресурс! Я загружал каждый протокол? Нет!
  • Загрузите все обработчики протоколов, я не могу найти файл «your java.protocol.handler.pkgs-package».CustomURI.Handler.

    ...... StackOverflowException!!!

Ответ 3

Если вы не хотите использовать одностраничный URLStreamHandlerFactory, вы можете использовать отвратительное, но эффективное соглашение об именах, чтобы войти в реализацию по умолчанию.

Вы должны назвать свой URLStreamHandler класс Handler, а протокол, на который он сопоставляется, является последним сегментом пакета этого класса.

Итак, com.foo.myproto.Handlermyproto:urls, если вы добавите свой пакет com.foo в список "исходных пакетов потока URL" для поиска по неизвестному протоколу. Это делается с помощью системного свойства "java.protocol.handler.pkgs" (который представляет собой список имен пакетов для поиска).

Вот абстрактный класс, который выполняет то, что вам нужно: (не обращайте внимания на недостающие StringTo<Out1<String>> или StringURLConnection, они делают то, что предлагают их имена, и вы можете использовать любые абстракции, которые вы предпочитаете)

public abstract class AbstractURLStreamHandler extends URLStreamHandler {

    protected abstract StringTo<Out1<String>> dynamicFiles();

    protected static void addMyPackage(Class<? extends URLStreamHandler> handlerClass) {
        // Ensure that we are registered as a url protocol handler for JavaFxCss:/path css files.
        String was = System.getProperty("java.protocol.handler.pkgs", "");
        String pkg = handlerClass.getPackage().getName();
        int ind = pkg.lastIndexOf('.');
        assert ind != -1 : "You can't add url handlers in the base package";
        assert "Handler".equals(handlerClass.getSimpleName()) : "A URLStreamHandler must be in a class named Handler; not " + handlerClass.getSimpleName();

        System.setProperty("java.protocol.handler.pkgs", handlerClass.getPackage().getName().substring(0, ind) +
            (was.isEmpty() ? "" : "|" + was ));
    }


    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        final String path = u.getPath();
        final Out1<String> file = dynamicFiles().get(path);
        return new StringURLConnection(u, file);
    }
}

Тогда вот фактический класс, реализующий абстрактный обработчик (для dynamic: urls:

package xapi.dev.api.dynamic;

// imports elided for brevity

public class Handler extends AbstractURLStreamHandler {

    private static final StringTo<Out1<String>> dynamicFiles = X_Collect.newStringMap(Out1.class,
        CollectionOptions.asConcurrent(true)
            .mutable(true)
            .insertionOrdered(false)
            .build());

    static {
        addMyPackage(Handler.class);
    }

    @Override
    protected StringTo<Out1<String>> dynamicFiles() {
        return dynamicFiles;
    }

    public static String registerDynamicUrl(String path, Out1<String> contents) {
        dynamicFiles.put(path, contents);
        return path;
    }

    public static void clearDynamicUrl(String path) {
        dynamicFiles.remove(path);
    }

}

Ответ 4

Если все, что вы пытаетесь сделать, это вызвать схему URI, которая уже зарегистрирована в системе, и вы не против использовать код для Windows, чтобы сделать это (вы упомянули реестр, поэтому я предполагаю, что ваш код только для Windows), вы можете сделать это, если CMD "start" builtin вызовет ShellExecute для вас. Это не связывает вашу Java-программу с URL-адресом, но если вы просто хотите вызвать обработчик URI, тогда он отлично работает:

public static void main (String[] args)
{
    Runtime run = Runtime.getRuntime();
    try {
        run.exec("cmd /c CustomURI:");
    } catch (Exception ex) {
        System.err.println(ex.toString());
    }
}

Это полный взлом, конечно, и имеет накладные расходы на выполнение вызова CMD.EXE, но он может работать для вашего дела. Несколько более элегантный подход заключается в вызове ShellExecuteW (http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx) через JNI, но это справедливо. Код, который вы вызываете (C/С++),

ShellExecuteW(NULL, NULL, L"CustomURI:", NULL, NULL, 1 /* SW_SHOWNORMAL */);