我正在尝试注册一个自定义URLStreamHandler来以通用方式处理对Amazon S3 URL的请求.处理程序的实现看起来非常像S3-URLStreamHandler (github).我没有将我的Handler类放入sun.net.www.protocol.s3包中,而是使用自定义包com.github.dpr.protocol.s3.为了使Java拾取这个包,我在URL class的文档之后提供了系统属性-Djava.protocol.handler.pkgs =“com.github.dpr.protocol”.但是,如果我尝试处理像s3这样的s3-URL ://my.bucket/some-awesome-file.txt,我得到一个MalformedURLException:
Caused by: java.net.MalformedURLException: unknown protocol: s3
at java.net.URL.
我的应用程序是一个基于Spring的Web应用程序,目前在tomcat中运行,但不应该对底层应用程序容器的任何知识混乱.
我已经调试了相应的代码,发现我的URLStreamHandler无法初始化,因为用于加载类的类加载器不知道它.这是来自java.net.URL(jdk 1.8.0_92)的相应代码:
1174: try {
1175: cls = Class.forName(clsName);
1176: } catch (ClassNotFoundException e) {
1177: ClassLoader cl = ClassLoader.getSystemClassLoader();
1178: if (cl != null) {
1179: cls = cl.loadClass(clsName);
1180: }
1181: }
java.net.URL类的类加载器(由Class.forName使用)为null,表示引导类加载器和系统类加载器不知道我的类.如果我在那里放置一个断点并尝试使用当前线程的类加载器加载我的处理程序类,它可以正常工作.这是我的类显然存在于应用程序的类路径中,但Java使用“错误的”类加载器来查找处理程序.
我知道this question on SOF,但我不能注册自定义URLStreamHandlerFactory,因为tomcat在应用程序启动时注册它自己的工厂(org.apache.naming.resources.DirContextURLStreamHandlerFactory),并且只能为一个JVM注册一个工厂. Tomcat的DirContextURLStreamHandlerFactory允许添加可用于处理自定义协议的用户工厂,但我不想在我的应用程序代码中向Tomcat添加依赖项,因为应用程序应该在不同的容器中运行.
更新2017-01-25:
我给了不同的选择@Nicolas Filotto提出了一个尝试:
选项1 – 使用反射设置自定义URLStreamHandlerFactory
此选项按预期工作.但是通过使用反射,它引入了对java.net.URL类的内部工作的紧密依赖.幸运的是,Oracle并不急于修复基本java类中的便利问题 – 实际上this related bug report已经开放了将近14年(伟大的工作Sun / Oracle) – 这很容易进行单元测试.
选项2 – 将处理程序JAR放入{JAVA_HOME} / jre / lib / ext
此选项也有效.但是,只添加处理程序jar作为系统扩展将无法做到 – 当然.人们还需要添加处理程序的所有依赖项.由于这些类对使用此Java安装的所有应用程序都是可见的,因此可能会由于类路径中相同库的不同版本而导致不希望的影响.
选项3 – 将处理程序JAR放入{CATALINA_HOME} / lib
这不起作用.根据Tomcat’s classloader documentation,将使用Tomcat的Common类加载器加载放入此目录的资源. java.net.URL不会使用此类加载器来查找协议处理程序.
鉴于这些选项,我将继续使用反射变体.所有选项都不是很好,但至少第一个选项可以轻松测试,不需要任何部署逻辑.但是我稍微调整了代码以使用相同的锁对象进行同步,如java.net.URL所做的那样:
public static void registerFactory() throws Exception {
final Field factoryField = URL.class.getDeclaredField("factory");
factoryField.setAccessible(true);
final Field lockField = URL.class.getDeclaredField("streamHandlerLock");
lockField.setAccessible(true);
// use same lock as in java.net.URL.setURLStreamHandlerFactory
synchronized (lockField.get(null)) {
final URLStreamHandlerFactory urlStreamHandlerFactory = (URLStreamHandlerFactory) factoryField.get(null);
// Reset the value to prevent Error due to a factory already defined
factoryField.set(null,null);
URL.setURLStreamHandlerFactory(new AmazonS3UrlStreamHandlerFactory(urlStreamHandlerFactory));
}
}
1.使用装饰器
设置自定义URLStreamHandlerFactory的一种方法是使用URLStreamHandlerFactory类型的装饰器来包装可能已经定义的URLStreamHandlerFactory(在本例中为tomcat).棘手的部分是你需要使用反射(这非常hacky)来获取和重置当前可能定义的工厂.
这是装饰器的伪代码:
public class S3URLStreamHandlerFactory implements URLStreamHandlerFactory {
// The wrapped URLStreamHandlerFactory's instance
private final Optional
以下是定义它的代码:
// Retrieve the field "factory" of the class URL that store the
// URLStreamHandlerFactory used
Field factoryField = URL.class.getDeclaredField("factory");
// It is a package protected field so we need to make it accessible
factoryField.setAccessible(true);
// Get the current value
URLStreamHandlerFactory urlStreamHandlerFactory
= (URLStreamHandlerFactory) factoryField.get(null);
if (urlStreamHandlerFactory == null) {
// No factory has been defined so far so we set the custom one
URL.setURLStreamHandlerFactory(new S3URLStreamHandlerFactory());
} else {
// Retrieve the field "streamHandlerLock" of the class URL that
// is the lock used to synchronize access to the protocol handlers
Field lockField = URL.class.getDeclaredField("streamHandlerLock");
// It is a private field so we need to make it accessible
lockField.setAccessible(true);
// Use the same lock to reset the factory
synchronized (lockField.get(null)) {
// Reset the value to prevent Error due to a factory already defined
factoryField.set(null,null);
// Set our custom factory and wrap the current one into it
URL.setURLStreamHandlerFactory(
new S3URLStreamHandlerFactory(urlStreamHandlerFactory)
);
}
}
注意:从Java 9开始,您需要在启动命令中添加–add-opens java.base / java.net = myModuleName,以允许对包含模块myModuleName中的类URL的java.net包进行深入反思.调用setAccessible(true)将引发RuntimeException.
2.将其部署为扩展
应该避免此ClassLoder问题的另一种方法是将自定义URLStreamHandler移动到专用jar中,并将其作为installed extension(及其所有依赖项)部署在${JAVA-HOME} / jre / lib / ext中,以便它可用在Extension ClassLoader中,这样你的类将在ClassLoader中定义,在层次结构中足够高,可以看到.