OSGI/Apache Felix: практический пример

Давайте, наконец, рассмотрим как писать компоненты для Apache Felix. Начало здесь.



В 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();
 }
}


Мне кажется, проще некуда. Не нужно задумываться откуда берется соединение с базой данных. Получил, попользовался, закрыл. Нужно поправить бандл — остановил его (но не весь сервер!) обновил, включил. При этом соединение с базой данных можно опять получать как ни в чем не бывало.
  • +8
  • 08 сентября 2010, 22:44
  • yababay

Комментарии (1)

RSS свернуть / развернуть
+
0
Да нет, читается как раз нормально и все достаточно «ВКУСНО»!
И методы простого копирования в каталог- бальзам на раны…
Те, кто писал обьектно — ор. программы — тех текст не испугает.
Просто я хорошо себе представляю сколько всего осталось за кадром…
На С++ бывало и по 9500 строк, но там — все текст (кроме *.либ и *.обг)
Здесь многое висит в настройках и «за кадром».
Я и не сомневаюсь, что это оптимальный путь.
Просто с опытом оптимизм пропадает.
avatar

Markony

  • 09 сентября 2010, 10:13

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.