Статьи

Как создавать расширяемые Java-приложения

Многие приложения выигрывают от возможности расширения. В этом посте описаны два способа реализации такой расширяемости в Java.

Расширяемые приложения

Расширяемые приложения — это приложения, функциональность которых может быть расширена без перекомпиляции, а иногда даже без перезапуска. Это может произойти, просто добавив jar в classpath или с помощью более сложной процедуры установки
Точки расширения

Одним из примеров расширяемого приложения является Eclipse IDE. Это позволяет устанавливать расширения, называемые подключаемыми модулями , чтобы новые функциональные возможности стали доступны. Например, вы можете установить плагин управления исходным кодом (SCM) для работы с вашим любимым SCM.

В качестве другого примера представьте реализацию спецификации XACML для авторизации . «X» в XACML означает «расширяемый», а спецификация определяет ряд точек расширения , таких как идентификаторы атрибутов и категорий, объединяя алгоритмы, функции и информационные точки политики. Хорошая реализация XACML позволит вам расширить продукт, предоставив модуль, который реализует точку расширения.

Интерфейс поставщика услуг

Решением Oracle для создания расширяемых приложений является интерфейс поставщика услуг (SPI).

В этом подходе точка расширения определяется интерфейсом:

1
2
3
4
5
package com.company.application;
 
public interface MyService {
  // ...
}

Вы можете найти все расширения для такой точки расширения, используя класс ServiceLoader :

01
02
03
04
05
06
07
08
09
10
11
public class Client {
 
  public void useService() {
    Iterator<MyService> services = ServiceLoader.load(
        MyService.class).iterator();
    while (services.hasNext()) {
      MyService service = services.next();
      // ... use service ...
  }
 
}

Расширением для этой точки расширения может быть любой класс, реализующий этот интерфейс:

1
2
3
4
5
package com.company.application.impl;
 
public class MyServiceImpl implements MyService {
  // ...
}

Класс реализации должен быть общедоступным и иметь открытый конструктор без аргументов. Однако этого недостаточно для того, чтобы класс ServiceLoader нашел его.

Вы также должны создать файл с именем полного имени интерфейса точки расширения в META-INF/services . В нашем примере это будет:

1
META-INF/services/com.company.application.Myservice

Этот файл должен быть в кодировке UTF-8, иначе ServiceLoader не сможет его прочитать. Каждая строка этого файла должна содержать полное имя одного расширения, реализующего точку расширения, например:

1
com.company.application.impl.MyServiceImpl

OSGi Services

Сервисный реестр Описанный выше подход SPI работает, только когда файлы точек расширения находятся в пути к классам.
В среде OSGi это не так. К счастью, у OSGi есть собственное решение проблемы расширяемости: сервисы OSGi .

Благодаря декларативным сервисам сервисы OSGi просты в реализации, особенно при использовании аннотаций Apache Felix Service Component Runtime (SCR):

1
2
3
4
5
@Service
@Component
public class MyServiceImpl implements MyService {
  // ...
}

С OSGi и SCR также очень легко использовать сервис:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class Client {
 
  @Reference
  private MyService myService;
 
  protected void bindMyService(MyService bound) {
    myService = bound;
  }
 
  protected void unbindMyService(MyService bound) {
    if (myService == bound) {
      myService = null;
    }
  }
 
  public void useService() {
    // ... use myService ...
  }
 
}

Лучшее обоих миров

Так какой из двух вариантов выбрать? Конечно, это зависит от вашей ситуации. Когда вы находитесь в среде OSGi, выбор, очевидно, должен быть сервисами OSGi. Если вы не находитесь в среде OSGi, вы не можете их использовать, поэтому вы остаетесь с SPI.

Кекс Но что, если вы пишете фреймворк или библиотеку и не знаете, будет ли ваш код использоваться в среде OSGi или classpath?

Вы захотите обслуживать как можно больше пользователей вашей библиотеки, поэтому лучше всего будет поддерживать обе модели. Это можно сделать, если вы осторожны.

Обратите внимание, что добавление файла компонента службы декларативных служб, такого как OSGI-INF/myServiceComponent.xml в ваш jar-файл (что в итоге и делают аннотации SCR при обработке) будет работать только в среде OSGi, но вне OSGi безвредно.

Аналогично, служебный файл SPI будет работать в традиционной среде classpath, но безвреден для OSGi.

Таким образом, два подхода на самом деле являются взаимоисключающими, и в любой конкретной среде только один из двух подходов найдет что-либо Таким образом, вы можете написать код, который использует оба подхода. Это немного дублирует, но позволяет вашему коду работать в обоих типах сред, так что вы можете получить свой торт и съесть его тоже.

Ссылка: Как создавать расширяемые Java-приложения от нашего партнера по JCG Ремона Синнема в блоге по безопасной разработке программного обеспечения .