Зарегистрировать пользовательский URLStreamHandler в веб-приложении Spring (Tomcat)
я пытаюсь зарегистрировать заказ URLStreamHandler
для обработки запросов к URL-адресам Amazon S3 в общем виде. Реализация обработчика выглядит в значительной степени как S3-URLStreamHandler (github). Я не ставил свой Handler
класса в sun.net.www.protocol.s3
пакет, но используется пользовательский пакет com.github.dpr.protocol.s3
. Чтобы сделать Java pickup этим пакетом, я предоставил системное свойство -Djava.protocol.handler.pkgs="com.github.dpr.protocol"
следующая документация URL-адресов класс. Однако, если я попытаюсь обработать S3-URL, как s3://my.bucket/some-awesome-file.txt
, Я MalformedURLException
:
Caused by: java.net.MalformedURLException: unknown protocol: s3
at java.net.URL.<init>(URL.java:600)
at java.net.URL.<init>(URL.java:490)
at java.net.URL.<init>(URL.java:439)
at java.net.URI.toURL(URI.java:1089)
...
мое приложение-это веб-приложение на основе Spring, которое в настоящее время работает в 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 использует "неправильные" загрузчики классов для поиска обработчика.
я в курсе этот вопрос на SOF, но я не должен зарегистрируйте пользовательский URLStreamHandlerFactory
поскольку tomcat регистрирует свою собственную фабрику (org.apache.naming.resources.DirContextURLStreamHandlerFactory
) при запуске приложения и должна быть зарегистрирована только одна фабрика для одного JVM. Котяра это DirContextURLStreamHandlerFactory
позволяет добавлять пользовательские фабрики, которые могут использоваться для обработки пользовательских протоколов, но я не хочу добавлять зависимость от Tomcat в коде приложения, так как приложение должно работать в разных контейнерах.
есть ли способ зарегистрировать обработчик для пользовательских URL-адресов в независимом контейнере мода?
обновление 2017-01-25:
Я дал различные варианты @Nicolas Filotto предложил попробовать:
опция 1-установить пользовательский URLStreamHandlerFactory с помощью отражения
Эта опция работает как ожидалось. Но с помощью отражения он вводит жесткую зависимость от внутренней работы java.net.URL
класса. К счастью, Oracle не слишком стремится к исправлению проблем удобства в основных классах java-на самом деле это сообщение об ошибке открыт почти для 14 лет (отличная работа Sun / Oracle) - и это может быть легко протестировано.
Вариант 2-Поместите обработчик JAR в {JAVA_HOME}/jre/lib / ext
Этот вариант также работает. Но только добавление обработчика jar в качестве системного расширения не сделает трюк-конечно. Также необходимо будет добавить все зависимости обработчика. Поскольку эти классы видны всем приложениям, использующим эту Java установки, это может привести к нежелательным последствиям из-за разных версий одной библиотеки в classpath.
Вариант 3-Поместите обработчик JAR в {CATALINA_HOME} / lib
Это не работает. Согласно документация загрузчика классов Tomcat ресурсы, помещенные в этот каталог, будут загружены с помощью Tomcat's общие загрузчика. Этот загрузчик классов не будет использоваться 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 ответов
можно:
1. Используйте декоратор
один из способов установить пользовательскую URLStreamHandlerFactory
, может быть, использовать декоратор типа URLStreamHandlerFactory
для того чтобы обернуть URLStreamHandlerFactory
это, возможно, уже было определено (в данном случае tomcat). Хитрая часть заключается в том, что вам нужно использовать отражение (которое довольно хаки), чтобы получить и сбросить текущий factory
потенциально определенными.
вот псевдо-код оформитель:
public class S3URLStreamHandlerFactory implements URLStreamHandlerFactory {
// The wrapped URLStreamHandlerFactory's instance
private final Optional<URLStreamHandlerFactory> delegate;
/**
* Used in case there is no existing URLStreamHandlerFactory defined
*/
public S3URLStreamHandlerFactory() {
this(null);
}
/**
* Used in case there is an existing URLStreamHandlerFactory defined
*/
public S3URLStreamHandlerFactory(final URLStreamHandlerFactory delegate) {
this.delegate = Optional.ofNullable(delegate);
}
@Override
public URLStreamHandler createURLStreamHandler(final String protocol) {
if ("s3".equals(protocol)) {
return // my S3 URLStreamHandler;
}
// It is not the s3 protocol so we delegate it to the wrapped
// URLStreamHandlerFactory
return delegate.map(factory -> factory.createURLStreamHandler(protocol))
.orElse(null);
}
}
здесь является ли код для его определения:
// 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)
);
}
}
NB: начиная с Java 9, вам нужно будет добавить --add-opens java.base/java.net=myModuleName
к вашей команде запуска, чтобы позволить глубокое отражение на пакете java.net
это включает в себя класс URL
из вашего модуля myModuleName
в противном случае вызов setAccessible(true)
буду ставить RuntimeException
.
2. Разверните его как расширение
еще один способ избежать этого ClassLoder
проблема может заключаться в перемещении вашего пользовательского URLStreamHandler
в специальный сосуд и разверните его в ${JAVA-HOME}/jre/lib/ext
как установленное расширение (со всеми его зависимостями), так что он будет доступен в расширении ClassLoader
, таким образом, ваш класс будет определен в ClassLoader
достаточно высоко в иерархии, чтобы быть замеченными.