0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Теги: java, java se, service provider interface (spi), jar-архив, \meta-inf\services, интерфейс для группы модулей, стратегия упаковки классов-реализаций в jar-архивы, classloader, загрузка классов-реализаций из jar-архива</p>
1
<p>Теги: java, java se, service provider interface (spi), jar-архив, \meta-inf\services, интерфейс для группы модулей, стратегия упаковки классов-реализаций в jar-архивы, classloader, загрузка классов-реализаций из jar-архива</p>
2
<p>Вот представьте: повысили вас до ведущего разработчика! Не номинально, а по-настоящему - будете отвечать за техническое развитие продукта. И вот заходите вы в почту, естественно, чтобы должность в подписи поменять, а там...</p>
2
<p>Вот представьте: повысили вас до ведущего разработчика! Не номинально, а по-настоящему - будете отвечать за техническое развитие продукта. И вот заходите вы в почту, естественно, чтобы должность в подписи поменять, а там...</p>
3
<p>Вместе с полномочиями пришла и ответственность: - пользователи не хотят больше устанавливать дистрибутив приложения целиком, они хотят устанавливать только то, что планируют использовать; - техническая поддержка просит реализовать наконец возможность автоматического обновления приложения; - разработчики тоже ругают долгий цикл доставки обновлений пользователям; - отдел продаж требует технически поддержать новую модель гибкого ценообразования - теперь пользователь покупает только нужный ему функционал; - партнёры хотят создавать собственные расширения к продукту; - юристы перестраховываются и требуют убрать из состава дистрибутива коммерческого продукта ту часть функционала, которая использует некоторые open source библиотеки, чтобы избежать необходимости публикации исходников...</p>
3
<p>Вместе с полномочиями пришла и ответственность: - пользователи не хотят больше устанавливать дистрибутив приложения целиком, они хотят устанавливать только то, что планируют использовать; - техническая поддержка просит реализовать наконец возможность автоматического обновления приложения; - разработчики тоже ругают долгий цикл доставки обновлений пользователям; - отдел продаж требует технически поддержать новую модель гибкого ценообразования - теперь пользователь покупает только нужный ему функционал; - партнёры хотят создавать собственные расширения к продукту; - юристы перестраховываются и требуют убрать из состава дистрибутива коммерческого продукта ту часть функционала, которая использует некоторые open source библиотеки, чтобы избежать необходимости публикации исходников...</p>
4
<h2>Как разрубить этот "гордиев узел"?</h2>
4
<h2>Как разрубить этот "гордиев узел"?</h2>
5
<p>Разделить монолитное приложение на модули! Модули могут загружаться по требованию и обновляться при необходимости в процессе работы основного приложения.</p>
5
<p>Разделить монолитное приложение на модули! Модули могут загружаться по требованию и обновляться при необходимости в процессе работы основного приложения.</p>
6
<p><strong>А как это сделать?</strong>Выбор велик. Можно просидеть все выходные, изобретая собственный "велосипед". Можно вооружиться кофе-машиной и погрузиться в изучение вселенной<strong>OSGi</strong>...</p>
6
<p><strong>А как это сделать?</strong>Выбор велик. Можно просидеть все выходные, изобретая собственный "велосипед". Можно вооружиться кофе-машиной и погрузиться в изучение вселенной<strong>OSGi</strong>...</p>
7
<p>А можно вспомнить, чему учат на<a>курсе OTUS</a>: в состав<strong>Java SE</strong>входит технология<strong>Service Provider Interface (SPI)</strong>.</p>
7
<p>А можно вспомнить, чему учат на<a>курсе OTUS</a>: в состав<strong>Java SE</strong>входит технология<strong>Service Provider Interface (SPI)</strong>.</p>
8
<h2>О ней и пойдёт речь далее</h2>
8
<h2>О ней и пойдёт речь далее</h2>
9
<p>Технология SPI позволяет разделить сервисы (бизнес-логику) приложения на интерфейс и его реализации. Таким образом, реализация сервиса может распространяться в виде отдельного jar-архива и включаться в работу уже в процессе работы основного приложения.</p>
9
<p>Технология SPI позволяет разделить сервисы (бизнес-логику) приложения на интерфейс и его реализации. Таким образом, реализация сервиса может распространяться в виде отдельного jar-архива и включаться в работу уже в процессе работы основного приложения.</p>
10
<p>Эта технология, например, применяется в хорошо известной вам JDBC для загрузки драйвера конкретной СУБД.</p>
10
<p>Эта технология, например, применяется в хорошо известной вам JDBC для загрузки драйвера конкретной СУБД.</p>
11
<h2>На практике это выглядит так:</h2>
11
<h2>На практике это выглядит так:</h2>
12
<p><strong>1) Создаёте общий интерфейс для группы ваших модулей</strong>Сигнатуры методов интерфейса могут быть абсолютно любыми. Например:</p>
12
<p><strong>1) Создаёте общий интерфейс для группы ваших модулей</strong>Сигнатуры методов интерфейса могут быть абсолютно любыми. Например:</p>
13
public interface Plugin { Object execute(Object input); }<p><strong>2) Создаёте необходимое количество классов-реализаций ваших модулей</strong>Например:</p>
13
public interface Plugin { Object execute(Object input); }<p><strong>2) Создаёте необходимое количество классов-реализаций ваших модулей</strong>Например:</p>
14
public class EchoPlugin implements Plugin { public Object execute(Object input) { // Делаем что-то полезное return input; } }<p><strong>3) Определяете стратегию упаковки классов-реализаций в jar-архивы</strong>В один jar-архив может быть упакован один или несколько классов-реализаций. Как правило, один архив содержит реализацию одного логически целостного модуля приложения. Внутри каждого jar-архива в каталоге<strong>\META-INF\services</strong>должен находится текстовый файл в кодировке UTF-8, название которого совпадает с полным именем интерфейса, созданного в п.1, например,<strong>ru.otus.Plugin</strong>. В тексте файла должны быть указаны полные имена классов-реализаций, содержащихся в jar-архиве, например:</p>
14
public class EchoPlugin implements Plugin { public Object execute(Object input) { // Делаем что-то полезное return input; } }<p><strong>3) Определяете стратегию упаковки классов-реализаций в jar-архивы</strong>В один jar-архив может быть упакован один или несколько классов-реализаций. Как правило, один архив содержит реализацию одного логически целостного модуля приложения. Внутри каждого jar-архива в каталоге<strong>\META-INF\services</strong>должен находится текстовый файл в кодировке UTF-8, название которого совпадает с полным именем интерфейса, созданного в п.1, например,<strong>ru.otus.Plugin</strong>. В тексте файла должны быть указаны полные имена классов-реализаций, содержащихся в jar-архиве, например:</p>
15
ru.otus.EchoPlugin ru.otus.CopyPlugin<p><strong>4) В программном коде основного приложения создаёте ClassLoader для загрузки классов-реализаций из jar-архива</strong></p>
15
ru.otus.EchoPlugin ru.otus.CopyPlugin<p><strong>4) В программном коде основного приложения создаёте ClassLoader для загрузки классов-реализаций из jar-архива</strong></p>
16
URL[] moduleUrls = new URL[]{new URL("http://example.com/module-1.0.jar")}; URLClassLoader urlClassLoader = new URLClassLoader(moduleUrls);<p><strong>5) А затем, получаете и используете реализации необходимых вам модулей</strong></p>
16
URL[] moduleUrls = new URL[]{new URL("http://example.com/module-1.0.jar")}; URLClassLoader urlClassLoader = new URLClassLoader(moduleUrls);<p><strong>5) А затем, получаете и используете реализации необходимых вам модулей</strong></p>
17
for (final Plugin plugin : ServiceLoader.load(Plugin.class, urlClassLoader)) { Object result = plugin.execute(new Object()); }<h2>Всё, пользуйтесь!</h2>
17
for (final Plugin plugin : ServiceLoader.load(Plugin.class, urlClassLoader)) { Object result = plugin.execute(new Object()); }<h2>Всё, пользуйтесь!</h2>
18
<p><em>Есть вопрос? Напишите в комментариях!</em></p>
18
<p><em>Есть вопрос? Напишите в комментариях!</em></p>
19
19