Java ServiceLoader с несколькими загрузчиками классов - PullRequest
35 голосов
/ 12 августа 2011

Каковы оптимальные методы использования ServiceLoader в среде с несколькими ClassLoaders?Документация рекомендует создавать и сохранять один экземпляр службы при инициализации:

private static ServiceLoader<CodecSet> codecSetLoader = ServiceLoader.load(CodecSet.class);

Это инициализирует ServiceLoader с использованием текущего контекстного загрузчика классов.Теперь предположим, что этот фрагмент содержится в классе, загруженном с использованием общего загрузчика классов в веб-контейнере, и несколько веб-приложений хотят определить свои собственные реализации служб.Они не будут обнаружены в приведенном выше коде, возможно даже, что загрузчик будет инициализирован с использованием первого загрузчика классов контекста webapps и предоставит неправильную реализацию другим пользователям.мудро, так как он должен каждый раз перечислять и анализировать служебные файлы. Редактировать: Это даже может быть большой проблемой производительности, как показано в этом ответе относительно реализации XPath в java .

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

Ответы [ 5 ]

59 голосов
/ 30 августа 2011

Лично мне не нравится ServiceLoader ни при каких обстоятельствах.Он медленный и неоправданно расточительный, и вы мало что можете сделать, чтобы оптимизировать его.

Я также считаю его немного ограниченным - вам действительно нужно уйти с дороги, если вы хотите сделать больше, чем поиск по типув одиночку.

ResourceFinder xbean-finder

  • ResourceFinder представляет собой автономный файл Java, способный заменить использование ServiceLoader.Копирование / вставка повторного использования не проблема.Это один файл java, лицензированный ASL 2.0 и доступный от Apache.

Прежде чем наше внимание станет слишком коротким, вот как он может заменить ServiceLoader

ResourceFinder finder = new ResourceFinder("META-INF/services/");
List<Class<? extends Plugin>> impls = finder.findAllImplementations(Plugin.class);

Это найдетвсе реализации META-INF/services/org.acme.Plugin в вашем пути к классам.

Обратите внимание, что на самом деле не создаются все экземпляры.Выберите те, которые вам нужны, и вы на один newInstance() вызов откажетесь от наличия экземпляра.

Почему это мило?

  • Насколько сложно позвонить newInstance() с правильной обработкой исключений?Не сложно.
  • Приятно иметь возможность создавать только те экземпляры, которые вам нужны.
  • Теперь вы можете поддерживать аргументы конструктора!

Сужение области поиска

Если вы хотите просто проверить определенные URL-адреса, вы можете сделать это легко:

URL url = new File("some.jar").toURI().toURL();
ResourceFinder finder = new ResourceFinder("META-INF/services/", url);

Здесь при любом использовании этого экземпляра ResourceFinder будет выполняться поиск только файла «some.jar».

Существует также удобный класс, называемый UrlSet, который позволяет очень легко выбирать URL-адреса из пути к классам.

ClassLoader webAppClassLoader = Thread.currentThread().getContextClassLoader(); 
UrlSet urlSet = new UrlSet(webAppClassLoader);
urlSet = urlSet.exclude(webAppClassLoader.getParent());
urlSet = urlSet.matching(".*acme-.*.jar");

List<URL> urls = urlSet.getUrls();

Альтернативные стили "service"

Скажем, вы хотите применить ServiceLoader введите концепцию, чтобы изменить дизайн обработки URL и найти / загрузить java.net.URLStreamHandler для определенного протокола.

Вот как вы можете расположить сервисы в вашем classpath:

  • META-INF/java.net.URLStreamHandler/foo
  • META-INF/java.net.URLStreamHandler/bar
  • META-INF/java.net.URLStreamHandler/baz

Где foo - это простой текстовый файл, который содержит имя реализации службы, как и прежде.Теперь скажите, что кто-то создает foo://... URL.Мы можем быстро найти реализацию для этого с помощью:

ResourceFinder finder = new ResourceFinder("META-INF/");
Map<String, Class<? extends URLStreamHandler>> handlers = finder.mapAllImplementations(URLStreamHandler.class);
Class<? extends URLStreamHandler> fooHandler = handlers.get("foo");

Альтернативных стилей "обслуживания" 2

Допустим, вы хотите поместить некоторую информацию о конфигурации в файл службы, чтобы она содержала болеепросто имя класса.Вот альтернативный стиль, который разрешает службы для файлов свойств.По соглашению один ключ будет именами классов, а другие ключи - свойствами, которые можно будет ввести.

Итак, red - это файл свойств

  • META-INF/org.acme.Plugin/red
  • META-INF/org.acme.Plugin/blue
  • META-INF/org.acme.Plugin/green

Вы можете искать вещи так же, как и раньше.

ResourceFinder finder = new ResourceFinder("META-INF/");

Map<String,Properties> plugins = finder.mapAllProperties(Plugin.class.getName());
Properties redDefinition = plugins.get("red");

Вот как вы можете использовать эти свойства с xbean-reflect, еще одна маленькая библиотека, которая может дать вам IoC без фреймворка.Вы просто даете ему имя класса и несколько пар «имя-значение», и он будет создавать и вставлять.

ObjectRecipe recipe = new ObjectRecipe(redDefinition.remove("className").toString());
recipe.setAllProperties(redDefinition);

Plugin red = (Plugin) recipe.create();
red.start();

Вот как это может выглядеть «прописано» в длинной форме:

ObjectRecipe recipe = new ObjectRecipe("com.example.plugins.RedPlugin");
recipe.setProperty("myDateField","2011-08-29");
recipe.setProperty("myIntField","100");
recipe.setProperty("myBooleanField","true");
recipe.setProperty("myUrlField","http://www.stackoverflow.com");
Plugin red = (Plugin) recipe.create();
red.start();

Библиотека xbean-reflect - это шаг за пределы встроенного API JavaBeans, но немного лучше, не требуя от вас полного перехода к полнофункциональной среде IoC, такой как Guice или Spring.Он поддерживает фабричные методы и аргументы конструктора и внедрение сеттера / поля.

Почему ServiceLoader так ограничен?

Устаревший код в JVM повреждает сам язык Java.Многие вещи обрезаются до костей перед добавлением в JVM, потому что вы не можете обрезать их после.ServiceLoader является ярким примером этого.API ограничен, и реализация OpenJDK занимает где-то около 500 строк, включая javadoc.

Там нет ничего особенного, и заменить его легко.Если это не работает для вас, не используйте его.

Область пути к классам

* * * * *

API, в чистом виде сужение области поиска URL-адресов является истинным решением этой проблемы.Серверы приложений имеют довольно много URL-адресов сами по себе, не считая фляги в вашем приложении.Например, Tomcat 7 в OSX имеет около 40 ~ URL-адресов только в StandardClassLoader (это родитель для всех загрузчиков классов веб-приложений).

Чем больше ваш сервер приложений, тем дольше даже простой поиск.

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

Сузьте URL-адреса до 5 или 12, которые вас действительно волнуют, и вы можете выполнять все виды загрузки услуг и никогда не замечать попадания.

5 голосов
/ 26 августа 2011

Вы пытались использовать версию с двумя аргументами, чтобы вы могли указать, какой загрузчик классов использовать?Т.е. java.util.ServiceLoader.load(Class, ClassLoader)

4 голосов
/ 23 августа 2011

Mu.

В системе 1x WebContainer <-> Nx WebApplication, экземпляр ServiceLoader, созданный в WebContainer, не будет принимать никакие классы, определенные в WebApplications, только те, которые находятся в контейнере.ServiceLoader, созданный в WebApplication, обнаружит классы, определенные в приложении, в дополнение к классам, определенным в контейнере.

Имейте в виду, что WebApplications необходимо хранить отдельно, они спроектированы таким образом, что будет сломаться, если вы попытаетесь обойти это, и они не являются методом и системой, доступными для расширения контейнера - если ваша библиотека - простой Jar, просто поместите ее в соответствующую папку расширения контейнера.

3 голосов
/ 16 августа 2011

Мне очень нравится ответ Нила по ссылке, которую я добавил в своем комментарии.Из-за того, что у меня такие же ожидания в моем недавнем проекте.

"Еще одна вещь, которую следует иметь в виду при использовании ServiceLoader, - попытаться абстрагировать механизм поиска. Механизм публикации довольно приятный, чистый и декларативный.via java.util.ServiceLoader) так же безобразен, как ад, реализован как сканер пути к классам, который ужасно ломается, если вы помещаете код в любую среду (например, OSGi или Java EE), которая не имеет глобальной видимости.с этим потом вам будет тяжело запустить его на OSGi позже. Лучше написать абстракцию, которую вы сможете заменить, когда придет время ».

Я действительно столкнулся с этой проблемой в среде OSGi, на самом деле это просто затмение в нашем проекте.Но я, к счастью, исправил это своевременно.Мой обходной путь - использовать один класс из плагина, который я хочу загрузить, и получить из него classLoader.Это будет правильным решением.Я не использовал стандартный ServiceLoader, но мой процесс довольно похож, используйте свойства для определения классов плагинов, которые мне нужно загрузить.И я знаю, что есть другой способ узнать загрузчик классов каждого плагина.Но, по крайней мере, мне не нужно это использовать.

Честно, мне не нравятся обобщения, используемые в ServiceLoader.Потому что он ограничен тем, что один ServiceLoader может обрабатывать классы только для одного интерфейса.Ну, это действительно полезно?В моей реализации, это не заставит вас этим ограничением.Я просто использую одну реализацию загрузчика для загрузки всех классов плагинов.Я не вижу смысла использовать два или более.Из-за потребителя можно узнать из конфигурационных файлов об отношениях между интерфейсами и реализацией.

1 голос
/ 28 августа 2011

Этот вопрос кажется более сложным, чем я ожидал.На мой взгляд, существует 3 возможных стратегии работы с ServiceLoaders.

  1. Использовать статический экземпляр ServiceLoader и поддерживать загрузку классов только из того же загрузчика классов, что и тот, который содержит ссылку на ServiceLoader.Это будет работать, когда

    • Конфигурация и реализация службы находятся в общем загрузчике классов, и все дочерние загрузчики классов используют одну и ту же реализацию.Пример в документации ориентирован на этот вариант использования.

      Или

    • Конфигурация и реализация помещаются в каждый дочерний загрузчик классов и развертываются вдоль каждого веб-приложения в WEB-INF/lib.

    В этом сценарии невозможно развернуть службу в общем загрузчике классов и позволить каждому веб-приложению выбрать собственную реализацию службы.

  2. InitializeServiceLoader при каждом доступе, передающий контекстный загрузчик классов текущего потока в качестве второго параметра.Этот подход используется для API JAXP и JAXB, хотя они используют собственную реализацию FactoryFinder вместо ServiceLoader.Таким образом, можно связать синтаксический анализатор xml с веб-приложением и получить его автоматически, например, с помощью DocumentBuilderFactory#newInstance.

    . Этот поиск оказывает влияние на производительность , но в случаеВремя анализа xml для поиска реализации невелико по сравнению со временем, необходимым для фактического анализа XML-документа.В библиотеке, которую я представляю, сама фабрика довольно проста, поэтому время поиска будет доминировать в производительности.

  3. Каким-то образом кэшируйте класс реализации с ключом загрузчика классов контекста.Я не совсем уверен, возможно ли это во всех вышеупомянутых случаях, не вызывая утечек памяти.

В заключение, я, вероятно, буду игнорировать эту проблему и потребовать развертывания библиотекивнутри каждого веб-приложения, т. е. вариант 1b выше.

...