понедельник, 25 января 2010 г.

Расширяем ClassPath бандла с помощью фрагментов (на примере логирования)


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

Давайте рассмотрим следующую ситуацию: мы хотим использовать в нашем OSGi-приложении логирование с помощью log4j. Ситуация, кстати, не такая уж и редкая. В проект Orbit входит бандл org.apache.log4j, который инкапсулирует данную библиотеку и экспортирует ее packages. По идее, достаточно добавить данный бандл как зависимость (через Require-Bundle или Import-Package) и наслаждаться жизнью, но не тут то было. Дело в том, что log4j нужно конфигурировать, т.е. в корне classpath должен находиться файл log4j.properties, с примерно таким содержимым:


log4j.rootLogger=warn, stdout, file



### direct log messages to stdout ###

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n



### direct messages to file test.log ###

log4j.appender.file=org.apache.log4j.FileAppender

log4j.appender.file.File=test.log

log4j.appender.file.Append=true

log4j.appender.file.layout=org.apache.log4j.PatternLayout

log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n



### icqlib logging

log4j.logger.name.samolisov=debug

 


Проблема в следующем: бандл org.apache.log4j ничего не знает о наших бандлах, соответственно, импортировать содержимое нашего classpath (т.е. log4j.properties) он не может.

К счастью, в OSGi существует такое понятие, как фрагментный бандл (или, для краткости, - фрагмент). От обычного бандла данная сущность отличается наличием в манифесте строчки Fragment-Host: с указанием названия и диапазона версий хостового (основного) бандла. Особенность фрагмента заключается в том, что он не может находиться в состоянии Active (и, соответственно, не может иметь активатора) и не имеет отдельного classpath, а расширяет classpath хостового бандла. Т.е., чтобы было понятнее, у хостового бандла и всех его фрагментов classpath общий.

Давайте рассмотрим пример. Создадим обычный бандл name.samolisov.log4j.test. Код его активатора следующий:

package name.samolisov.log4j.test;



import org.apache.log4j.Logger;

import org.osgi.framework.BundleActivator;

import org.osgi.framework.BundleContext;



public class Activator implements BundleActivator {



    private static Logger logger = Logger.getLogger(Activator.class);



    /*

     * (non-Javadoc)

     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)

     */


    public void start(BundleContext context) throws Exception {

        logger.info("bundle's activator has been started");

    }



    /*

     * (non-Javadoc)

     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)

     */


    public void stop(BundleContext context) throws Exception {

        logger.info("bundle's activator has been stoped");

    }

}

 


Естественно, данный бандл зависит от org.apache.log4j, поэтому его манифест содержит следующие строки:

Import-Package: org.osgi.framework;version="1.3.0"

Require-Bundle: org.apache.log4j;bundle-version="1.2.13"

 


Затем создадим фрагмент name.samolisov.log4j.connector. Для создания фрагменов Eclipse PDE предоставляет отдельный визард, вызвать который можно через New -> Project -> Plug-in Development -> Fragment Project. Отличается от обычного он тем, что содержит поле, позволяющее указать значение параметра Fragment-Host: .

В каталог src данного бандла положим файл конфигурации log4j.properties. Но! Этого недостаточно. Чтобы даный файл попал в бинарную сборку плагина, необходимо отметить его галочкой в окошке Binary Build на вкладке Build дизайнера манифеста.

Т.е. файл build.properties будет выглядеть следующим образом:

source.. = src/

output.. = bin/

bin.includes = META-INF/,\

               .,\

               src/log4j.properties

 


Манифест фрагмента приведу полностью:

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Connector

Bundle-SymbolicName: name.samolisov.log4j.connector

Bundle-Version: 1.0.0.qualifier

Bundle-Vendor: Pavel Samolisov

Fragment-Host: org.apache.log4j;bundle-version="1.2.13"

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

 


Фрагменты могут расширять classpath не только собственным содержимым, но и за счет определения в них необходимых импортов. Например, наше приложение использует в качестве логера бандл org.apache.commons.logging и мы хотим, чтобы он делегировал логирование log4j. Как сделать это применительно к Spring я уже писал. В данном случае, проблема заключается в том, что бандл org.apache.commons.logging не импортирует никакие packages бандла org.apache.log4j. Решить данную проблему можно с помощью фрагмента с таким манифестом:

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: CLConnector

Bundle-SymbolicName: name.samolisov.commons_logging.connector

Bundle-Version: 1.0.0.qualifier

Bundle-Vendor: Pavel Samolisov

Fragment-Host: org.apache.commons.logging;bundle-version="1.0.4"

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

Import-Package: org.apache.log4j;version="1.2.13",

 org.apache.log4j.config;version="1.2.13",

 org.apache.log4j.helpers;version="1.2.13"

 


(естественно, при необходимости можно импортировать и другие packages).

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

Понравилось сообщение - подпишитесь на блог или читайте меня в twitter

11 комментариев:

Dieu комментирует...

а реальное применение какое?
так как логгирование есть и перекомпиленное под осги.

ЗЫ: кстати кто знает быстрое логгирование? и с которым не проседает скорость (если логгирование выключено)?

Pavel Samolisov комментирует...

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

Andrey Rybin комментирует...

>кто знает быстрое логгирование?

http://logback.qos.ch/

хорош (от автора log4j),
но работает ли он с OSGi и как я не знаю

Pavel Samolisov комментирует...

Спасибо!

Evgeni комментирует...

а как использовать в бандле зависимости в виде jar librarys?(те если моему бандлу нужны jar).
thanks!

Pavel Samolisov комментирует...

Вам нужно положить эти jar-ники в отдельный каталог внутри бандла, например lib и перечислить их в параметре Bundle-Classpath манифеста бандла, например так:
Bundle-ClassPath: lib/axis.jar,
lib/commons-discovery-0.2.jar,
lib/commons-logging-1.0.4.jar,
lib/jaxrpc.jar,
lib/saaj.jar,
lib/wsdl4j-1.5.1.jar,
.

Точка в конце обозначает собственные классы бандла.

Evgeni комментирует...

спасибо, а что делать с config/properties files?
тоже самое

Pavel Samolisov комментирует...

Если они просто лежат в каталогах вашего бандла, то они по-умолчанию попадут в classpath, если же они лежат внутри какого-то jar-а, то перечислите этот jar.

evgeny_k комментирует...

как я могу к своему бандлу подсоеденить сторонний, которий я скачал из репозитория spring

Pavel Samolisov комментирует...

Чтобы ответить на этот вопрос нужно знать что вы имеете ввиду под словом "присоединить".

evgeny_k комментирует...

в своем проекте я использую сторонние библиотеки. с однои из них (c3p0 pool) происходит проблема. я скачал ее как бандл из spring repository и хочу попробовать исползовать подругому(вдруг поможет :( )

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

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!