获取对Java的引用';s默认http(s)URLStreamHandler

获取对Java的引用';s默认http(s)URLStreamHandler,java,httpurlconnection,urlconnection,httpsurlconnection,Java,Httpurlconnection,Urlconnection,Httpsurlconnection,我有一个库需要在我的一个项目中使用,不幸的是,它注册了自己的URLStreamHandler来处理http-url。有没有办法获取对Java默认http和https-URLStreamHandlers的引用,这样我就可以在URL的构造函数中指定其中一个来打开标准http连接,而无需使用库覆盖的协议 sun.net.www.protocol.http.Handler 有了这些,我现在可以做: URL url = new URL(null, "http://...", new sun.net.ww

我有一个库需要在我的一个项目中使用,不幸的是,它注册了自己的
URLStreamHandler
来处理http-
url
。有没有办法获取对Java默认http和https-
URLStreamHandlers
的引用,这样我就可以在
URL
的构造函数中指定其中一个来打开标准http连接,而无需使用库覆盖的协议

sun.net.www.protocol.http.Handler
有了这些,我现在可以做:

URL url = new URL(null, "http://...", new sun.net.www.protocol.http.Handler());
HttpURLConnection cxn = (HttpURLConnection) url.openConnection();
我得到了一个普通的Java HttpUrl连接,而不是库提供的连接


更新:

我发现了另一种更通用的方法:删除库的
URLStreamHandlerFactory

这有点棘手,因为URL类在技术上不允许您多次设置工厂或通过正式函数调用清除工厂,但通过一点反射魔法,我们无论如何都可以做到:

public static String unsetURLStreamHandlerFactory() {
    try {
        Field f = URL.class.getDeclaredField("factory");
        f.setAccessible(true);
        Object curFac = f.get(null);
        f.set(null, null);
        URL.setURLStreamHandlerFactory(null);
        return curFac.getClass().getName();
    } catch (Exception e) {
        return null;
    }
}
此方法获取
URL
-类中的静态字段
factory
,使其可访问,获取其当前值并将其更改为null。之后,它调用(现在已完成且没有错误)使此设置为“正式”,即给函数一个机会执行它可能希望执行的任何其他清理。然后返回以前注册的工厂的类名,仅供参考。如果出现任何错误,它会吞下异常(我知道,这是个坏主意…),并返回null

供参考:以下是最新版本

注意:这种方法可能比使用内部sun类(就可移植性而言)风险更大,因为它依赖于URL类的特定内部结构(即
工厂
-字段的存在和确切功能),但它的优点是,我不需要遍历所有代码来查找所有URL构造函数并添加处理程序参数。。。此外,它可能会破坏依赖于其注册处理程序的库的某些功能。幸运的是,这两个问题(可移植性和部分损坏的库功能)都不是与我的情况相关的问题


更新:#2

当我们使用反射时:下面是获取对默认处理程序的引用的最安全的方法:

public static URLStreamHandler getURLStreamHandler(String protocol) {
    try {
        Method method = URL.class.getDeclaredMethod("getURLStreamHandler", String.class);
        method.setAccessible(true);
        return (URLStreamHandler) method.invoke(null, protocol);        
    } catch (Exception e) {
        return null;
    }
}
你可以简单地称之为:

URLStreamHandler hander = getURLStreamHandler("http");
注意:此调用需要在库注册其
URLStreamHandlerFactory
之前进行,否则您将最终引用其处理程序


为什么我会认为这是最安全的方法?因为
URL.getURLStreamHandler(…)
不是完全私有的方法,而只是包私有的。因此,修改其调用签名可能会破坏同一包中的其他代码。此外,它的名字并没有留下太多的空间,除了我们正在寻找的东西。因此,我认为
URL
-类的不同/未来实现不太可能(尽管并非不可能)与此处所做的假设不兼容。

您可以注册自己的
URLStreamHandlerFactory
wirh
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory())

所以您可以使用标准处理程序


编辑:找到

以下是为Android重写的Markus解决方案:

public static URLStreamHandler getURLStreamHandler(String protocolIdentifier) {
    try {
        URL url = new URL(protocolIdentifier);
        Field handlerField = URL.class.getDeclaredField("handler");
        handlerField.setAccessible(true);
        return (URLStreamHandler)handlerField.get(url);
     } catch (Exception e) {
        return null;
    }
}
android中不存在getURLStreamHandler方法,因此需要以不同的方式执行。protocolIdentifier是协议名称加上冒号。您需要传入一个值,以便用于实例化所需协议URLStreamHandler的URL实例。如果需要标准的“jar:”协议处理程序,则必须输入如下内容:“jar:file:!/”。“文件”可以替换为“http”或“https”,因为它们都为您提供相同的处理程序实例


android支持的标准处理程序是http、https、文件、jar、ftp。

我认为在打开URL之前,您可以在URL上使用静态方法设置自己的流处理程序工厂。@bimaleshja正确,但是我必须重新实现协议处理。我想重用Java已经提供的东西。@bimaleshja实际上,尝试设置自己的工厂还有一个问题:我不能!:)
URL.setURLStreamHandlerFactory(…)
的文档说“在给定的Java虚拟机中,此方法最多只能调用一次。”而且由于库使用了此调用,如果我试图覆盖库的工厂,我就会遇到一个错误。。。除非。。。请参阅我的答案更新!;)@马库萨。但是,如果您对
main()
具有控制权,您应该能够在其他库调用该方法之前调用该方法,否?@Matthieu一般来说,是的,但不幸的是,我正在使用的库在设置其处理程序之后调用了我的主函数。另外,如果我可以先访问该方法,我可能可以查询默认的处理程序,但是如果我设置自己的处理程序,它可能会使库崩溃,而库将不再能够注册它的处理程序……我确信一些java代码检查器会将其标记为警告,因为您直接调用某个内部Sun实现类:)。我将实现我自己的处理程序并保持在公共API级别。@BimaleshJha不幸的是,要注册我自己的处理程序,我还需要脱离公共API级别…谢谢!非常感谢。非常感谢。事实上,官方上我不能这样做,因为setURLStreamHandlerFactory方法只能“在给定的Java虚拟机中最多调用一次”(参见JavaDoc了解
URL
)。这个电话已经被图书馆用光了。但是我发现了一种基于反射的方法可以绕过这个限制。它甚至允许我完全移除库的工厂,所以我实际上不再需要自己的工厂。事情将回到默认的处理程序。(有关详细信息,请参阅更新我的答案。)
public static URLStreamHandler getURLStreamHandler(String protocolIdentifier) {
    try {
        URL url = new URL(protocolIdentifier);
        Field handlerField = URL.class.getDeclaredField("handler");
        handlerField.setAccessible(true);
        return (URLStreamHandler)handlerField.get(url);
     } catch (Exception e) {
        return null;
    }
}