Публичные сервисы #
Этот раздел описывает публичные сервисы, принцип инверсии управления, а также содержит рекомендации по использованию и добавлению публичных сервисов при разработке плагинов.
Использование Google Guice #
В качестве механизма для внедрения зависимостей при разработке плагинов и сервисов 1C:EDT использует Google Guice. Это позволяет обеспечить слабую связанность.
Google Guice — это универсальный фреймворк с открытым исходным кодом для Java-платформы. Он разработан компанией Google под лицензией Apache 2.0. Фреймворк обеспечивает поддержку внедрения зависимостей при помощи аннотаций для конфигурирования объектов Java.
Внедрение зависимостей — это паттерн проектирования. Его основная задача заключается в том, чтобы отделить поведение объекта от управления его зависимостями. Google Guice позволяет классам реализаций программно привязываться к интерфейсу и затем внедряться в конструкторы, методы или поля, помеченные аннотацией @Inject
.
Почему мы используем Google Guice?
На данный момент существует множество фреймворков, позволяющих реализовать внедрение зависимостей. Преимущества Google Guice заключаются в следующем:
- чистые и простые реализация и использование,
- сравнительная легковесность,
- зависимости и реализации описываются на языке Java, что гарантирует защиту от несоответствия типов и от ошибок инициализации после рефакторинга кода.
Также мы используем в 1C:EDT технологию Eclipse Xtext, которая уже внутри себя использует Google Guice. Таким образом, использование внутри плагинов 1C:EDT единого фреймворка внедрения зависимостей дает более чистое и прозрачное решение. С помощью Google Guice связывается внутренняя инфраструктура плагинов.
Публичные сервисы #
В 1C:EDT присутствует такое понятие, как публичные сервисы. В качестве публичных сервисов используются OSGi-сервисы.
OSGi (Open Services Gateway Initiative) — это спецификация динамической модульной системы и сервисной платформы для Java-приложений, разрабатываемая консорциумом OSGi Alliance. Она дает модель для построения приложения из компонентов, которые связаны посредством сервисов. Преимущество этой модели заключается в возможности динамически переустановить компоненты и составные части приложения без необходимости остановки и запуска самого приложения.
1C:EDT является приложением, построенным на базе Eclipse Platform, поэтому оно представляет собой модульную OSGi-систему. По этой причине мы выбрали OSGi-сервисы в качестве публичных сервисов. Публичные сервисы используются для сервисной связи между плагинами в 1C:EDT.
Общая структура сервисного взаимодействия в плагинах 1C:EDT выглядит следующим образом:
В качестве публичных сервисов в 1C:EDT мы используем OSGi-сервисы, поэтому они могут быть доступны и могут быть получены стандартными средствами или любыми сторонними средствами работы с OSGi-сервисами. Обратите внимание, что при этом использование простых Java-классов или библиотечных классов одного плагина в другом идет напрямую без каких-либо сервисов. Нет нужды делать сервисы из классов или плагинов, представляющих собой библиотеку.
В рамках 1C:EDT мы реализовали удобную инфраструктуру использования публичных OSGi-сервисов, которые можно подключать или регистрировать в любом плагине, в том числе и сторонним разработчикам. Рассмотрим на примерах возможности использования публичных сервисов.
Использование публичных сервисов внутри Google Guice #
Для импорта OSGi-сервисов в Guice-модули в 1C:EDT реализован класс AbstractServiceAwareModule. Далее показаны несколько примеров:
Импорт публичных сервисов
|
|
Реализация внутреннего сервиса
С помощью таких модулей возможно простое внедрение (@Inject
) публичных OSGi-сервисов в нужные места, включая внутренние сервисы плагина в этом модуле:
|
|
Импорт публичных сервисов
У класса
AbstractServiceAwareModule имеется конструктор с параметром org.osgi.framework.BundleContext
. Его можно использовать в тестах, где экземпляр плагина может быть недоступен:
|
|
Пример плагина #
Напишите плагин, который будет дополнять 1C:EDT редактором. Этот редактор сможет открывать файлы с расширением *.example
.
Класс редактора
Создайте проект плагина, после чего создайте в нем редактор. Вы будете использовать в нем сервис общего назначения
IConfigurationProvider. Этот сервис даст доступ к конфигурации проекта, который будет содержать файлы с расширением *.example
. Для этого в классе редактора используйте аннотацию @Inject
с именем сервиса, который вы хотите внедрить при помощи Google Guice:
|
|
Guice-модуль плагина
Чтобы ваш класс редактора ExampleEditor
создавался в нужном Guice-окружении, вы должны сделать следующее:
- Создать собственный Guice-модуль, в котором будет описано, какие сервисы следует использовать.
- Сделать так, чтобы редактор создавался в окружении этого модуля.
Создайте Guice-модуль и подключите в нем сервис общего назначения IConfigurationProvider (список сервисов общего назначения можно найти в соответствующем документе):
|
|
В этом примере вы унаследовали свой модуль от специального класса-модуля AbstractServiceAwareModule. Он знает обо всех сервисах общего назначения 1C:EDT и может легко подключать необходимые из них (о том, как подключать свои сервисы, будет рассказано ниже).
В методе doConfigure()
вы описали, что хотите использовать сервис общего назначения
IConfigurationProvider.
Активатор плагина
Модуль готов: теперь вы должны зарегистрировать свой редактор. Для этого платформа Eclipse предоставляет механизм IExecutableExtensionFactory
, а в 1C:EDT реализован абстрактный класс
AbstractGuiceAwareExecutableExtensionFactory. Этому классу нужно просто указать, какой модуль использовать при создании и инициализации расширений.
Для начала определитесь с местоположением инжектора вашего Guice-модуля (инжектор Guice-модуля — это сущность, которая позволяет использовать описанные в модуле зависимости. Подробнее — здесь). Мы рекомендуем использовать для этого сам класс плагина (активатора):
|
|
Фабрика для создания расширений
Далее определите свою реализацию IExecutableExtensionFactory
:
|
|
Фрагмент plugin.xml
Теперь укажите в описании своего расширения с редактором, что вы хотите создавать его, используя свою фабрику MyPluginExecutableExtensionFactory
:
<extension point="org.eclipse.ui.editors">
<editor
class="com.myplugin.example.MyPluginExecutableExtensionFactory:com.myplugin.example.editor.ExampleEditor"
default="false"
extensions="example"
id="com.myplugin.example.editor"
name="My Editor">
</editor>
</extension>
Фрагмент активатора плагина
Теперь ваш редактор будет создаваться в нужном вам Guice-окружении согласно описанию вашего модуля. В него вы можете подключать необходимые вам сервисы общего назначения, а также описывать свои сервисы. Однако мы рекомендуем отделять модули-потребители внешних сервисов от модулей, описывающих свои внутренние сервисы плагина, а при создании инжектора в плагине — объединять их:
|
|
Использование публичных сервисов вне Guice-окружения #
При создании плагинов вам может потребоваться работа с публичными OSGi-сервисами вне Guice-окружения. Мы рекомендуем следующий способ работы с публичным OSGi-сервисами:
Получение сервиса мы рекомендуем включать во внутренний метод. Остальной код класса будет взаимодействовать с методом-аксессором сервиса. Такой вариант даст возможность в дальнейшем гибко настраивать получение сервиса, если это потребуется.
Мы рекомендуем писать код так, чтобы клиент не хранил ссылку на сервис: тогда код останется актуальным в различных ситуациях доступности и изменения сервисов. Другими словами, клиент должен всегда вызывать get
сервиса перед вызовом методов сервиса.
Далее приведен пример следования этим рекомендациям:
|
|
Кроме показанного выше способа в 1C:EDT реализован и другой, нерекомендуемый способ работы. Он может использоваться либо как временное решение в коде, которое будет в дальнейшем переработано, либо там, где некритична производительность, например в тестах. Обратите внимание, что такой способ доступа медленнее.
|
|
Необходимость статического доступа зачастую является следствием плохого дизайна классов и сигналом к пересмотру собственного кода. По возможности старайтесь избегать статического доступа к сервисам и используйте подход с внедрением зависимостей, описанный выше.
Предоставление публичных сервисов #
В 1C:EDT реализована точка расширения com._1c.g5.wiring.serviceProvider
. Плагины, предоставляющие публичные сервисы, могут зарегистрировать расширение, после чего быть активированы при старте приложения специальным служебным плагином, который будет стартовать автоматически:
Фрагмент plugin.xml
|
|
Для того чтобы предоставить публичные сервисы, можно воспользоваться классом ServiceRegistrator
. Далее приведен пример активатора плагина, предоставляющего публичные сервисы:
|
|
Инициализацию и регистрацию сервисов плагина мы рекомендуем выполнять в отдельном потоке, т. к. платформа OSGi чувствительна к времени запуска плагинов. Время запуска должно быть коротким. Для удобства реализован служебный классServiceInitialization
с методомServiceInitialization#schedule(Runnable)
.
Если требуется, чтобы в качестве выставляемых реализаций сервисов были доступны сервисы из IoC-контейнера на основе Google Guice, вы можете воспользоваться реализацией-расширением InjectorAwareServiceRegistrator:
|
|
Если есть потребность выставлять публичные сервисы с жизненным циклом, отличным от жизненного цикла плагина, это можно сделать аналогичным образом и вне активатора:
|
|
Разработка сервисов, требующих активации и деактивации #
Иногда имеется потребность в сервисах, которые инициализируются вместе со стартом плагина и деинициализируются с остановкой плагина. В 1C:EDT существует интерфейс IManagedService, и есть возможность встраивать сервисы, реализующие данный интерфейс, в жизненный цикл плагина c помощью ServiceRegistrator или InjectorAwareServiceRegistrator. Далее приведен пример активатора плагина с публичными сервисами, имеющими жизненный цикл:
|
|
Инициализацию управляемых сервисов мы рекомендуем выносить в отдельный поток, если в процессе инициализации предполагается, например, чтение Extension Registry. Причина заключается в следующем: в расширениях, которые будут читаться при инициализации этого сервиса, могут быть попытки доступа к другим публичным сервисам, которые регистрируются в этом плагине. Это возможно потому, что, строго говоря, расширения могут определяться и разработчиками расширений 1C:EDT, которые могут использовать там все, что захотят. Поэтому в процессе инициализации могут возникать взаимные блокировки из-за очередности регистрации / активации управляемых сервисов. Если при активации управляемых сервисов они не читают Extension Registry, то выносить их в отдельный поток не обязательно.
Общие рекомендации #
- Отделяйте плагин с интерфейсами публичных сервисов и внешним API от плагина с их реализацией и регистрацией.
- Не делайте сервисы из классов или плагинов, представляющих собой библиотеку.
- Публичные сервисы на данный момент реализованы в единственном объеме (scope) — как синглтоны. Поэтому внедрение их через Google Guice с расчетом получать каждый раз новый экземпляр сервиса невозможно. В случае такой потребности мы рекомендуем реализовать фабрику и зарегистрировать ее в качестве публичного сервиса. Эта фабрика уже сама будет каждый раз возвращать новый экземпляр необходимого сервиса.