OSGI/Apache Felix: практический пример
Давайте, наконец, рассмотрим как писать компоненты для Apache Felix. Начало здесь.
В Java есть такое понятие — интерфейс. Оно абстрактно описывает поведение некоего объекта, его методы. Например, если мы собираемся работать с базами данных, то нужно прежде всего установить с ними соединение. Опишем это так:
Реализаций этого интерфейса может быть множество. Можно создать компоненты, которые будут предоставлять соединения с базами PostgreSQL, MySQL (как в этом примере) и т.п. Совсем как в топике про ODBC, только Java в ODBC не нуждается, у нее свой механизм — JDBC.
Итак, интерфейс написан, нужно его превратить в бандл (bundle) — по сути jar-архив, только манифест-файл у него позаковыристей, чем у обычного. Пока не буду раскрывать всех деталей создания бандлов чтобы не разрывать повествование. Скажу только, что интерфейсы в OSGi (как и вообще в Java) хранятся, как правило, отдельно от своих реализаций, так что бандлов будет несколько. А вот более понятная мысль: бандлы устанавливаются в систему Apache Felix тупым копированием в указанный в его конфигурационном файле каталог.
Во втором бандле будет храниться объект, реализующий вышеприведенный интерфейс и предоставляющий по шине эти самые соединения с базой данных «всем желающим» модулям. Код его тоже не сложен и представляет собой хрестоматийное соединение с базой по JDBC. Сначала загружается драйвер, потом на основе описания, вытаскиваемого из настроечного текстового файла, создается соединение:
Здесь кода побольше (хотя это программа-карлик), но разобраться можно. Метод start() запускается в момент запуска модуля, stop() — чтобы его остановить. При этом всем модулям, которые используют данный бандл, посылается соответствующий сигнал и им приходится потерпеть, пока программист, например, обновляем модуль из новой версии. На это уходит несколько секунд, т.е. это самая настоящая «горячая замена». Например, вы отлаживаете какое-то приложение вместе с коллегами и вам сообщают, что в таком-то месте глюк. Вы не торопясь исправляете ошибку (в это время глючная версия продолжает пока худо-бедно исполнять то, что может исполнять), готовите обновленный бандл, потом останавливаете старый модуль, обновляете и вновь запускаете. Это похоже на ситуацию в цеху, когда из станка с ЧПУ вытаскивают перегоревшую плату, заменяют новой и через мгновение он опять работает. Или в дата-центре горячая замена винчестеров. Т.е. не нужно ничего останавливать, парализуя работу множества служб. Обновились на 5 секунд, обновились — поехали дальше.
Вышеприведенную реализацию интерфейса нужно превратить в бандл и тут мы подходим к главной трудности их формирования: в них нужно добавлять все классы, к которым они могут обратиться во время своей работы. Внешних библиотек, настраиваемых в CLASSPATH, OSGi не признает, да и удобно это — когда необходимые библиотеки упакованы в единый jar-файл. Голова на тему Jar-hell не болит. Как упаковать приведенный код в бандл, реализующий MySQL-соединение? Есть специальный плагин Bnd для сборщика Ant. Привожу работающий build.xml, в котором прописан также плагин iPOGO (зачем он нужен скажу ниже).
Для того, чтобы бандл сформировался, нужен еще один файл — mysql.bnd вот такого содержания:
Здесь указывается из чего формировать бандл и что при этом игнорировать. Букв здесь много, но если начнете заниматься — быстро поймете сто к чему . Подробности — у автора плагина.
Бандл уже можно было бы «воткнуть» в каталог Apache Felix, если бы не использованное в коде послабление. Во-первых, методам start() и stop() по стандарту должны передаваться довольно сложные аргументы (в нашем примере этого нет). Во-вторых, ссылки на «соседние сервисы» OSGi (веб-сервер и т.п.) организуются тоже довольно утомительным образом (в нижеприведенном примере сервис mysql объявляется, но никак не инициализируется). Чтобы избавиться от этой рутины можно задействовать технологию iPOJO. Платой за это упрощение (а я им пользуюсь, ибо действительно высвобождает время и нервы) является появление еще одного файла. Назовем его mysql.xml. В нем прописываются взаимоотношения между сервисами OSGi: кто кому что предоставляет и что хочет получать. Здесь же указывается какие методы задействовать при запуске и остановке сервисов:
Согласитесь, даже дочитать до этого места непросто. Что уж говорить об освоении на практике. Но если уж хватило терпения освоить и разобраться — программирование серьезных серверов на Java превращается в приятнейшее занятие. Количество кода сокращается в разы. Можно не ломать голову над всякими вспомогательными процедурами, не изобретать велосипеды, а действительно сосредоточиться на программировании предметной области, ради чего и изобретена была в свое время Java.
Давайте, например, воспользуемся созданным бандлом для установления связи с MySQL-базами данных. Напишем такой модуль:
Мне кажется, проще некуда. Не нужно задумываться откуда берется соединение с базой данных. Получил, попользовался, закрыл. Нужно поправить бандл — остановил его (но не весь сервер!) обновил, включил. При этом соединение с базой данных можно опять получать как ни в чем не бывало.
В Java есть такое понятие — интерфейс. Оно абстрактно описывает поведение некоего объекта, его методы. Например, если мы собираемся работать с базами данных, то нужно прежде всего установить с ними соединение. Опишем это так:
package net.sf.lab3f.osgi;
import java.sql.Connection;
import java.sql.SQLException;
public interface Sqlable{
Connection getConnection() throws SQLException, ClassNotFoundException;
}
Реализаций этого интерфейса может быть множество. Можно создать компоненты, которые будут предоставлять соединения с базами PostgreSQL, MySQL (как в этом примере) и т.п. Совсем как в топике про ODBC, только Java в ODBC не нуждается, у нее свой механизм — JDBC.
Итак, интерфейс написан, нужно его превратить в бандл (bundle) — по сути jar-архив, только манифест-файл у него позаковыристей, чем у обычного. Пока не буду раскрывать всех деталей создания бандлов чтобы не разрывать повествование. Скажу только, что интерфейсы в OSGi (как и вообще в Java) хранятся, как правило, отдельно от своих реализаций, так что бандлов будет несколько. А вот более понятная мысль: бандлы устанавливаются в систему Apache Felix тупым копированием в указанный в его конфигурационном файле каталог.
Во втором бандле будет храниться объект, реализующий вышеприведенный интерфейс и предоставляющий по шине эти самые соединения с базой данных «всем желающим» модулям. Код его тоже не сложен и представляет собой хрестоматийное соединение с базой по JDBC. Сначала загружается драйвер, потом на основе описания, вытаскиваемого из настроечного текстового файла, создается соединение:
package net.sf.lab3f.mysql;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import net.sf.lab3f.osgi.Sqlable;
public class MysqlHolder implements Sqlable{
private Properties props = new Properties();
public void start () throws Exception{
props.load(new FileInputStream("conf/mysql.properties"));
}
public void stop () throws Exception{}
public Connection getConnection() throws SQLException, ClassNotFoundException {
Class.forName(props.getProperty("jdbcDriver"));
Connection conn = DriverManager.getConnection(props.getProperty("jdbcUrl"));
return conn;
}
}
Здесь кода побольше (хотя это программа-карлик), но разобраться можно. Метод start() запускается в момент запуска модуля, stop() — чтобы его остановить. При этом всем модулям, которые используют данный бандл, посылается соответствующий сигнал и им приходится потерпеть, пока программист, например, обновляем модуль из новой версии. На это уходит несколько секунд, т.е. это самая настоящая «горячая замена». Например, вы отлаживаете какое-то приложение вместе с коллегами и вам сообщают, что в таком-то месте глюк. Вы не торопясь исправляете ошибку (в это время глючная версия продолжает пока худо-бедно исполнять то, что может исполнять), готовите обновленный бандл, потом останавливаете старый модуль, обновляете и вновь запускаете. Это похоже на ситуацию в цеху, когда из станка с ЧПУ вытаскивают перегоревшую плату, заменяют новой и через мгновение он опять работает. Или в дата-центре горячая замена винчестеров. Т.е. не нужно ничего останавливать, парализуя работу множества служб. Обновились на 5 секунд, обновились — поехали дальше.
Вышеприведенную реализацию интерфейса нужно превратить в бандл и тут мы подходим к главной трудности их формирования: в них нужно добавлять все классы, к которым они могут обратиться во время своей работы. Внешних библиотек, настраиваемых в CLASSPATH, OSGi не признает, да и удобно это — когда необходимые библиотеки упакованы в единый jar-файл. Голова на тему Jar-hell не болит. Как упаковать приведенный код в бандл, реализующий MySQL-соединение? Есть специальный плагин Bnd для сборщика Ant. Привожу работающий build.xml, в котором прописан также плагин iPOGO (зачем он нужен скажу ниже).
<?xml version="1.0" encoding="utf-8"?>
<project name="TuttiFrutty" default="jc" basedir=".">
<property name="classes.raw" value="/_classes/raw"/>
<property name="classes.lib" value="/_classes/lib"/>
<taskdef resource="aQute/bnd/ant/taskdef.properties" classpath="${classes.lib}/task-bnd.jar"/>
<taskdef name="ipojo" classname="org.apache.felix.ipojo.task.IPojoTask" classpath="${classes.lib}/task-ipojo.jar"/>
<target name="pck">
<bnd classpath="${classes.raw}" files="mysql.bnd" output="/_felix/bundle/3f-mysql.jar"/>
<ipojo input="/_felix/bundle/3f-mysql.jar" metadata="mysql.xml"/>
</target>
</project>
Для того, чтобы бандл сформировался, нужен еще один файл — mysql.bnd вот такого содержания:
Export-Package: net.sf.lab3f.mysql; version=1.0
Bundle-Name: 3f-mysql
Bundle-SymbolicName: 3f-mysql
Include-Resource: @/_classes/lib/mysql.jar, @/_classes/lib/commons-logging-1.1.1.jar, \
@/_classes/lib/log4j.jar
Import-Package: net.sf.lab3f.osgi.*, javax.naming.*, \
!org.apache.commons.logging.*, !javax.*, !com.mchange.*, \
!org.apache.log4j, !org.jboss.*, !org.w3c.*, !org.xml.*, \
!com.sun.*, !com.ibm.*, \
!org.apache.avalon.*, !org.apache.log.*
Здесь указывается из чего формировать бандл и что при этом игнорировать. Букв здесь много, но если начнете заниматься — быстро поймете сто к чему . Подробности — у автора плагина.
Бандл уже можно было бы «воткнуть» в каталог Apache Felix, если бы не использованное в коде послабление. Во-первых, методам start() и stop() по стандарту должны передаваться довольно сложные аргументы (в нашем примере этого нет). Во-вторых, ссылки на «соседние сервисы» OSGi (веб-сервер и т.п.) организуются тоже довольно утомительным образом (в нижеприведенном примере сервис mysql объявляется, но никак не инициализируется). Чтобы избавиться от этой рутины можно задействовать технологию iPOJO. Платой за это упрощение (а я им пользуюсь, ибо действительно высвобождает время и нервы) является появление еще одного файла. Назовем его mysql.xml. В нем прописываются взаимоотношения между сервисами OSGi: кто кому что предоставляет и что хочет получать. Здесь же указывается какие методы задействовать при запуске и остановке сервисов:
<?xml version="1.0" encoding="utf-8"?>
<ipojo>
<component classname="net.sf.lab3f.mysql.MysqlHolder">
<provides/>
<callback transition="validate" method="start"/>
<callback transition="invalidate" method="stop"/>
</component>
<instance component="net.sf.lab3f.mysql.MysqlHolder"/>
</ipojo>
Согласитесь, даже дочитать до этого места непросто. Что уж говорить об освоении на практике. Но если уж хватило терпения освоить и разобраться — программирование серьезных серверов на Java превращается в приятнейшее занятие. Количество кода сокращается в разы. Можно не ломать голову над всякими вспомогательными процедурами, не изобретать велосипеды, а действительно сосредоточиться на программировании предметной области, ради чего и изобретена была в свое время Java.
Давайте, например, воспользуемся созданным бандлом для установления связи с MySQL-базами данных. Напишем такой модуль:
package ru.yababay.hz.server;
import java.sql.*;
import net.sf.lab3f.osgi.Sqlable;
public class Main{
private Sqlable mysql;
public void start(){
Connection conn = mysql.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT VERSION()");
if(rs.next())System.out.println(rs.getString(1));
rs.close(); stmt.close(); conn.close();
}
}
Мне кажется, проще некуда. Не нужно задумываться откуда берется соединение с базой данных. Получил, попользовался, закрыл. Нужно поправить бандл — остановил его (но не весь сервер!) обновил, включил. При этом соединение с базой данных можно опять получать как ни в чем не бывало.
Комментарии (1)
RSS свернуть / развернутьИ методы простого копирования в каталог- бальзам на раны…
Те, кто писал обьектно — ор. программы — тех текст не испугает.
Просто я хорошо себе представляю сколько всего осталось за кадром…
На С++ бывало и по 9500 строк, но там — все текст (кроме *.либ и *.обг)
Здесь многое висит в настройках и «за кадром».
Я и не сомневаюсь, что это оптимальный путь.
Просто с опытом оптимизм пропадает.
Markony
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.