Java ServiceLoader объяснение

Я пытаюсь понять Java ServiceLoader концепции, рабочий механизм и конкретные варианты использования, но сочтите официальную документацию слишком абстрактной и запутанной.

Прежде всего, в документации описываются услуги и поставщики услуг. Сервис - это набор интерфейсов и абстрактных классов, упакованных в jar-архив (библиотека API). Поставщик услуг - это набор классов, который реализует или расширяет API, упакованный в отдельный файл JAR (библиотека поставщика).


Пока все хорошо, но тогда документация сбивает с толку.

В целях загрузки служба представлена ​​одним типом, то есть единственным интерфейсом или абстрактным классом. (Можно использовать конкретный класс, но это не рекомендуется.) Поставщик данной услуги содержит один или несколько конкретных классов, которые расширяют этот тип службы данными и кодом, специфичными для поставщика. Класс провайдера обычно является не самим провайдером, а прокси-сервером, который содержит достаточно информации, чтобы решить, способен ли провайдер удовлетворить определенный запрос вместе с кодом, который может создать фактического провайдера по требованию. Детали классов провайдеров, как правило, сильно зависят от услуг; ни один класс или интерфейс не может объединить их, поэтому такой тип здесь не определен. Единственным требованием, предъявляемым этой возможностью, является то, что классы провайдеров должны иметь конструктор с нулевым аргументом, чтобы их можно было создавать во время загрузки.

Так что же на самом деле тип сервиса и класс провайдера? У меня складывается впечатление, что тип сервиса - это фасад в библиотеке API, а класс провайдера - это реализация этого интерфейса фасада в библиотеке провайдера, класс, который ServiceLoader на самом деле загружает. Это правильно? Но это все еще не имеет большого смысла для меня, как все компоненты связаны друг с другом.

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


Тогда о файле конфигурации провайдера...

Поставщик услуг определяется путем размещения файла конфигурации поставщика в каталоге ресурсов META-INF/services. Имя файла - это полное двоичное имя типа службы. Файл содержит список полностью определенных двоичных имен конкретных классов провайдеров, по одному на строку...

Файл конфигурации с именем конкретного поставщика не обязательно должен находиться в том же файле JAR или другом модуле распространения, что и сам поставщик. Поставщик должен быть доступен из того же загрузчика классов, который первоначально запрашивался для поиска файла конфигурации; обратите внимание, что это не обязательно загрузчик классов, из которого файл был фактически загружен.

Означает ли это, что для API с типом службы org.foo.BarServiceType в classpath должен существовать jar провайдера с классом, реализующим этот тип, и META-INF/services/org.foo.BarServiceType Файл конфигурации именованного провайдера, в котором указан этот класс провайдера Classloader который загружен ServiceLoader найти и связать провайдера по API?

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

В файле конфигурации провайдера перечислены классы провайдера, и он может быть связан в пакете провайдера (зачем он в любом случае перечисляет несколько классов, если он объединен?) Или поступать извне. Но какой подход является более распространенным: предоставить файл конфигурации поставщику или предоставить список поддерживаемых поставщиков из самой библиотеки API? Или последнее заблуждение?


Наконец о ServiceLoader

Где ServiceLoader на самом деле создается и вызывается для загрузки поставщика услуг? Это происходит в фабричном методе, предоставленном библиотекой API? Например, делает LoggingFactory.getLogger(clazz) SLF4J внутренне делегировать ServiceLoader, который использует отражение, чтобы прочитать файлы конфигурации провайдера и загрузить службы?

Как механизм загрузки сервисов относится к ситуациям, когда существуют либо несколько провайдеров с их файлами конфигурации, либо есть запись в файле конфигурации провайдера, но не сам класс?

И каковы некоторые другие конкретные случаи использования ServiceLoader вне рамок логирования? Насколько он используется в таких популярных средах, как Java EE, Spring и Hibernate? Какие есть альтернативы механизму загрузки сервисов со слабосвязанной привязкой API- провайдера, или есть?

1 ответ

Решение

Тип сервиса - это интерфейс или абстрактный класс, который передается в ServiceLoader.load или ServiceLoader.loadInstalled. Поставщик - это конкретная реализация этого интерфейса или абстрактного класса.

Поскольку служба часто состоит из большого количества функциональных возможностей, полезно, если такие большие классы загружаются не сразу, когда их сканирует ServiceLoader. Вместо этого, лучший дизайн - это крошечный класс, который предоставляет доступ к основным функциям. Например, ServiceLoader.load(FileSystemProvider.class) не загружает целую библиотеку, способную обрабатывать определенный набор файловых систем; скорее он загружает объект FileSystemProvider, который способен инициализировать эту библиотеку, если и только если приложение решит использовать ее. Это позволяет самому поставщику оставаться легким.

Означает ли это, что для API с типом службы org.foo.BarServiceType в classpath должен существовать jar провайдера с классом, реализующим этот тип, и META-INF/services/org.foo.BarServiceType с именем файла конфигурации провайдера, в котором указан этот провайдер класс, все доступны тем же Classloader, который загрузил ServiceLoader для поиска и привязки провайдера по API?

Да. Обычно это довольно просто; например, файл.jar, содержащий класс org.foo.BarServiceType, также содержит META-INF/services/org.foo.BarServiceType запись, содержимое которой состоит из одной строки текста.

зачем вообще перечислять несколько классов, если они связаны?

Некоторые поставщики услуг могут справиться только с некоторыми ситуациями. Примером может служить класс IIORegistry (в котором не упоминается ServiceLoader, и он фактически присутствовал задолго до того, как ServiceLoader был добавлен в Java SE, но функционирует идентично ServiceLoader). Может быть одна реализация ImageReaderSpi, которая предоставляет ImageReaders для PNG, другая ImageReaderSpi, которая обеспечивает ImageReaders для JPEG, и так далее. Каждый такой класс поставщика услуг (то есть каждая конкретная реализация ImageReaderSpi) будет иметь различную логику в своем методе canDecodeInput, поэтому экземпляры ImageReader тяжелого веса не создаются, если они не нужны приложению.

Но какой подход является более распространенным: предоставить файл конфигурации поставщику или предоставить список поддерживаемых поставщиков из самой библиотеки API?

Если я правильно понимаю ваш вопрос, ответ таков: на практике дескриптор SPI всегда находится в том же файле.jar, что и классы провайдера, которые он называет.

Что касается последней части вашего вопроса: я не думаю, что фреймворки логгеров используют ServiceLoader. Для примеров использования ServiceLoader посмотрите все пакеты Java SE, которые заканчиваются на .spi (java.awt.im.spi, java.nio.channels.spi, java.nio.charset.spi и т. д.). Большинство из них не говорят, что полагаются на ServiceLoader, но все они описывают свое поведение поиска, и вы обнаружите, что оно почти всегда идентично поведению ServiceLoader.

Как механизм загрузки сервисов относится к ситуациям, когда существуют либо несколько провайдеров с их файлами конфигурации, либо есть запись в файле конфигурации провайдера, но не сам класс?

В случае нескольких поставщиков, присутствующих в пути к классам, ServiceLoader просто вернет их всех в свой итератор.

Для неправильных файлов конфигурации выдается ошибка ServiceConfigurationError из метода next() итератора ServiceLoader. Из документации:

Ошибка выдается, когда что-то идет не так при загрузке поставщика услуг.

Эта ошибка будет выдана в следующих ситуациях:

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

В итоге:

  • Класс реализации поставщика услуг обычно находится в том же файле.jar, что и файл дескриптора META-INF / services.
  • Класс поставщика услуг обычно является легковесным классом, который позволяет приложению получать доступ к более тяжелым классам, если и только если приложение решит, что оно им необходимо.
  • Многие части Java SE используют ServiceLoader, особенно пакеты *.spi.
  • Если существует много реализаций службы, все они возвращаются в итератор.
  • Неправильные дескрипторы приводят к ошибке ServiceConfigurationError.
Другие вопросы по тегам