Комментарии 14
Интересно, не знал про такую штуку. Но полностью согласен с вашим финальным комментарием про IoC. Что-то как-то некомфортно себя чувствовать, когда какой-то хитрый код что-то за меня делает незаметно.
Непонятно, где использование этого механизма оправданно. Даже при работе с JDBC обычно грузишь название драйвера из пропертей, чтобы поменять можно было легко, поправив один текстовый файл. А тут, получается, надо будет проект пересобирать.
Сдается мне, этот SPI агент Сатаны, чтобы всех нас утащить в jar-hell. Есть у кого идеи, как можно удобно использовать этот SPI? У меня ничего в голове не появилось после 10 минут обдумывания.
Непонятно, где использование этого механизма оправданно. Даже при работе с JDBC обычно грузишь название драйвера из пропертей, чтобы поменять можно было легко, поправив один текстовый файл. А тут, получается, надо будет проект пересобирать.
Сдается мне, этот SPI агент Сатаны, чтобы всех нас утащить в jar-hell. Есть у кого идеи, как можно удобно использовать этот SPI? У меня ничего в голове не появилось после 10 минут обдумывания.
0
пример где это можно было бы использовать: liquibase (вместо этого автор сделал свой собственный костыль на основе манифеста). Скажем, у liquibase есть свой собственный класс для работы с MySQL. Работает это так: код хочет создать новую таблицу в БД, запрашивает у сервис локатора все доступные имплементации интерфейса СоздаваторТаблиц, идет по ним и у каждой интересуется, умеет ли она работать с MySQL. Находит одну и с ее помощью создает таблицу. Как я сказал все работает прямо out-of-the-box. Однако, код не умеет устанавливать engine. И в моей личной базе данных плодит MyISAM таблицы, что мне не нравится. Благодаря механизму расширений, я создаю свой джарничек, который кладу в класспаф, в нем правильно настраиваю окружение (манифест/сервисы) и пишу свою собственную имплементацию, которая твикает sql так, как мне нужно: pastebin.com/PRxFuWTa. Чтобы избежать проблем с jar-hell автор использует метод getPriority, чтобы выбирать между иначе равнозначными имплементациями.
+3
Еще похожий пример — slf4j, используется свой костыль.
Еще может быть полезно, как я уже в статье говорил, если приложение достаточно старое и громоздкое и очень сложно перейти на использование IoC-контейнера, например если построено на синглтонах и фабриках. Вот здесь этот механизм как раз кстати,и волки целы, и овцы сыты и код практически не надо переделывать, и расширения писать удобно.
Еще может быть полезно, как я уже в статье говорил, если приложение достаточно старое и громоздкое и очень сложно перейти на использование IoC-контейнера, например если построено на синглтонах и фабриках. Вот здесь этот механизм как раз кстати,
0
Стандартный ServiceLoader не пригоден для использования — в нем нельзя (или по крайней мере очень трудно) подменить возвращаемые сервисы, а без этого не возможно тестировать методы. Поэтому нужно писать свою реализацию.
0
имхо ServiceLoader используется на этапе «сборки» графа объектов и для него нужно писать интеграционные тесты из серии «собрали джарник, запустили — работает», а не юнит-тесты, т.к. не очень понятно, что именно тут можено про-юнит-тестировать.
0
ServiceLoader тем и хорош, что его можно использовать везде. Вот только тестировать после этого нельзя.
0
Все очень просто — если надо во время тестов подменить провайдер, нужно просто подложить в тест класпас нужный файлик, ну или как вариант подложить свой фэйковый класлоадер.
0
А если у нас 2 теста и каждому из них нужна своя мок-реализация сервиса?
С фейковым класслоадером тоже есть проблемы. Общаться с тестируемым объектом нужно через рефлекшен — ибо он не совместим с обычным классом (класслоадер-то другой!). Написать фейковый класслоадер тоже весьма нетривиально.
С фейковым класслоадером тоже есть проблемы. Общаться с тестируемым объектом нужно через рефлекшен — ибо он не совместим с обычным классом (класслоадер-то другой!). Написать фейковый класслоадер тоже весьма нетривиально.
0
На каждую архитектуру приложения нужна своя особенная архитектура тестов, и она тоже важна. У большинства фреймворков есть готовые реализации архитектуры для тестов, у голой джавы нет, что тут поделаешь, приходится писать свою, но все равно нерешаемых задач нет.
+1
Mock classloader гуглится по сочетанию «mock classloader», их там дофига
+1
Кроме прочего SPI штатно используется для определения текущего XML парсера. Очень кривое решение, с теми же XML парсерами от него больше проблем чем пользы (грузится тот, который раньше указан в classpath-е, чтобы это изменить приходится прибегать к разного рода шаманству).
К тому же, SPI-механизм не поддерживает lazy загрузку и, как следствие, жаден до ресурсов. Например перебор доступных реализаций JDBC загрузит в jvm все найденные драйверы — если их много, то это тьма памяти и времени. А нужен-то обычно всего один.
К тому же, SPI-механизм не поддерживает lazy загрузку и, как следствие, жаден до ресурсов. Например перебор доступных реализаций JDBC загрузит в jvm все найденные драйверы — если их много, то это тьма памяти и времени. А нужен-то обычно всего один.
0
В коде
ServiceLoader будет всякий раз создавать итератор и заполнять внутренний кэш на итерировании. Если в ClassLoader не добавляются динамически новые ресурсы с META-INF/services/..., то было бы более производительно создать ServiceLoader один раз (например, записав его в статическое финальное поле), и просто итерировать по нему в других методах.
Если все-таки в ClassLoader в рантайме добавляются ресурсы, то можно при добавлении такого ресурса вызывать ServiceLoader.reload(), чтобы очистить кэш уже загруженных инстансов.
Единственная проблема — в том, что ServiceLoader не потокобезопасен. Однако проблему можно решить, создав какой-нибудь метод типа
public class ReportRenderer {
//...
public List<String> findMusic() {
final List<String> music = new ArrayList<String>();
for (final MusicFinder finder : ServiceLoader.load(MusicFinder.class)) {
music.addAll(finder.getMusic());
}
Collections.sort(music);
return music;
}
//...
}
ServiceLoader будет всякий раз создавать итератор и заполнять внутренний кэш на итерировании. Если в ClassLoader не добавляются динамически новые ресурсы с META-INF/services/..., то было бы более производительно создать ServiceLoader один раз (например, записав его в статическое финальное поле), и просто итерировать по нему в других методах.
Если все-таки в ClassLoader в рантайме добавляются ресурсы, то можно при добавлении такого ресурса вызывать ServiceLoader.reload(), чтобы очистить кэш уже загруженных инстансов.
Единственная проблема — в том, что ServiceLoader не потокобезопасен. Однако проблему можно решить, создав какой-нибудь метод типа
public static void visitAllMusicFinders(Consumer<MusicFinder> consumer) {
synchronized (serviceLoader) {
for (final MusicFinder musicFinder : serviceLoader) {
consumer.accept(musicFinder);
}
}
}
0
Спасибо! Через 8 лет статья оказалась полезной. Нагуглена по java SPI mechanism :)
0
Зарегистрируйтесь на Хабре , чтобы оставить комментарий
Использование SPI механизма для создания расширений