Несколько дней новогоднего волшебства:
Успейте начать обучение в 2018-ом году со скидкой до 30%!
Выбрать курс

Service Provider Interface (SPI)

VKjavaDeep1.png

Вот представьте: повысили вас до ведущего разработчика! Не номинально, а по-настоящему – будете отвечать за техническое развитие продукта. И вот заходите вы в почту, естественно, чтобы должность в подписи поменять, а там...

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

Как разрубить этот «гордиев узел»?

Разделить монолитное приложение на модули! Модули могут загружаться по требованию и обновляться при необходимости в процессе работы основного приложения.

А как это сделать? Выбор велик. Можно просидеть все выходные, изобретая собственный «велосипед». Можно вооружиться кофе-машиной и погрузиться в изучение вселенной OSGi...

А можно вспомнить, чему учат на курсе OTUS: в состав Java SE входит технология Service Provider Interface (SPI).

О ней и пойдёт речь далее

Технология SPI позволяет разделить сервисы (бизнес-логику) приложения на интерфейс и его реализации. Таким образом, реализация сервиса может распространяться в виде отдельного jar-архива и включаться в работу уже в процессе работы основного приложения.

Эта технология, например, применяется в хорошо известной вам JDBC для загрузки драйвера конкретной СУБД.

На практике это выглядит так:

1) Создаёте общий интерфейс для группы ваших модулей Сигнатуры методов интерфейса могут быть абсолютно любыми. Например:

public interface Plugin { 
        Object execute(Object input); 
}

2) Создаёте необходимое количество классов-реализаций ваших модулей Например:

public class EchoPlugin implements Plugin { 
  public Object execute(Object input) { 
        // Делаем что-то полезное 
        return input; 
  } 
} 

3) Определяете стратегию упаковки классов-реализаций в jar-архивы В один jar-архив может быть упакован один или несколько классов-реализаций. Как правило, один архив содержит реализацию одного логически целостного модуля приложения. Внутри каждого jar-архива в каталоге \META-INF\services должен находится текстовый файл в кодировке UTF-8, название которого совпадает с полным именем интерфейса, созданного в п.1, например, ru.otus.Plugin. В тексте файла должны быть указаны полные имена классов-реализаций, содержащихся в jar-архиве, например:

ru.otus.EchoPlugin 
ru.otus.CopyPlugin 

4) В программном коде основного приложения создаёте ClassLoader для загрузки классов-реализаций из jar-архива

URL[] moduleUrls = new URL[]{new URL("http://example.com/module-1.0.jar")};
URLClassLoader urlClassLoader = new URLClassLoader(moduleUrls);

5) А затем, получаете и используете реализации необходимых вам модулей

for (final Plugin plugin : ServiceLoader.load(Plugin.class, urlClassLoader)) { 
        Object result = plugin.execute(new Object()); 
} 

Всё, пользуйтесь!

Есть вопрос? Напишите в комментариях!

Автор
0 комментариев
Для комментирования необходимо авторизоваться