Статьи

OSGI — Модуляризация вашего приложения

Так как я большой сторонник модульности, низкого сцепления, высокого сцепления и т. Д.
Я считаю, что эта технология — прорыв в том, как мы создаем приложения, используя платформу Java. С OSGi очень просто создавать расширяемые приложения, например, в Eclipse IDE. Моя цель здесь — не показать подробно, как работает технология, а продемонстрировать небольшой пример некоторых ее преимуществ. Образец состоит из системы для отправки сообщений. Пользователь вводит сообщение в TextField, и это сообщение может быть отправлено несколькими способами, такими как электронная почта или SMS. Однако в этом примере у нас есть четыре модуля. Графический интерфейс пользователя, домен, отправитель сообщений электронной почты и отправитель через SMS.

Следуя номенклатуре OSGi, каждый модуль представляет собой Bundle. Bundle — это не что иное, как «баночка» с дополнительной информацией от MANIFEST.MF. Эта информация используется платформой OSGi. Как и почти все в Java, технология OSGi является спецификацией и поэтому имеет различные реализации на выбор. Среди них самые известные Равноденствие (Eclipse Project), Феликс (Apache) и Knopflerfish. В этой статье мы будем использовать Равноденствие.

Загрузите Равноденствие. Для этой статьи нам нужна только баночка. Запустите банку, чтобы получить доступ к консоли Равноденствия.

1
C:\osgi>java -jar org.eclipse.osgi_3.5.1.R35x_v20090827.jar –console

Для просмотра установленных пакетов просто введите команду ss.

1
2
C:\osgi>java -jar org.eclipse.osgi_3.5.1.R35x_v20090827.jar – console
osgi> ss

Framework запущен.

1
2
3
id State Bundle<
0 ACTIVE org.eclipse.osgi_3.5.1.R35x_v20090827
osgi> _

Как мы видим на данный момент, у нас установлен только один пакет. Расслоение равноденствия.
Теперь мы создадим наш пакет и добавим его в Equinox. Создать комплект очень просто.
Создайте простой проект со следующим классом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package br.com.luiscm.helloworld;
 
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
 
public class Activator implements BundleActivator {
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    public void start(BundleContext context) throws Exception {
        System.out.println("Hello World!");
    }
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    public void stop(BundleContext context) throws Exception {
        System.out.println("Good Bye World!");
    }
}

Этот класс является Активатором нашего комплекта. Активатор используется платформой OSGi для запуска или остановки пакета. В этом первом примере активатор будет печатать сообщения только при запуске и остановке. Теперь нам нужно изменить манифест jar, чтобы сделать его OSGi-пакетом.

1
2
3
4
5
6
7
8
9
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: LuisCM Plug-in
Bundle-SymbolicName: br.com.luiscm.helloworld
Bundle-Version: 1.0.0
Bundle-Activator: br.com.luiscm.helloworld.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExcutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version=”1.3.0?

Посмотрите МАНИФЕСТ, переданный в комплект OSGi, часть нашей информации. Среди них имя пакета (SymbolicName) и класс Activator. Теперь давайте установим этот пакет в Equinox. Сгенерировать банку проекта и установить его в Equinox просто:

1
2
3
4
install file:.jar
osgi> install file:bundle.jar
Bundle id is 1
osgi>

Чтобы убедиться, что пакет был правильно установлен, просто запустите команду ss:

1
2
3
4
5
6
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.5.1.R35x_v20090827
1 INSTALLED br.com.luiscm.helloworld_1.0.0
osgi> _

Пакет правильно установлен, просто запустите его сейчас:

1
2
3
4
start
osgi> start 1
Hello World!
osgi>

Чтобы остановить пакет:

1
2
3
osgi> stop 1
Goodbye World!
osgi>

Теперь, когда мы знаем, как создать пакет, давайте начнем наш пример. В примере у нас есть четыре пакета.

* Домен: как видно из названия, в нашем примере хранятся доменные классы. У нас будет два класса: Message и IMessageSender.
* SenderSMS: реализация IMessageSender, который отправляет сообщения через SMS.
* SenderEmail: реализация IMessageSender, который отправляет сообщения по электронной почте.
* UI: пример графического интерфейса

Bundle UI

Начнем с комплекта пользовательского интерфейса. Активатор просто построит фрейм, чтобы пользователь мог ввести сообщение.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package br.com.luiscm.helloworld;
 
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
 
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
 
import br.com.luiscm.helloworld.core.service.Message;
 
public class Activator implements BundleActivator {
 
    private Message message;
    private JFrame frame;
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    public void start(BundleContext context) throws Exception {
        buildInterface();
    }
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    public void stop(BundleContext context) throws Exception {
        destroyInterface();
    }
 
    private void destroyInterface() {
        frame.setVisible(false);
        frame.dispose();
    }
 
    private void buildInterface() {
        frame = new JFrame("Hello");
        frame.setSize(200, 80);
        frame.getContentPane().setLayout(new BorderLayout());
        final JTextField textField = new JTextField();
 
        final JButton button = new JButton("Send");
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                message.send(textField.getText());
            }
        });
 
        frame.getContentPane().add(textField, BorderLayout.NORTH);
        frame.getContentPane().add(button, BorderLayout.SOUTH);
        frame.setVisible(true);
    }
}

Обратите внимание, что комплект зависит от класса с именем Message. Этот класс является нашим доменом, поэтому он не является частью этого пакета. Здесь прибывает другая деталь OSGi. Общение осуществляется через пакеты услуг. Мы можем рассматривать эту модель как SOA внутри виртуальной машины. Пользовательский интерфейс пакета услуг будет использовать ядро ​​пакета. Давайте посмотрим на интерфейс пакета MANIFEST.

01
02
03
04
05
06
07
08
09
10
11
12
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: UI Plug-in
Bundle-SymbolicName: br.com.luiscm.helloworld.ui<
Bundle-Version: 1.0.0
Bundle-Activator: br.com.luiscm.helloworld.ui.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: br.com.luiscm.helloworld.core.service,
javax.swing,
org.osgi.framework;version=”1.3.0?,
org.osgi.util.tracker;version=”1.3.6?

Смотрите инструкцию Import-Package. Мы импортируем ядро ​​пакета. В этом пакете находятся услуги, которые предоставляет наша область. Также импортируйте пакет javax.swing.

Теперь нам нужно создать сервис.

Bundle Core

Базовый комплект состоит из двух классов доменов. Интерфейс отправителей и поле сообщения.

Интерфейс:

1
2
3
4
5
package br.com.luiscm.helloworld.core.service;
 
public interface IMessageSender {
    void send(String message);
}

Домен:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package br.com.luiscm.helloworld.core.service;
 
import java.util.ArrayList;
import java.util.List;
 
public class Message {
 
    private final List services = new ArrayList();
 
    public void addService(final IMessageSender messageSender) {
        services.add(messageSender);
    }
 
    public void removeService(final IMessageSender messageSender) {
        services.remove(messageSender);
    }
 
    public void send(final String message) {
        for (final IMessageSender messageSender : services) {
           messageSender.send(message);
        }
    }
}

См. Класс сообщения состоит из списка услуг. Эти сервисы являются отправителями сообщений, которые будут использоваться. Обратите внимание, что метод send взаимодействует только с сообщением в списке рассылки. Пока все очень просто. Теперь нам нужно экспортировать класс Message в качестве основного пакета услуг. Модуль пользовательского интерфейса будет напрямую взаимодействовать с этой службой для отправки сообщений.

Для начала нам нужно сказать ему экспортировать пакет OSGi в другие пакеты. Смотрите МАНИФЕСТ:

01
02
03
04
05
06
07
08
09
10
11
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Helloworld Plugin
Bundle-SymbolicName: br.com.luiscm.helloworld.core
Bundle-Version: 1.0.0
Bundle-Activator: br.com.luiscm.helloworld.core.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version=”1.3.0?,
org.osgi.util.tracker;version=”1.3.6?
Export-Package: br.com.luiscm.helloworld.core.service

Смотрите информацию Экспорт-Пакет. Чтобы класс был виден для другого пакета, его необходимо экспортировать в пакет. В нашем случае для интерфейса пользователя необходим пакет класса Message, поэтому нам нужно экспортировать пакет, в котором находится класс. Помните, что пользовательский интерфейс импортировал пакет.

Сообщение Чтобы зарегистрировать компонент как сервис, нам нужно напрямую взаимодействовать с OSGi API. Когда основной пакет запущен, мы зарегистрируем сервис в контексте OSGi. Код прост:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package br.com.luiscm.helloworld.core;
 
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
 
public class Activator implements BundleActivator {
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    public void start(BundleContext context) throws Exception {
        context.registerService(Message.class.getName(), messageService, null);
    }
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    public void stop(BundleContext context) throws Exception {
        messageService = null;
    }
}

Метод registerService ожидает параметр в качестве имени службы (для рекомендации это имя класса), самой службы и некоторых дополнительных настроек.

Теперь нам нужно изменить пользовательский интерфейс, чтобы использовать пакетный сервис сообщений. В пользовательском интерфейсе активатора пакета просто выполните службу поиска, используя свое имя (имя класса):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    private Message message;
    private JFrame frame;
    private ServiceTracker serviceTracker;
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    public void start(BundleContext context) throws Exception {
        serviceTracker = new ServiceTracker(context, Message.class.getName(), null);
        serviceTracker.open();
        message = (Message)serviceTracker.getService();
        buildInterface();
    }
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    public void stop(BundleContext context) throws Exception {
        destroyInterface();
        serviceTracker.close();
    }
}

Если мы добавим две связки в Равноденствие, мы увидим, что эти две связки общаются. Теперь нам нужно создать пакеты, которые действительно отправляют сообщения.

Bundle Sender Email и SMS

Услуги доставки по электронной почте и SMS будут новыми услугами нашей системы. Поэтому мы создаем связку для каждого. Таким образом, мы можем контролировать их отдельно. Например, мы можем остановить службу, отправив SMS и оставить только электронную почту, не влияя на работу системы. Два пакета имеют практически одинаковую структуру, поэтому я сохраню некоторые строки здесь.

У отправителя будет только один класс комплекта, который реализует интерфейс и класс IMessageSender Activator. Этот интерфейс находится в базовом комплекте, поэтому нам нужно импортировать пакет так же, как мы делали в интерфейсе пакета.

01
02
03
04
05
06
07
08
09
10
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-name: SMS Plug-in
Bundle-SymbolicName: br.com.luiscm.helloworld.sms.Activator<
Bundle-Version: 1.0.0
Bundle-Activator: br.com.luiscm.helloworld.sms.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: br.com.luiscm.helloworld.core.service,
org.osgi.framework;version=”1.3.0?

Единственный класс Sender реализует наш интерфейс:

01
02
03
04
05
06
07
08
09
10
11
package br.com.luiscm.helloworld.sms;
 
import br.com.luiscm.helloworld.core.service.IMessageSender;
 
public class MessageSenderSMS implements IMessageSender {
 
    @Override
    public void send(final String message) {
        System.out.println("Sending by SMS : " + message);
    }
}

Отправка по СМС является услугой нашей системы. Поэтому мы должны зарегистрировать его в контексте OSGi:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class Activator implements BundleActivator {
 
    private IMessageSender service;
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    public void start(BundleContext context) throws Exception {
        service = new MessageSenderSMS();
        context.registerService(IMessageSender.class.getName(), service, null);
    }
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    public void stop(BundleContext context) throws Exception {
        service = null;
    }
}

Пакет почты практически тот же код. Единственное отличие — это сообщение на System.out.

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

Теперь у нас есть два сервиса для отправки сообщений, нам нужно изменить наше ядро ​​пакета, чтобы использовать их. Для достижения этой цели используется ServiceTrackerCustomizer.

ServiceTrackerCustomizer e ServiceTracker

Как мы видели, мы использовали сервис поиска Servicetrack. Однако в случае отправителей нам нужно знать, когда доступна новая служба отправителей или когда отправитель удален. Эта информация важна для подачи списка услуг в объекте Message.

Для доступа к этой информации мы используем ServiceTrackerCustomizer. Код прост:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package br.com.luiscm.helloworld.core;
 
import org.osgi.framework.BundleContext;
 
public class MessageSenderServiceTracker implements ServiceTrackerCustomizer {
 
    private final BundleContext context;
    private final Message message;
 
    public MessageSenderServiceTracker(final BundleContext context, final Message message) {
        this.context = context;
        this.message = message;
    }
 
    @Override
    public Object addingService(final ServiceReference serviceReference) {
        final IMessageSender sender = (IMessageSender)context.getService(serviceReference);
        message.addService(sender);
        System.out.println("tracker : " + sender.getClass().getName());
        return sender;
    }
 
    @Override
    public void removedService(final ServiceReference serviceReference, Object service) {
        final IMessageSender sender = (IMessageSender)context.getService(serviceReference);
        message.removeService(sender);
    }
}

Просто реализуйте интерфейс и код ServiceTrackerCustomizer, что вы хотите, когда сервис добавляется, изменяется или удаляется. Просто!

В нашем случае мы добавим или удалим сервис из списка сервисов нашего объекта Message. Также есть сообщение «log», чтобы помочь нам с тестированием.

Теперь нам нужно сделать еще одно небольшое изменение в активаторе ядра пакета. Мы должны зарегистрировать наш ServiceTrackerCustomizer в качестве прослушивателя таких служб, как IMessageSender.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Activator implements BundleActivator {
 
    public Message messageService = new Message();
    private ServiceTracker messageSenderServiceTracker;
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    public void start(BundleContext context) throws Exception {
        context.registerService(Message.class.getName(), messageService, null);
        final MessageSenderServiceTracker serviceTracker = new MessageSenderServiceTracker(context, messageService);
        messageSenderServiceTracker = new ServiceTracker(context, IMessageSender.class.getName(), serviceTracker);
        messageSenderServiceTracker.open();
    }
 
    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    public void stop(BundleContext context) throws Exception {
        messageSenderServiceTracker.close();
        messageService = null;
    }
}

Мы используем ServiceTrackerCustomizer вместе с ServiceTtracker. Если служба добавлена, изменена или удалена, будет вызван наш компонент.

Тестирование приложения

Теперь, когда мы кодируем, мы тестируем приложение.

Создайте четыре банки:

* bundleCore.jar
* bundleUI.jar
* bundleSenderEmail.jar
* bundleSenderSMS.jar

Установите четыре пакета в Равноденствие:

Framework запущен.

01
02
03
04
05
06
07
08
09
10
11
id State Bundle
0 ACTIVE org.eclipse.osgi_3.5.1.R35x_20090827.jar
osgi> install file:bundleCore.jar
Bundle id is 5
osgi> install file:bundleUI.jar
Bundle id is 6
osgi> install file:bundleSenderEmail.jar
Bundle id is 7
osgi> install file:bundleSenderSMS.jar
Bundle id is 8
osgi> ss

Framework запущен.

1
2
3
4
5
0 ACTIVE org.eclipse.osgi._3.5.1.R35x_v20090827.jar
5 INSTALLED br.com.luiscm.helloworld.core_1.0.0
6 INSTALLED br.com.luiscm.helloworld.ui_1.0.0
7 INSTALLED br.com.luiscm.helloworld.email_1.0.0
8 INSTALLED br.com.luiscm.helloworld.sms_1.0.0

Запустите пакеты и протестируйте приложение.

1
2
3
4
C:\osgi>java -jar org.eclipse.osgi._3.5.1.R35x_v20090827.jar -console
osgi> tracker: br.com.luiscm.heloworld.sms.SenderSMS
 
tracker: br.com.luiscm.helloworld.email.SenderEmail

Посмотрите, какие сообщения отправляются по электронной почте и SMS. В консоли Equinox приостановите службу электронной почты:

1
stop

Попробуйте еще раз отправить сообщение. Поскольку услуга больше недоступна, сообщение было отправлено только по SMS.

Остановка модулей питания без побочных эффектов — сенсация. Представьте, что вы обнаружили критическую ошибку в модуле SMS. Вам не нужно брать весь воздух, подаваемый для устранения этой проблемы. Просто приостановите модуль SMS. Остальная часть системы продолжит нормальную работу. Пройдите тест на этом небольшом примере. Пауза и запуск услуг. Это не повлияет на ядро, тем более на пользовательский интерфейс.

Мне удалось немного объяснить, что такое OSGi. Примечательно, что здесь гораздо больше подробностей о пакетах управления и конфигурации classpath, на которые здесь не обращают внимания. Это задача для интересующихся взглянуть на другие функции.

Стоит посмотреть на проект Spring-DM. Пружина позволяет очень легко настроить сервисы в дополнение к предоставлению отличного контейнера IoC.

Ссылка: OSGI — Модульная форма вашего приложения от нашего партнера JCG Луиса Карлоса Морейра да Коста в блоге Eclipse Brazil .