Google Analytics

вторник, 3 января 2012 г.

Hibernate-Extender или Hibernate, Spring и OSGi


К сожалению, на данный момент Hibernate не обладает необходимыми механизмами интеграции для работы в OSGi среде, хотя подвижки в этом направлении заметны (начальная OSGi-фикация путём разделения пакетов в 4-ой ветке). Это побуждает разрабатывать собственные механизмы, что требует немалых дополнительных усилий.

Эта статья предназначается для тех разработчиков, кому интересно: как можно использовать Hibernate со связкой Spring+OSGi; что такое паттерн Extender; как реализовать ClassLoader со специфичным поведением; как поддерживается Hibernate в Spring Framework и немного о расширении этого кода. Разумеется, для чтения статьи необходимо разбираться в технологиях Spring, Hibernate, OSGi, а также понимать основные проблемы выполнения кода в многопоточной среде. Те же кто незнаком с использованием Spring Framework в OSGi среде могут обратиться к вводной статье "Использование Spring в OSGi-контейнере".

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

Паттерн Extender

Программное обеспечение выполняющееся в OSGi-контейнере состоит из обособленных модулей (bundle), каждый из которых выполняет свои специфические обязанности. Что делать, когда один модуль должен использовать какие-либо сложные runtime-механизмы находящиеся в другом модуле? Ответ очевиден - использовать сервисы-OSGi. А что делать, когда эти механизмы требую, например, инициализации (регистрация сервлета в сервлет-контейнере или, как в нашем случае, регистрация маппированных классов Hibernate-сущностей). Ответ на данный вопрос уже менее очевиден. По сути, существуют только два основных варианта: первый - каждый модуль нуждающийся в такой инициализации вызывает её самостоятельно (например из BundleActivator); второй - модуль нуждающийся в такой инициализации обладает некими характерными метаданными по которым другой модуль может инициировать выполнение всей необходимой работы, благо в OSGi хорошо развита система событий. Второй вариант обладает несомненным преимуществом - мы не распространяем кучу одинакового кода по модулям и не обременяем их дополнительной работой, а выносим эту обязанность в отдельный модуль. Собственно по такому принципу работает Spring DM. Этот принцип работы и есть паттерн Extender[http://www.aqute.biz/Snippets/Extender] и он очень распространён в мире OSGi.

Cхема работы типичного Extender

Описание общей картины работы Hibernate-Extender

Приступим к обсуждению общей схемы работы Hibernate-Extender. Прежде всего необходимо выбрать метаданные благодаря которым наш extender будет отличать нуждающиеся в расширении модули от не являющихся таковыми. Пусть это будет заголовок Entity-Classes в META-INF/MANIFEST.MF содержащий список классов Hibernate-сущностей (любители xml-маппингов пока останутся не у дел). Далее необходимо получать события от модулей переходящих в состояние Active и от модулей выходящих из этого состояния, для этого воспользуемся механизмом событий OSGi и реализуем интерфейс SynchronousBundleListener. Мы используем синхронный обработчик событий от модулей, дабы незамедлительно реагировать на изменения состояний модулей перед вызовом асинхронных BundleListener. При этом мы должны обеспечить наименьшее возможное время обработки события, чтобы не блокировать поток вызвавший наш обработчик. Это достигается путём реализации паттерна Producer-Consumer (Производитель-Потребитель), где в качестве Producer выступает наш обработчик событий, который передает информацию о модулях в Consumer, выполняющий все длительные операции в отдельном потоке. В нашем случае он реинициализирует runtime-структуры Hibernate.

Механизм Extender является достаточно обобщёнными, поэтому реализация представленная ниже придерживается Dependency Inversion Principle (SOLID) для облегчения повторного использования кода.

public class Extender implements SynchronousBundleListener {
  
  private BundleContext bundleContext;
  private Executor executor;
  private BlockingQueue<ExtendedBundle> bundles 
    = new LinkedBlockingQueue<ExtendedBundle>();
  private BundleConsumer bundleConsumer;
  private ExtendedBundleFactory extendedBundleFactory;
  
  // getter's и setter's

  @Override
  public void bundleChanged(BundleEvent event) {
    
    Actions  action = null;
    
    switch (event.getType()) {

    case BundleEvent.STARTED: 
      action = Actions.ADDING;
      break;
    case BundleEvent.STOPPING:
      action = Actions.REMOVING;
      break;
    };
    
    if (action == null) return;
    
    ExtendedBundle extendedBundle = 
        extendedBundleFactory.newExtendedFactory(
            event.getBundle(), 
            action
            );

    try {
      bundles.put(extendedBundle);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }    
  }
  
  public void initialize() {
    getBundleConsumer().initialize(getBundles());
    
    getExecutor().execute(bundleConsumer);
    
    getBundleContext().addBundleListener(this);
    
    for (Bundle bundle: getBundleContext().getBundles()) {
      if (Bundle.ACTIVE != bundle.getState()) continue;
      
      ExtendedBundle extendedBundle = 
          extendedBundleFactory.newExtendedFactory(
              bundle, 
              Actions.ADDING
              );

      try {
        bundles.put(extendedBundle);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        break;
      }
    }
  }
  
}

Реализация Extender задействует: BundleContext в котором регистрируется сам Extender в качестве слушателя событий от модулей; Executor реализация исполнителя из фреймворка java.util.concurrent, в котором выполняется потребитель выполняющий длительные операции; BlockingQueue<ExtendedBundle> блокирующая очередь с помощью которой синхронизируются потоки выполнения Producer-Consumer; BundleConsumer конкретная реализация потребителя событий модулей; ExtendedBundleFactory фабрика "расширенных" модулей.

Метод public void initialize() вызывается сразу после конструирования объекта. В этом методе происходит регистрация нашего Extender в качестве обработчика, запуск выполнения потребителя и обработка модулей, которые уже находятся в OSGi-контейнере, на случай когда Extender запускается после расширяемых модулей.

BundleConsumer

BundleConsumer - это интерфейс, объект с типом реализующим этот интерфейс должен выполнять обработку модулей поставленных в очередь Extender.

public interface BundleConsumer extends Runnable {

  void run();
 
  void initialize(
      BlockingQueue<ExtendedBundle> newBundles
      );
 
}

Из кода представленного выше видно, что это всего лишь Runnable с дополнительным методом инициализации. Всё интересное должно происходить в реализации метода void run(). Давайте рассмотрим конкретную реализацию этого интерфейса, которая выполняет основную работу для нашего Hibernate-Extender.

public class SessionFactoryCreator implements BundleConsumer {

  private volatile Extendable extendable;
  private volatile ExtenderClassLoader extenderClassLoader;
  private volatile BlockingQueue<ExtendedBundle> newBundles;

  // getter's, setter's и прочие методы

  @Override
  public void run() {
 
    ExtendedBundle bundle = null;
 
    while (!Thread.currentThread().isInterrupted()) {
   
      try {
        bundle = newBundles.take();
      }
      catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        break;
      }
   
      if (!bundle.isValidForExtending()) continue;
   
      if (Actions.ADDING.equals(bundle.getAction())) {
        addBundle(bundle);
      }
      else if (Actions.REMOVING.equals(bundle.getAction())) {
        removeBundle(bundle);
      }
   
    }
 
  }

  private void addBundle(ExtendedBundle bundle) {
    bundle.extend();
    extenderClassLoader.addBundle(bundle);
    extendable.addBundle(bundle);
    bundle.markAsCompleted();
  }

  private void removeBundle(ExtendedBundle bundle) {
    bundle.extend();
    extendable.removeBundle(bundle);
    extenderClassLoader.removeBundle(bundle);
    bundle.markAsCompleted();
  }

}

Главный цикл в методе void run() выполняет обработку очереди newBundles. При отсутствии расширяемых модулей в очереди, выполнение блокируется в вызове метода newBundles.take(). При появлении расширяемых модулей они перебираются по одному. На каждом из таких модулей вызывается метод isValidForExtending(), который должен возвращать истинное значение в случае если модуль подходит для расширения. Этот метод проверки может занимать значительное время при обращении к внутренним ресурсам пакетов (например вызывать операции ввода/вывода), поэтому он вызывается именно здесь, а не в Extender. Если модуль подходит для расширения, то проверяется мета информация о действии которое необходимо над ним произвести Actions.ADDING или Actions.REMOVING. В зависимости от требуемого действия выполняется операция добавления либо удаления и модули помечается как обработанные.

Хочу обратить внимание читателя на условия выхода из главного цикла обработки !Thread.currentThread().isInterrupted(), оно обеспечивает корректное завершения потока при установки флага прерывания. Ещё одно место, где задействуется данный флаг - это обработка исключения выбрасываемого блокирующимся методом newBundles.take() при возникновении которого происходит установка флага прерывания потока и выход из главного цикла.

Аттрибуты класса помечены как volatile для обеспечения гарантий видимости в многопоточной среде. Создание объекта происходит в одном потоке, а дальнейшее использование в другом. Реализация BlockingQueue является потокобезопасной поэтому её использование в многопточной среде не требует дополнительной синхронизации.

HibernateSessionFactoryBean

Большое количество разнообразных фреймворков не случайно. Использование стороннего, хорошо оттестированного, готового к эксплуатации кода сильно облегчает разработку. Поэтому мы тоже постараемся не изобретать лишнего. Spring Framework поддерживает интеграцию с Hibernate. Одним из основных классов данной интеграции является LocalSessionFactoryBean. В нём осуществляется инициализация и создание фабрики сессий, которая затем используется в любом коде применяющим Hibernate API. У этого класса есть потомок AnnotationSessionFactoryBean, который добавляет некоторый функционал с поддержкой аннотаций. От последнего мы и будем наследовать нашу реализацию, код которой представлен далее.

public class HibernateSessionFactoryBean
    extends AnnotationSessionFactoryBean {

  private SessionFactory sessionFactoryProxy;
  private Lock sessionFactoryAccessLock =
      new ReentrantLock(true);
  private ClassLoader classLoader;
  private Set<Class<?>> annotatedClasses
    = new CopyOnWriteArraySet<Class<?>>();

  /**
   * Установить свой ClassLoader
   * @param classLoader
   */
  public void setClassLoader(ClassLoader classLoader) {
    super.setBeanClassLoader(classLoader);
    this.classLoader = classLoader;  
  }
  /**
   * Отключения установки ClassLoader контейнером через интерфейс BeanClassLoaderAware
   * @param beanClassLoader
   */
  @Override
  public void setBeanClassLoader(ClassLoader beanClassLoader) {
 
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    //Установка своего ClassLoader
    ProxyFactory.classLoaderProvider = new ClassLoaderProvider() {
      public ClassLoader get(ProxyFactory pf) {
        return HibernateSessionFactoryBean.this.classLoader;
      }
    };
 
    super.afterPropertiesSet();      
    sessionFactoryProxy = createSessionFactoryProxy();
  }

  @Override
  public SessionFactory getObject() {
    return sessionFactoryProxy;
  }

  /**
   * Пересоздать фабрику сессий Hibernate
   */
  protected void recreateSessionFactory() throws Exception {
    sessionFactoryAccessLock
      .tryLock(3000, TimeUnit.MILLISECONDS);
 
    try {
      //разрушение всего runtime-контекста связанного с Hibernate
      super.destroy();
      //создание нового runtime-контекста связанного с Hibernate
      super.afterPropertiesSet();
    }
    finally {
      sessionFactoryAccessLock.unlock();
    }
  }

  protected SessionFactory createSessionFactoryProxy()
      throws InstantiationException,
        IllegalAccessException, InterruptedException {
    // создание фабрики прокси объектов
    // для интерфейсов SessionFactory и Extendable
    ProxyFactory sessionFactoryProxyFactory = new ProxyFactory();
    sessionFactoryProxyFactory.setInterfaces(
        new Class[] {SessionFactory.class, Extendable.class}
        );

    //Не обрабатываем вызов метода finalize
    sessionFactoryProxyFactory.setFilter(new MethodFilter() {
      public boolean isHandled(Method m) {
        return !m.getName().equals("finalize");
      }
    });  

    Class<?> sessionFactoryProxyClass =
      sessionFactoryProxyFactory.createClass();
    MethodHandler mi = new MethodHandler() {
      public Object invoke(
          Object self,
          Method thisMethod,
          Method proceed,
          Object[] args) throws Throwable {
        Object result = null;
        //Переопределение TCCL
        ClassLoader defaultClassLoader =
            Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(classLoader);
        try {
          if (thisMethod.getName().contains("addBundle")) {
            addBundle((HibernateBundle) args[0]);
          }
          else if (thisMethod.getName().contains("removeBundle")) {
            removeBundle((HibernateBundle) args[0]);
          }
          else {
            sessionFactoryAccessLock
              .tryLock(3000, TimeUnit.MILLISECONDS);

            try {
              //выполнить оригинальный метод SessionFactory
              result = thisMethod
                .invoke(getSessionFactory(), args);
            }
            finally {
              sessionFactoryAccessLock.unlock();
            }
          }
        }
        finally {
          //Восстановление TCCL действующего до переопределения
          Thread.currentThread()
            .setContextClassLoader(defaultClassLoader);
        }

        return result;
      }
    };

    SessionFactory sessionFactory =
        (SessionFactory)sessionFactoryProxyClass.newInstance();
    ((ProxyObject)sessionFactory).setHandler(mi);


    return sessionFactory;
  }

  private void addBundle(HibernateBundle bundle) throws Exception {
    for (String entityClassName: bundle.getEntityClassNames()) {
      annotatedClasses.add(classLoader.loadClass(entityClassName));
    }
 
    setAnnotatedClasses(
      annotatedClasses.toArray(
        new Class[annotatedClasses.size()]));
    recreateSessionFactory();
  }

  private void removeBundle(HibernateBundle bundle) throws Exception {
    for (Class<?> annotatedClass: annotatedClasses) {
      if (bundle.getEntityClassNames().contains(
        annotatedClass.getCanonicalName())) {
        annotatedClasses.remove(annotatedClass);
      }
    }
 
    setAnnotatedClasses(
      annotatedClasses.toArray(
        new Class[annotatedClasses.size()]));
    recreateSessionFactory();
  }
}

Основной план выполнения данного кода следующий. AbstractSessionFactoryBean реализует интерфейс FactoryBean<SessionFactory>, обработка создания объекта класса реализующего этот интерфейс в Spring контейнере происходит особым образом. Более подробно об этом смотрите документацию Spring Framework. Здесь стоит упомянуть лишь то, что это просто фабрика объектов SessionFactory, а не конкретная его реализация. Вместо объекта SessionFactory создаваемого в LocalSessionFactoryBean код возвращает прокси объект который переадресует практически все вызовы к родным методам SessionFactory при этом защищая любые обращения к нему глобальной блокировкой (данную архитектуру можно переработать с точки зрения проиводительности, в добавок не учитывается возможность пересоздание SessionFactory в момент использования объектов Session). При каждом изменении множества расширяемых модулей происходит пересоздания объекта SessionFactory c учётом этого.

В обработчике вызова метода прокси объекта используется переопределение Thread Context Classloader (TCCL). В качестве TCCL на время вызова метода устанавливает наш собственный Classloader речь о котором пойдёт далее.

OSGi и ClassLoader

В обычном приложении, выполняющемся в среде jvm, загрузчики классов иерархически связаны (ссылка на родителя) и загружают классы и ресурсы сначала делегированием к своему родителю и в случае неуспеха уже самостоятельно. Для OSGi среды такое поведения не подходит. Аналогично оно не подходит для нашего случая с Extender. В OSGi контейнере каждый модуль получает свой собственный загрузчик классов который полностью обеспечивает его (модуля) потребности. И загрузчики классов модулей соединены в более сложную сеть делегирования. Каждый такой загрузчик имеет ссылку на загрузчик класса фреймворка, а также на загрузчики модулей из которых осуществляется импорт. Данная сеть делегирования строится в результате процесса Resolving в OSGi-контейнере.

Сеть делегирования загрузчиков классов в OSGi

В случае с Extender типовой загрузчик классов модуля не годится, потому что отсутствует возможность объявления импортируемых классов сущностей в MANIFEST.MF. О таких классах просто неизвестно на момент разработки Extender. Следовательно необходимо реализовать свой собственный загрузчик с нужным нам поведением. Код загрузчика классов для Extender представлен ниже.

public class ExtenderClassLoader extends ClassLoader {

  private volatile ClassLoader defaultClassLoader = null;
  private Set<ExtendedBundle> bundles =
    new CopyOnWriteArraySet<ExtendedBundle>();

  public ClassLoader getDefaultClassLoader() {
    return defaultClassLoader;
  }

  public void setDefaultClassLoader(
    ClassLoader defaultClassLoader) {
    this.defaultClassLoader = defaultClassLoader;
  }

  public void addBundle(
    ExtendedBundle bundle) {
    bundles.add(bundle);
  }

  public void removeBundle(ExtendedBundle bundle) {
    bundles.remove(bundle);
  }

  @Override
  public Class<?> loadClass(
    String name) throws ClassNotFoundException {
    Class<?> c = null;
    for (ExtendedBundle bundle: bundles) {
      try {
        c = bundle.loadClass(name);
        break;
      }
      catch (ClassNotFoundException e) {}
    }
 
    if (c == null && getDefaultClassLoader() != null) {
      try {
        c = getDefaultClassLoader().loadClass(name);
      }
      catch (ClassNotFoundException e) {}
    }
 
    if (c == null) throw new ClassNotFoundException(name);
   
    return c;
  }
  // прочие методы
}

Давайте подробно рассмотрим представленный код. Основная логика его работы находится в методе loadClass. Нужно подчеркнуть, что аналогично реализованы методы по загрузке ресурсов которые опущены в данном фрагменте. ExtenderClassLoader содержит коллекцию модулей содержащих классы сущностей и делегирует загрузку классов и ресурсов им. Это обеспечивает доступ к необходимому коду без нужды в импорте. Загрузку остальных нужных классов ExtenderClassLoader делегирует загрузчику установленному по-умолчанию. Потокобезопасность данного кода обеспечивается за счёт использования неблокируемой реализации множества CopyOnWriteArraySet и потокобезопасных загрузчиков классов.

Собираем всё вместе

К этому моменту у читателя должно сформироваться понимание работы основных элементов Hibernate-Extender. Теперь необходимо собрать все части воедино. Для этого необходимо сконфигурировать модуль для работы в OSGi-среде со spring dm. Для лучшего понимание конфигурирования объектов рассмотрим диаграмму классов нашей системы.

UML-диаграмма классов

Согласно устоявшимся канонам конфигурация модуля spring dm разбивается как минимум на два файла. В первом находится собственно конфигурация spring-контейнера. Во втором расположена конфигурация специфичная для OSGi-окружения. В нашем случае это файлы context.xml и osgi-context.xml.

Конфигурация Hibernate-Extender для spring dm идентична диаграмме классов. В начале конфигурационного файла context.xml описан объект dataSource - это источник данных с помощью которого осуществляется всё взаимодействие с базой данных. Следом идёт конфигурация объекта transactionManager обеспечивающего поддержку транзакций в Spring окружении. Объект sessionFactory это инстанс описанного выше класса HibernateSessionFactoryBean который отвечает за создание SessionFactory. Ссылка на sessionFactory передаётся в transactionManager. Очередной объект в конфигурации это extenderClassLoader в котором при создании в качестве defaultClassLoader устанавливается загрузчик классов по умолчанию в Spring окружении. Объект sessionFactoryCreator выполняет всю основную работу по инициированию переконфигурирования окружения Hibernate с учётом изменившихся данных о модулях. Центральным объектом нашего модуля является Extender который в процессе создания получает ссылки на объекты: bundleContext, новый безымянный Executor, новый безымянный ExtendedBundleFactory и sessionFactoryCreator. Во втором конфигурационном файле osgi-context.xml объявлены сервисы OSGi: dataSource, transactionManager, sessionFactory.

context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:osgi="http://www.springframework.org/schema/osgi"
  xmlns:osgi-compendium="http://www.springframework.org/schema/osgi-compendium"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    scope="singleton">
    <property name="driverClass" value="org.hsqldb.jdbc.JDBCDriver" />
    <property name="jdbcUrl" value="jdbc:hsqldb:hsql://localhost/test" />
    <property name="user" value="SA" />
    <property name="password" value="" />
  </bean>

  <bean id="transactionManager"
    class="org.springframework.orm.hibernate3.HibernateTransactionManager"
    scope="singleton">
    <property name="sessionFactory" ref="sessionFactory" />
  </bean>

  <bean id="sessionFactory"
    class="iconcerto.hibernate.HibernateSessionFactoryBean"      
    scope="singleton">
    <property name="dataSource" ref="dataSource" />
    <property name="configurationClass"
       value="org.hibernate.cfg.AnnotationConfiguration" />  
    <property name="classLoader" ref="extenderClassLoader" />
  </bean>

  <bean id="extenderClassLoader"
    class="iconcerto.extender.ExtenderClassLoader"
    scope="singleton">
    <property name="defaultClassLoader">
      <bean class="org.springframework.util.ClassUtils"
       factory-method="getDefaultClassLoader"/>
    </property>
  </bean>

  <bean id="sessionFactoryCreator"
    class="iconcerto.hibernate.extender.SessionFactoryCreator"
    scope="singleton">
    <property name="extenderClassLoader" ref="extenderClassLoader" />
    <property name="sessionFactory" ref="sessionFactory" />
  </bean>

  <bean id="extender"
    class="iconcerto.extender.Extender"
    scope="singleton"
    init-method="initialize">
    <property name="bundleContext" ref="bundleContext" />
    <property name="executor">
      <bean class="java.util.concurrent.Executors"
       factory-method="newSingleThreadExecutor" />
    </property>
    <property name="extendedBundleFactory">
      <bean class="iconcerto.hibernate.extender.HibernateBundleFactory"/>
    </property>
    <property name="bundleConsumer" ref="sessionFactoryCreator" />
  </bean>

</beans>

osgi-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:osgi="http://www.springframework.org/schema/osgi"
  xmlns:osgi-compendium="http://www.springframework.org/schema/osgi-compendium"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/osgi
  http://www.springframework.org/schema/osgi/spring-osgi-1.2.xsd
  http://www.springframework.org/schema/osgi-compendium
  http://www.springframework.org/schema/osgi-compendium/spring-osgi-compendium-1.2.xsd">

  <osgi:service ref="dataSource" interface="javax.sql.DataSource" />

  <osgi:service ref="transactionManager">
    <osgi:interfaces>
      <value>
       org.springframework.transaction.support.ResourceTransactionManager
      </value>
    </osgi:interfaces>  
  </osgi:service>

  <osgi:service ref="sessionFactory"
    interface="org.hibernate.SessionFactory" />

</beans>

Недоработки прототипа

Как и любой прототип Hibernate-Extender обладает рядом недоработок. С одной стороны трудно донести основную идею если она смешана с большим количеством второстепенных деталей. С другой, нельзя не упомянуть совсем явные недостатки, которые препятствуют использованию данного кода в реальном приложении.

Из spring конфигурации первое, что бросается в глаза - наличие установки свойств источника данных прямо в самом файле context.xml. Любое реальное приложение требует изменение этого. Например установка этих данных из внешнего файла свойств используя Spring-механизм property-placeholder или реализация более сложной системы обеспечивающей изменения параметров подключения к базе данных во время выполнения.

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

Описанная реализация обладает следующим серьёзным недостатком. Что произойдёт если будет добавлен модуль на расширение в момент, когда другой, уже расширенный модуль, будет выполнять какую-либо операцию используя Hibernate API и находится внутри транзакции? Такая операция  при текущей реализации обречена на провал, хотя если это учитано в коде, то неудобство вызовет только обработка специфических исключений, связанных с вызовом операций на уже не актуальном объекте Session.

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

OSGi-фикация сторонних библиотек

Много java библиотек уже поставляются в виде готовом для выполнения в среде OSGi, но к сожалению, не все. Выхода из этого положения два, либо использовать OSGi-фицированные версии подготовленные третьей стороной (например SpringSource Enterprise Bundle Repository[http://ebr.springsource.com/repository/app/]), либо подготовить такие версии библиотек самостоятельно. В проекте с исходным кодом примеров выполняется такая подготовка (подпроект lib). Хочу заметить, что для сложной библиотеки это отнюдь не тривиальная задача. Необходимо понимать структуру библиотеки для экспорта пакетов которые могут быть использованы сторонним кодом. Дополнительные проблемы могут возникнуть из-за внутренних механизмов библиотек плохо совместимых или несовместимых с OSGi-средой. Но всё это, в большинстве случаев, решаемые задачи. Технические проблемы архитектуры библиотек решаются только вручную, тогда как вопрос экспорта поддаётся автоматизации с помощью различных утилит (bnd, maven-плагины и др.)

Заключение

На этом описание Extender-подхода работы с Hibernate в OSGi завершается. Единственное, что осталось - это добавить каким образом можно использовать полученный модуль. Расширяемый модуль в заголовке Entity-Classes в META-INF/MANIFEST.MF должен поместить список полных имён класов сущностей Hibernate. Далее при запуске этого модуля Extender загрузит эти классы с помощью нашей реализации ClassLoader и переинициализирует runtime-окружение Hibernate. Кроме того, модуль нуждающийся в использовании Hibernate API должен импортировать OSGi сервис SessionFactory и ResourceTransactionManager. После этого модуль может использовать Hibernate API в Spring окружении в привычной манере (согласно официальному руководству).
Полный код Extender совместно с модульными и интеграционными тестами размещен в googlecode репозитории ссылка на который размещена в конце статьи. Этот код будет развиваться, поэтому все кто заинтересован в нём могут следить за изменениями. Кто же имеет желание поучаствовать в развитии этого кода может связаться со мной. Ну и как обычно, у кого возникли какие-то вопросы добро пожаловать в комментарии!

Дополнительные источники

http://code.google.com/p/iconcerto/source/browse/
http://www.aqute.biz/Bnd/Bnd
http://www.aqute.biz/Snippets/Extender
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/
http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/
http://static.springsource.org/osgi/docs/1.2.1/reference/html/

Комментариев нет:

Отправить комментарий