С тех пор, как в Eclipse была принята среда выполнения OSGi в версии 3.0, возникла некоторая напряженность между Реестром расширений, который с самого начала был особенностью Eclipse, и уровнем обслуживания, который происходил из OSGi и существовал при участии Eclipse. Причина напряженности в том, что эти две модели несколько перекрываются, и потому что они обе предназначены для решения очень похожих проблем. Тем не менее, «дьявол кроется в деталях», и эти две модели достаточно разные, чтобы сделать их объединение непрактичным. Поэтому разработчикам плагинов Eclipse и приложений RCP необходимо сделать выбор между ними.
До недавнего времени выбор для разработчиков Eclipse был предельно ясен: реестр расширений по-прежнему используется практически во всех плагинах и приложениях RCP, за исключением OSGi Services. Это подкрепляется инструментарием, доступным в Eclipse PDE, который обеспечивает большую поддержку расширений и ни одного для сервисов.
Однако недавние разработки в OSGi еще больше размыли границы между двумя моделями, и инструменты тоже развиваются. Это приводит к неизбежному вопросу: «какой из них лучше»?
Структура статьи
Эта статья предназначена в первую очередь для разработчиков Eclipse, которые достаточно знакомы с «Eclipse Way» разработки плагинов с точками расширения и расширениями, но не имеют большого опыта работы со службами OSGi. Таким образом, не так много дискуссий о том, как работают расширения или почему они существуют. Следующий раздел является лишь кратким обзором.
После этого мы немного подробнее обсудим службы OSGi, включая пример кода. Мы говорим о проблемах, с которыми разработчики Eclipse могут столкнуться при работе с сервисами. Мы также обсудим некоторые из новейших возможностей OSGi, которые могут помочь преодолеть эти проблемы.
Наконец, мы завершим сравнительной сеткой и заключением с некоторыми рекомендациями и дальнейшим чтением.
Реестр расширений Eclipse
Реестр расширений — это место, где точки расширения сопоставляются с расширениями . Точка расширения — это объявление, сделанное плагином, чтобы сказать, что оно открыто для расширения новыми функциями определенным образом. Он находится в plugin.xml
файле для плагина и состоит из:
-
Идентификатор точки расширения, который состоит из «пространства имен» и суффикса. Пространство имен обычно является идентификатором плагина, в котором определена точка расширения, но это не обязательно. Это отличается от «пространства имен XML».
-
Человекочитаемое имя.
-
Документ схемы XML, который определяет структуру метаданных, которые должны предоставлять расширения.
Расширением является оборотная сторона монеты. Это объявление подключаемого модуля о том, что оно обеспечивает функциональность для расширения другого подключаемого модуля. Он также находится в plugin.xml
и указывает:
-
Идентификатор точки расширения.
-
Узел XML, который должен соответствовать схеме, указанной точкой расширения.
После того как плагин установлен и входит в RESOLVED
состояние, Eclipse запрашивает его о внесении изменений в реестр расширений. Плагин может добавлять либо расширения, либо точки расширения, либо и то и другое. Результат сканирования кэшируется между запусками Eclipse, поэтому единственный раз, когда сканируются все плагины, это когда Eclipse запускается в первый раз или когда вы запускаете его с ключом командной строки -clean.
На данный момент Eclipse проделал очень мало работы. Вы можете думать о реестре как о паре HashMap
s, которые позволяют Eclipse искать расширения или точки расширения по их идентификатору. До сих пор мы просто бросили несколько пунктов в HashMap
с.
Через некоторое время плагин может решить запросить реестр. Обычно это делается определителем точки расширения. По сути, этот плагин спрашивает «кто объявил, что у меня есть расширения для меня?» API реестра предоставляет подключаемому модулю моментальный снимок всех установленных в настоящее время расширений для запрашиваемого идентификатора в виде массива структур данных, которые очень похожи на фактические узлы XML, заданные расширением. И это (почти) целая история для реестра расширений: он позволяет плагинам искать метаданные, объявленные плагинами. Ключевым моментом, на который следует обратить внимание, является то, что классы Java в плагине расширения еще не загружены. Мы просто загрузили некоторые данные XML; это занимает гораздо меньше времени и памяти, чем создание ClassLoader и использование его для загрузки кода.
Конечно, код Java должен быть загружен в какой-то момент. Когда это происходит? Это зависит от определения точки расширения. До сих пор он запрашивал реестр расширений и получал метаданные, но эти метаданные часто содержат имя класса Java. Чтобы использовать этот класс, плагин просит реестр расширений загрузить и создать экземпляр класса.
Таким образом, определение точки расширения зависит от того, будет ли загружен код в расширениях. Вполне возможно сразу загрузить все классы; однако это обычно не является необходимым. Обычно метаданных достаточно для создания какого-либо заполнителя, и тогда фактический класс загружается только тогда, когда пользователь проявляет к нему некоторый интерес.
Например, org.eclipse.ui.actionSets
точку расширения можно использовать для добавления кнопок на панель инструментов приложения. Нажатие на кнопку выполняет какое-то задание, которое реализовано в виде кода Java. Однако пока кнопка не нажата, она состоит только из значка и метки. Поэтому метаданные для точки расширения включают в себя текст для метки и путь к ресурсу иконки. Код не загружается до тех пор, пока пользователь не проявит интерес к кнопке, то есть, фактически нажав на нее.
На рисунке 1 показана диаграмма последовательности в стиле UML того, как определители точек расширения и поставщики расширений взаимодействуют с реестром расширений.
[img_assist | nid = 252 | title = | desc = | link = none | align = none | width = 620 | height = 640]
Рис. 1. Расширения. Нажмите на изображение, чтобы открыть его в полном размере
Еще один интересный аспект расширений заключается в том, что они вообще не должны приводить к загрузке какого-либо кода. Это полностью зависит от точки расширения. В основных подключаемых модулях RCP есть много точек расширения, которым не требуется class
атрибут. Например, вся справочная документация в Eclipse добавляется через расширения, и в нее не входит Java-код — только файлы HTML.
Точки, которые можно отнять, это:
-
расширения являются объявлениями
-
плагины обращаются к расширениям для создания заполнителей для функциональности
-
функциональные возможности заменителя реализуются, когда выбирается определитель точки расширения, что обеспечивает ленивую загрузку и меньшее использование памяти.
-
расширения не должны быть связаны с исполняемым кодом Java
OSGi Services
Сервис OSGi, напротив, является Java-объектом (или, если хотите, POJO). Он зарегистрирован в Реестре услуг под именем интерфейса Java.
В отличие от расширений, которые сканируются и регистрируются во время запуска, службы не регистрируются автоматически. Чтобы зарегистрировать сервис, пакет должен сначала создать объект, а затем он должен вызвать API OSGi, чтобы зарегистрировать объект как сервис. Например, предположим, что у нас есть служба, которая определяется интерфейсом, org.example.ICustomerLookup
и класс, реализующий его, который называется DbCustomerLookup
. Мы могли бы зарегистрировать это следующим образом:
public void start(BundleContext context) {
// Create the service object
DbCustomerLookup lookup =
new DbCustomerLookup("jdbc:mysql:localhost/customers");
// Create the properties to register with the service
Dictionary properties = new Hashtable();
properties.put("dbname", "local");
// Register the service
context.registerService(ICustomerLookup.class.getName(), lookup,
properties);
}
Существует несколько способов поиска пакета и использования сервиса из другого пакета. Это немного сложнее, потому что сервисный уровень в OSGi неявно динамичен — мы должны быть готовы к тому, что сервисы могут приходить и уходить в любое время. Поэтому знакомый метод поиска зависимости и ее кэширования не будет работать в OSGi. Одним из решений является поиск сервиса каждый раз, когда нам нужен доступ к нему, но, как показывает следующий код, это может быстро стать громоздким:
public void start(BundleContext context) {
this.context = context;
}
public String getCustomerName(long id) {
ServiceReference ref = context.getServiceReference(
ICustomerLookup.class.getName());
if(ref != null) {
ICustomerLookup lookup = (ICustomerLookup)
context.getService(ref);
if(lookup != null) {
Customer cust = lookup.findById(id);
context.ungetService(ref);
return cust.getName();
}
}
// Couldn't get name -- service not available
return null;
}
На рисунке 2 показана схема последовательности этапов регистрации и использования службы.
[img_assist | nid = 253 | title = | desc = | link = none | align = none | width = 640 | height = 621]
Рисунок 2: Сервисы. Нажмите на изображение, чтобы открыть его в полном размере
Поскольку делать это так неловко, есть служебный класс, который можно использовать для упрощения работы. Это называется ServiceTracker
и может использоваться следующим образом:
public void start(BundleContext context) {
this.custLookupTracker = new ServiceTracker(context,
org.example.ICustomerLookup.class.getName(), null);
this.custLookupTracker.open();
}
public String getCustomerName(long id) {
ICustomerLookup lookup = (ICustomerLookup)
this.custLookupTracker.getService();
if(lookup == null) {
return null; // Alternatively might throw an exception
} else {
Customer cust = lookup.findById(id);
return cust.getName();
}
}
ServiceTracker
также упрощает другие виды взаимодействия со службами, например отслеживание того, когда служба зарегистрирована и не зарегистрирована. Выполнение этого с API нижнего уровня, такими как, ServiceListener
может быть весьма подвержено ошибкам, поэтому рекомендуется всегда использовать ServiceTracker
для использования сервисов.
Обратите внимание, что как при регистрации служб, так и при доступе к службам взаимодействие с OSGi осуществляется через экземпляр BundleContext
интерфейса. Фактически, BundleContext
интерфейс является единственным способом взаимодействия пакетов со средствами среды выполнения OSGi. Кроме того, единственный способ получить это BundleContext
— дождаться, пока среда выполнения OSGi выдаст его нам — мы делаем это, предоставляя BundleActivator
для нашего пакета. Когда пакет запускается, среда выполнения вызывает start()
метод our BundleActivator
и предоставляет BundleContext
экземпляр. Прежде чем наш пакет может быть запущен, он должен быть загружен. Поэтому, пока пакет не загружен, не может быть никаких зарегистрированных служб. Предположим, что весь функционал нашего приложения реализован в виде сервисов OSGi. Какие связки мы запускаем?
Есть три варианта. Во-первых, мы не могли начать ни один из них. Это явно не работает. Во-вторых, мы могли бы начать все из них. Это работает, но это приведет к медленному запуску, и наше приложение будет использовать больше памяти, чем нужно для выполнения конкретной работы. Третий вариант — заранее знать, какие пакеты нужно запустить, и только запускать их. Эту опцию очень сложно реализовать, когда службы регистрируются программным кодом, как мы только что описали. Проблема заключается в том, что пакет имеет полную свободу регистрировать службу всякий раз, когда он выбирает, или же он может вообще не регистрировать службу из-за определенных предварительных условий. Например, предположим, что у нас есть служба A и служба B. Для службы B очень распространена зависимость от службы A, то есть служба B использует средства A для выполнения своей собственной работы.Для поддержки этого сценария пакет, который регистрирует службу B, может установить прослушиватель и гарантировать, что служба B регистрируется только тогда, когда услуга A становится доступной. Это всего лишь один сценарий — есть еще много возможностей. Пакет может даже решить зарегистрировать сервис только при запуске по пятницам с 13:00 до 14:00. Поскольку управление находится в руках произвольной программной логики, возможности бесконечны и непредсказуемы.
Таким образом, поскольку пакеты обладают такой гибкостью при регистрации сервисов, действительно возможно создать приложение из сервисов только тогда, когда предварительные условия всех сервисов тщательно задокументированы. Это явно нецелесообразно для таких приложений, как Eclipse IDE, где базовое приложение расширяется за счет подключаемых модулей сторонних производителей. Фактически, это может быстро стать непрактичным для больших приложений, даже созданных одной командой. Но Декларативные Услуги могут помочь (см. Следующий раздел).
Так есть ли у услуг какие-либо преимущества перед расширениями? На мой взгляд, да, у них есть одно существенное преимущество: они гораздо лучше справляются с динамической установкой и удалением, чем с расширениями. Это не означает, что расширения не обрабатывают динамическое поведение. Да, но некоторые люди считают, что API сложен и труден для работы, и при работе с ним создается впечатление, что динамические аспекты были закреплены поверх существующего API, а не были частью API с самого начала. На самом деле это именно то, что произошло. До Eclipse 3.0 API расширений не был динамическим, и с тех пор ядро API не изменилось. В отличие от этого API для работы со службами по своей сути динамичен. Это иногда неудобно — каждый вынужден думать о динамических аспектах, даже когда он не хочет!- но на самом деле это хорошая дисциплина, поскольку реальный мир по сути своей динамичен.
В некоторых случаях преимущества услуг перевешивают проблемы, связанные с активацией. Взять к примеру серверы. В идеальном мире серверы почти никогда не должны отключаться, но продолжают работать, пока не изнашивается оборудование, на котором они работают. В этом контексте мы могли бы также запустить все пакеты, когда сервер загрузится. Запуск может занять больше времени, но что нас волнует несколько дополнительных минут во время запуска, если сервер будет работать месяцами или годами? На самом деле разработчики заботятся, потому что им нужно быстро развернуть и протестировать новые версии своего кода на серверах разработки. Однако и здесь динамические качества сервисов могут помочь, потому что новые версии могут быть развернуты на работающем сервере без необходимости перезапуска.
Другое ключевое отличие заключается в том, как сервисы и расширения взаимодействуют со своими потребителями. Модель расширения по сути «один ко многим», что означает, что расширение явно предназначено для потребления одним конкретным плагином; т.е. определитель точки расширения. С другой стороны, сервисы используют модель «многие ко многим», что означает, что один сервис может иметь много потребителей. Сервисы зарегистрированы так, что любой может их найти и использовать. Например, представьте себе сценарий публикации сообщений / подписки. Каждый издатель может быть сопоставлен с несколькими подписчиками, и каждый подписчик может получать сообщения от нескольких издателей.
Поэтому мы действительно хотим объединить преимущества как расширений, так и сервисов. То, что неявно динамично, как сервисы, но загружается «по требованию», как расширения. В идеале, это то, что также может упростить код, который должны писать разработчики приложений.
Первые шаги к такому решению были сделаны Умберто Сервантесом и Ричардом С. Холлом в их библиотеке Service Binder. Это попыталось привнести декларативный оттенок в сервисы OSGi, а также автоматизировать соединение зависимостей между сервисами. Service Binder вместе с некоторой информацией от разработчиков Eclipse (которые к этому моменту стали активно участвовать в OSGi) превратились в платформу под названием декларативные сервисы (DS), разработанную в рамках OSGi Release 4. Реализация декларативных сервисов может быть найден в проекте Equinox. Equinox является реализацией OSGi Release 4 и является ядром Eclipse с версии 3.0.
Декларативные услуги
Ключевое наблюдение, которое привело к связыванию услуг и декларативному обслуживанию, заключалось в том, что на самом деле существуют две отдельные обязанности, связанные с предложением услуги. Во-первых, кто-то должен создать реализацию сервиса. Во-вторых, кто-то должен опубликовать сервис в реестре сервисов. В традиционной модели сервисов, которую мы описали выше, эти два элемента обычно были одинаковыми — т.е. пакет, который реализует сервис, также регистрирует его. Однако если мы разделим две обязанности, то сможем достичь чего-то действительно мощного.
Это то, что делает декларативный сервис. Ответственность за реализацию услуг остается за пакетами, как и раньше; однако ответственность за регистрацию сервисов возлагается на специальный пакет, называемый Service Component Runtime или SCR. Таким образом, вместо каждого пакета, нуждающегося в коде поставки для регистрации своих сервисов, он просто позволяет SCR регистрировать эти сервисы от своего имени.
Естественно, SCR нужен какой-то способ узнать об услугах, которые он должен регистрировать. Как следует из названия «Декларативные услуги», эта информация предоставляется декларативно. Фактически, мы прошли полный круг назад к XML-файлам. Пакет, содержащий службы, которыми он хочет управлять SCR от своего имени, содержит один или несколько файлов XML, каждый из которых представляет «компонент службы». SCR сканирует пакеты для этих файлов XML и регистрирует службы, описанные в них. В отличие от расширений (если в каждом пакете имеется один XML-файл, и он всегда вызывается plugin.xml
), в каждом пакете может быть произвольное количество файлов XML с произвольными именами. Поэтому файлы должны быть перечислены в манифесте пакета под новым, Service-Component
чтобы SCR мог их найти.
Теперь вышеприведенный сценарий все еще не «по требованию». Для поддержки отложенной регистрации в декларативном сервисе есть понятие «отложенного» обслуживания. Когда SCR регистрирует отложенную службу, он создает прокси-объект, который будет действовать как заполнитель, и регистрирует его в реестре службы. С точки зрения потребителя, сервис теперь доступен для использования, однако пакет, содержащий реальную реализацию сервиса, еще не запущен. Затем, когда потребитель пытается фактически использовать службу, SCR обнаруживает это и просит среду выполнения OSGi полностью загрузить и активировать пакет. Затем он заменяет прокси для реального сервиса. Ключевое различие между расширениями и декларативными службами заключается в том, что для потребителя DS прозрачно реализует лень, т.е.код, который использует сервис, не должен знать или заботиться о том, что сервис временно представлен только как заполнитель. Напротив, потребитель расширения должен явно создавать и управлять заполнителем, а также выбирать, когда менять местами заполнитель для реальной реализации.
На рисунке 3 показана диаграмма последовательности для декларативных услуг. Обратите внимание, что все шаги из диаграммы сервисов vanilla OSGi все еще присутствуют. DS уточняет модель услуг, а не заменяет ее.
[img_assist | nid = 254 | title = | desc = | link = none | align = none | width = 640 | height = 518]
Рисунок 3: Декларативные услуги. Нажмите на изображение, чтобы открыть его в полном размере
Другая проблема, которую мы определили со службами, а именно то, что программный код обладает слишком большой гибкостью в отношении того, регистрирует ли службы и когда это происходит, также решается с помощью декларативных служб. Логика, управляющая регистрацией сервисов, определяется через декларации, которые легко доступны и обоснованы. Это значительно облегчает администраторам задачу, скажем, выяснить, почему не работает конкретный сервис, а затем исправляет ситуацию.
Составление услуг
Одна очень интересная и мощная функция декларативных сервисов — это простота создания сервисов.
Как упоминалось ранее, довольно часто можно найти две службы, A и B, где служба B зависит от службы A. То есть, когда вызывается метод службы B, он может сделать один или несколько вызовов службе A для достижения своей цели. конечный результат. Далее предположим, что сервис C зависит от сервиса B: мы начали создавать граф зависимых сервисов. Декларативные услуги упрощают построение этого графа. В файле XML для сервиса B мы просто создаем a reference
для сервиса A — тогда SCR будет связывать зависимость всякий раз, когда она может быть удовлетворена.
Обратите внимание, что, поскольку сервисы динамичны, возникают некоторые интересные вопросы. Например, что произойдет, если служба A уйдет — что нам делать с услугой B? Должны ли мы убить его, или он все равно продолжит работать? Ответ на это зависит от того, является ли зависимость необязательной или обязательной. Затем, что происходит, когда есть двадцать экземпляров A? Б хочет их всех или только одного? Если только один, какой мы выбираем? Наконец, предположим, что B связан с конкретным экземпляром A, и этот экземпляр исчезает, но уже имеется подходящая подсистема (например, один из оставшихся 19 экземпляров). Должны ли мы переключить B, чтобы использовать новый A, или нам нужно убить B, а затем создать новый B, указывающий на новый A?
Все это правильные вопросы, и ответ в каждом случае заключается в том, что это зависит от наших требований. Поэтому DS обеспечивает контроль в трех измерениях:
- Необязательно против обязательного.
- Унарный против множественного.
- Статический против динамического.
Комбинирование первых двух осей дает семье четыре варианта 0..1
(необязательно + унарный), 1..1
(обязательный + унарный), 1..n
(обязательный + множественный) и 0..n
(необязательный + множественный). Третья ось менее знакома. Зависимость является динамической, если ее можно заменить в «горячем» режиме, т. Е. Если службе B могут быть предоставлены отдельные экземпляры A. Иногда это невозможно; например, A может быть с состоянием или B может просто не справиться с заменой из-за того, как это было запрограммировано. Мы говорим, что зависимость статическая — мы должны уничтожить B и создать новый экземпляр.
Все вышеперечисленные действия, естественно, могут вызвать последующие эффекты на большом графике. Если служба C имеет статическую обязательную зависимость от B, а служба B имеет статическую обязательную зависимость от A, то каждый раз, когда экземпляр A удаляется, мы обнаруживаем, что и B, и C должны быть остановлены и перезапущены. Когда график большой, мы обнаруживаем, что один перезапуск службы может вызвать лавину перезапусков по всей системе. Однако лавина останавливается, когда она попадает в необязательную или динамическую зависимость. Таким образом, для наиболее масштабируемого решения рекомендуется сделать ваши зависимости необязательными и динамическими, когда это возможно.
Обратите внимание, что все вышеперечисленное может быть достигнуто с помощью сервисов OSGi. В конце концов, DS построен на сервисах. Но код, необходимый для обработки всех сценариев и их основных случаев, сложен и подвержен ошибкам. Напротив, DS позволяет вам декларативно указывать, каким должно быть поведение, и тщательно проверяется для угловых случаев.
Делать выбор
Может показаться, что декларативные службы могут делать все, что могут делать расширения, и, кроме того, лучше справляются с динамикой. Также, поскольку DS является частью спецификации OSGi, тогда как расширения являются внешним дополнением (хотя и частью каждой загрузки Eclipse), должно быть естественное желание использовать DS.
К сожалению, это не совсем так. Функция «отложенных» сервисов требует некоторых небольших настроек базовой среды выполнения Equinox, и эти настройки еще не были реализованы в выпущенной версии 3.2. Они еще не появляются (на момент написания) в других средах разработки OSGi с открытым исходным кодом, Apache Felix и Knopflerfish. Они появляются в последних выпусках Equinox 3.3, но вам может показаться рискованным использование этого выпуска в производственном приложении.
Кроме того, в настоящее время в среде разработки плагинов Eclipse (PDE) отсутствуют инструменты для декларативных услуг. Для сборки пакетов с помощью DS требуется поддерживать файлы XML в соответствии со специальной схемой, хранить список этих файлов XML в манифесте пакета и обеспечивать его синхронизацию, а также обновлять файлы XML с учетом изменений в коде Java в качестве имен классов и методов. развиваться. В отличие от этого, PDE обеспечивает большую поддержку графического редактирования расширений и точек расширения, поддержку рефакторинга и так далее. Поэтому современные инструменты в Eclipse настоятельно рекомендуют использовать расширения, а не DS.
Наконец, DS — не единственное решение проблем, которые мы описали с сервисами. Apache Felix предлагает две альтернативы: менеджер зависимостей Felix и библиотека iPOJO. Однако я считаю, что самая жесткая конкуренция будет исходить от Spring Framework.
В 2006 году Spring продолжил распространять свое влияние практически на все уголки мира Java. Род Джонсон (отец весны) объявил, что 2006 год стал годом, когда весна стала повсеместной. Я думаю, что Род преувеличивал, но незначительно: по всей вероятности, 2007 год будет годом.
Какое отношение Spring имеет к Eclipse и OSGi? Ну, а когда Spring 2.0 готовился к финальному выпуску, несколько разработчиков ядра, работающих над Spring, начали обращать внимание на OSGi, и им понравилось то, что они увидели. В частности, им нравились сервисы, которые, как они поняли, были во многом похожи на «бобы», которые Spring связывает вместе. Однако OSGi динамична, а Spring в значительной степени статичен. Еще одна вещь, на которую обратили внимание разработчики Spring, заключалась в том, что модель программирования для сервисов была несколько неудобной в использовании и имела тенденцию включать изрядное количество повторяющегося шаблонного кода. Они рассчитывали, что если Spring и OSGi смогут работать вместе, то получившаяся в результате платформа получит выгоду от силы OSGi в динамике и силы Spring в упрощенных моделях программирования и уменьшенном шаблоне.Поэтому было принято решение о создании поддержки OSGi в Spring 2.1.
Откровенно говоря, существует очень высокая степень совпадения между поддержкой SpringG OSGi и декларативными сервисами. Разработчики Spring рассмотрели декларативные службы и решили, что Spring уже делает многое из того, что делает DS, и делает это лучше. Этот момент спорен. Но в итоге долгосрочный успех DS отнюдь не гарантирован. Несмотря на то, что де-юре является стандартом из спецификаций OSGi, разработчики склоняются в пользу стандарта де-факто, который вполне может оказаться Spring. Фактически, недавние предложения от Питера Криенса (евангелиста OSGi) указывают, что он выступает за то, чтобы Spring-OSGi стал частью спецификации OSGi в Выпуске 5.
Кстати, важно отметить, что (как и в DS) поддержка OSGi в Spring потребует тех же настроек времени выполнения, чтобы обеспечить использование по требованию. Также Spring-OSGi находится в зачаточном состоянии и на данный момент абсолютно не подходит для производственного использования. Декларативные службы несколько более зрелые, но реализация, доступная в Eclipse, все еще немного ошибочна. Частично это происходит из-за проблемы «курица и яйцо» — если бы больше разработчиков использовали DS и сообщали об ошибках, то DS стал бы более стабильным. Но до тех пор, пока он не станет стабильным, многие разработчики воздерживаются от его использования.
Итак, DS или даже Spring-OSGi когда-нибудь заменит использование расширений в Eclipse? Это возможно, да. Но это вряд ли произойдет в течение нескольких лет. К тому времени я полагаю, что Eclipse будет широко использоваться как для серверных платформ, так и для IDE и графических интерфейсов, и в расширенном объеме Eclipse может потребоваться более гибкая модель, чем это обеспечивают расширения. Тем не менее, расширения, безусловно, будут еще долго.
Так что же нам делать сегодня? Должны ли мы начать планирование перехода от расширений к декларативным службам или Spring-OSGi? Должны ли мы беспокоиться о создании наших приложений с расширениями, которые могут привести к устареванию?
Нет, совсем нет. Если расширения соответствуют вашим требованиям, не бойтесь: используйте расширения. Они зрелые и будут поддерживаться в течение долгого времени в будущем. Значит ли это, что вам следует избегать сервисов, DS или Spring-OSGi? Тоже нет. Если они хорошо соответствуют вашим требованиям, используйте их. Что ж, было бы разумно подождать, пока Spring-OSGi станет зрелым, но традиционные сервисы уже очень развиты, и DS уже достаточно зрелый, чтобы первые пользователи могли его использовать.
Увы, квалификацию «если они хорошо соответствуют вашим требованиям» оценить не так просто. На практике вы можете обнаружить, что ваши требования могут быть удовлетворены как расширениями, так и услугами. Трудная часть — это решить, какая из них лучше всего подходит. Вот несколько рекомендаций.
Для людей, создающих плагины, которые будут установлены в более крупные приложения, такие как Eclipse IDE, выбор предельно ясен: используйте расширения. Это просто единственный способ на данный момент, так как вы должны использовать то, что использует хост-приложение. Если вы напишите свои плагины, используя только сервисы, на другом конце просто никто не будет слушать.
Если вы создаете приложение RCP, выбор все еще довольно ясен: чаще всего используйте расширения. Вместо этого невозможно отказаться от расширения и использовать сервисы, особенно если у вас есть контроль над всем приложением. Но тем самым вы пожертвуете существующей структурой RCP, которая предполагает, что вы будете использовать расширения. Вам нужно будет переопределить всю инфраструктуру представлений, перспектив, редакторов, наборов действий и т. Д. Кроме того, у вас будут проблемы с активацией, и вы не будете извлекать выгоду из отложенной загрузки, если не используете декларативные службы (или Spring-OSGi) плюс последние вехи Eclipse 3.3.
С другой стороны, вы можете обнаружить, что некоторые части вашего приложения хорошо работают со службами. Если это так, то во что бы то ни стало используйте их в дополнение к расширениям. Но убедитесь, что понимаете проблемы активации, и имейте план для обеспечения того, чтобы плагины, которые должны быть запущены, действительно начинали.
Если вы используете Eclipse или OSGi для разработки чего-то другого, кроме приложения RCP — такого как сервер или, возможно, пакетный процесс, — тогда сервисы могут быть лучшим выбором. В таких приложениях функциональность горячей замены без простоя может быть важнее, чем быстрый запуск. Опять же, ничто не мешает вам использовать расширения, но вам придется иметь дело с API динамических расширений и его недостатками.
Распространенные заблуждения
Прежде чем идти на компромисс, давайте кратко рассмотрим некоторые распространенные заблуждения. Следующие аргументы иногда используются для обоснования предпочтения расширений или сервисов:
-
«Расширения не являются чистой OSGi». Это неправда. Реестр расширений реализуется пакетом
org.eclipse.equinox.registry
, который зависит отorg.eclipse.equinox.common
. Эти пакеты реализованы с использованием чистого кода OSGi. Никаких секретных хуков в специфических функциях Equinox (реализация OSGi для Eclipse) не существует, и поэтому нет никаких причин, по крайней мере теоретически, в том, что расширения нельзя использовать в других средах выполнения OSGi, таких как Apache Felix или Knopflerfish. -
«Сервисы не имеют метаданных». Это тоже неправильно. Сервисы могут публиковаться с любым количеством произвольных свойств, и потребители могут легко получить к ним доступ. Расширения, возможно, предоставляют более гибкие метаданные, чем сервисы (т. Е. Узел XML по сравнению со списком пар имя / значение), но вряд ли это будет реальным отличием.
-
«Расширения являются наследием Eclipse 2.0». В некотором смысле это верно, но «наследие» может означать «устаревшее». Как уже говорилось, расширения далеко не устарели и не будут в течение некоторого времени.
Матрица сравнения
расширения | Сервисы | Декларативные услуги | Весна-OSGi | |
---|---|---|---|---|
Что зарегистрировано? | Объявления XML, опционально содержащие имена классов. | Объекты Java. | Заполнители-прокси для объектов Java, замененные реальными объектами Java при первом использовании. | Заполнители-прокси для объектов Java, замененные реальными объектами Java при первом использовании. |
Как они зарегистрированы? | Все <extension> узлы в plugin.xml автоматически регистрируются. |
Через BundleContext API |
Все XML-файлы сервисных компонентов, объявленные через Service- Component запись манифеста, автоматически регистрируются. |
Все файлы Spring XML, содержащиеся в META-INF/spring папке пакета, или объявленные черезSpring-Context |
Как они потребляются? | Запрашивается по идентификатору точки расширения. Реестр возвращает все записи, потребитель должен выполнить их итерацию, чтобы найти то, что он хочет. | Запрашивается по имени интерфейса плюс фильтр свойств. Обычно фильтр достаточно специфичен, чтобы обеспечить только одно совпадение. | Внутренне использует тот же запрос к имени интерфейса плюс фильтр, но запрос выполняется в пределах SCR. Соответствующие сервисы предоставляются потребительскому коду с помощью методов установки в стиле JavaBean. | То же, что и для DS, но может дополнительно предоставлять соответствующие сервисы через аргументы конструктора. |
Что такое мощность? | Условно один ко многим. Одна точка расширения имеет много расширений, но у каждого расширения есть только одна точка расширения. Однако другой плагин может запрашивать точки расширения, которые ему не принадлежат. | Много ко многим. Один сервис может использоваться несколькими потребителями, а один потребитель может использовать много сервисов. | То же, что и для услуг | То же, что и для услуг |
Когда они загружены? | Объявления расширений загружаются во время запуска или при установке нового плагина. Классы, названные в расширении, загружаются лениво, т.е. только при необходимости. | Класс, который реализует сервис, должен быть загружен и создан до того, как сервис будет зарегистрирован. Это происходит только тогда, когда пакет явно запущен. | Для отложенных сервисов SCR регистрирует прокси-сервис-заполнитель сразу же после разрешения пакета с поддержкой DS. Реальный класс загружается и создается при первом использовании сервиса. | То же, что и для DS. |
Как происходит динамическая установка / удаление? | Либо запрос на каждое использование или отслеживать с ExtensionTracker . |
Либо запрос на каждое использование или отслеживать с ServiceTracker . |
SCR вызывает методы для компонента, чтобы предоставить ему последнюю доступную службу сопоставления. Он также может вызывать unset метод, когда служба становится недоступной. |
Подобно DS, предоставляет последний сервис сопоставления через метод set для bean-компонента. |
Может ли кеширование ссылок на расширения / сервисы вызывать проблемы? | Да, и как наследие использования до OSGi, некоторые плагины все еще делают это. | Да, но это сильно не рекомендуется спецификацией, и на практике это случается редко. | Нет, SCR не делает этого. | Нет, Spring-OSGi этого не делает. |
Вывод
В этой статье я в общих чертах описал некоторые сильные и слабые стороны расширений в стиле Eclipse и сервисов в стиле OSGi. Однако я не хотел бы, чтобы мои читатели убрали упрощенное сообщение о том, что «расширения не являются динамическими» или «службы не могут использоваться в приложениях RCP». Боюсь, что проблемы слишком тонкие для этого, и ничто не заменит вашей собственной оценки в контексте ваших требований. Чтобы помочь вам сделать это, я оставляю вам некоторые ссылки на дополнительную информацию по каждой из обсуждаемых тем.
Рекомендации
-
Eclipse Equinox можно загрузить с http://www.eclipse.org/equinox/ .
-
Спецификации OSGi и JavaDocs можно просмотреть через http://www.osgi.org .
-
Для получения полной информации о расширениях обратитесь к справочной документации, прилагаемой к Eclipse. Также попробуйте книгу Джеффа Мак-Аффера и Жана-Мишеля Лемье (Addison-Wesley) Eclipse Rich Client Platform: Проектирование, кодирование и упаковка Java-приложений .
-
Сведения об услугах см. В разделе 4 «Базовая спецификация платформы обслуживания OSGi», раздел 5. Также см. Учебное пособие по службам на веб-сайте Knopflerfish и на странице «Snippets» Питера Криенса.
-
Служба переплета Умберто Сервантеса и Ричарда С. Холла
-
Декларативные услуги см. В сборнике услуг OSGi, выпуск 4, раздел 112.
-
Felix Dependency Manager пока еще не документирован, но основан на Xenotron Service Manager
-
IPOJO Клемента Эскофье
-
Для Spring-OSGi см. Предварительную документацию на веб-сайте Spring .
-
Для разработки политики отложенной активации OSGi («настройка», необходимая для работы отложенных служб в декларативных службах), см. Эту страницу в OSGi Wiki .
Подтверждения
Я хотел бы поблагодарить Алекса Блевитта, Джеффа Макаффера и Питера Криенса за их неоценимый вклад в этот документ. Однако все высказанные мнения являются моими, как и все ошибки и упущения.