注册和使用自定义java.net.URL协议

45

我试图从我的Java程序中调用一个自定义URL,因此我使用了类似于这样的代码:

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

我遇到了以下异常:

java.net.MalformedURLException: unknown protocol: CustomURI at java.net.URL.(Unknown Source) at java.net.URL.(Unknown Source) at java.net.URL.(Unknown Source) at com.demo.TestDemo.main(TestDemo.java:14)

如果我从浏览器触发URI,它会按预期工作,但是如果我尝试从Java程序调用它,则会出现上述异常。

编辑:

以下是我尝试的步骤(肯定有些步骤错了,请指正):

步骤1:将Custom URI添加到java.protocol.handler.pkgs

步骤2:从URL触发Custom URI

代码:

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方法),但当使用CustomURI:作为构造函数初始化URL时,我会得到这个异常。
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
“_as expected_” 是什么意思? - Sotirios Delimanolis
“As Expected” 表示自定义 URL 可以触发应用程序。基本上,这个自定义 URL 是我创建的用于触发桌面应用程序的注册表项。 - user182944
3
我相信你需要编写自己的URLStreamHandlerFactory来处理该协议。 - Sotirios Delimanolis
2
我认为类似的问题已经在https://dev59.com/jnE95IYBdhLWcg3wN7Ov得到了回答。 - Lemonov
任何关于使用URLStreamHandlerFactory的示例代码都将不胜感激。我没有找到太多相关的示例。 - user182944
显示剩余2条评论
3个回答

89
  1. Create a custom URLConnection implementation which performs the job in connect() method.

    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!");
        }
    
    }
    

    Don't forget to override and implement other methods like getInputStream() accordingly. More detail on that cannot be given as this information is missing in the question.


  2. Create a custom URLStreamHandler implementation which returns it in openConnection().

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

    Don't forget to override and implement other methods if necessary.


  3. Create a custom URLStreamHandlerFactory which creates and returns it based on the protocol.

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

    Note that protocols are always lowercase.


  4. Finally register it during application's startup via URL#setURLStreamHandlerFactory()

    URL.setURLStreamHandlerFactory(new CustomURLStreamHandlerFactory());
    

    Note that the Javadoc explicitly says that you can set it at most once. So if you intend to support multiple custom protocols in the same application, you'd need to generify the custom URLStreamHandlerFactory implementation to cover them all inside the createURLStreamHandler() method.


    Alternatively, if you dislike the Law of Demeter, throw it all together in anonymous classes for code minification:

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

    If you're on Java 8 already, replace the URLStreamHandlerFactory functional interface by a lambda for further minification:

    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();
// ...

1
我尝试过这个(详细信息在这里http://stackoverflow.com/questions/37528167/how-to-set-source-ip-to-http-request/37532231#37532231)。您能否提供“getInputStream()”的实现呢? - theHeman
2
如果我们将所有必要的类放入“默认包”中,我们是否需要调用URL.setURLStreamHandlerFactory()?(顺便问一下,“默认包”是哪个?) - Matthieu
你能提供 getInputStream() / getOutputStream() 的实现吗?或者至少给一个提示怎么做?那将是非常愉快的! :) - shurrok
1
在Java 9+之后,可以通过https://dev59.com/tHRA5IYBdhLWcg3wsgFP#56088592的示例非常轻松地实现此操作。 - rombow
请注意,如果您正在编写一个库,在那里接管唯一的URLStreamHandlerFactory实例可能是不得体的,您可能需要检查我的答案,该答案使用不同的机制来添加对给定协议的支持:而不是接管URLStreamHandlerFactory的唯一实例。 - Ajax

13

如果您不想接管唯一的URLStreamHandlerFactory,实际上可以使用一个丑陋但有效的命名约定来使用默认实现。

必须将您的URLStreamHandler类命名为Handler,并且它映射到的协议是该类包的最后一个段。

因此,com.foo.myproto.Handler -> myproto: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);
    }
}

然后,这里是实现抽象处理程序的实际类(用于动态URL:dynamic:):
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);
    }

}

2

你写了一个递归/无限循环。

类加载器以不同的方式搜索类。

堆栈跟踪(URLClassPath)如下:

  1. 加载资源。
  2. 我是否加载了每个协议?没有!
  3. 加载所有协议处理程序,我找不到文件 «your java.protocol.handler.pkgs-package».CustomURI.Handler
  4. 类是资源!我是否加载了每个协议?没有!
  5. 加载所有协议处理程序,我找不到文件 «your java.protocol.handler.pkgs-package».CustomURI.Handler
  6. 类是资源!我是否加载了每个协议?没有!
  7. 加载所有协议处理程序,我找不到文件 «your java.protocol.handler.pkgs-package».CustomURI.Handler
  8. 类是资源!我是否加载了每个协议?没有!
  9. 加载所有协议处理程序,我找不到文件 «your java.protocol.handler.pkgs-package».CustomURI.Handler
  10. 类是资源!我是否加载了每个协议?没有!
  11. 加载所有协议处理程序,我找不到文件 «your java.protocol.handler.pkgs-package».CustomURI.Handler

    ...... StackOverflowException!!!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接