WEB容器托管OSGi容器(轻量级集成方式)

        OSGi是JAVA动态模块化的标准,使用OSGi构建面向模块、可重用、可热插拔服务是大家都想追求的,但实际采用OSGi作为系统主骨骼框架时却发现理想总是那么丰满,现实总那么骨感,究其原因,总结成以下几点:第一、采用OGGi架构对架构师的要求非常高,针对项目需求设计重用性、扩展性、耦合性良好的功能模块划分不是一件容易的事情,特别是项目需求经常变更的时候,简直就是噩梦;第二,OSGi本身只是一个动态模块化标准,缺少对JAVA EE企业级应用特性的完善支持,比如企业级事务、ORM等特性;第三、OSGi不是一个Web App容器,想要在OSGi架构下提供Web服务需要第三方WEB容器支持,有多种方法并且各有优缺点,这是本文重点讨论的话题。
        方式一,采用OSGi托管WEB容器,例如,Jetty就提供了针对OSGi的bundle运行环境,这时如果把WEB APP以OSGi Bundle形式部署,就能够实现WEB服务的发布,又可以利用OSGi的动态模块化特性,这是一种推荐的集成方式;方式二,WEB容器托管OSGi容器,这种方式主要针对传统的JAVA WEB应用,但是又想要提供一些额外的可插拔热部署的服务时,可以采用的一种轻量级集成方式;分析两种方式,显而易见,第一种方式更优雅更OSGi,但缺点是完全侵入式,要求整个系统进行模块化架构,第二种方式最大的问题是,WEB容器跟OSGi的Bundle环境如何良好双向复用,它的好处是侵入性小可比较方便的继续利用现有的JAVA EE框架企业级特性。但就像谈恋爱一样,优雅的美丽的不一定是适合的,需要根据实际场景做分析。接下来,我详细介绍一下我在工作环境中使用的第二种集成方式实现。

        主要实现原理时,WEB容器启动加载FelixFrameworkLauncher监听器,FelixFrameworkLauncher监听器完成Felix容器的启动并且使用BundleContextHolder托管最最核心的BundleContext,同时设置到ServletContext上下文,通过BundleContex就可以在WEB容器中通过反射调用OSGi对外提供的Bundle服务。

       web应用目录结构:

WEB容器托管OSGi容器(轻量级集成方式)WEB容器托管OSGi容器(轻量级集成方式)

WEB容器托管OSGi容器(轻量级集成方式)
 

        web.xml配置如下:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
	<display-name>Archetype Created Web Application</display-name>

	<context-param>
		<param-name>felix.config.properties</param-name>
		<param-value>/WEB-INF/conf/config.properties</param-value>
	</context-param>
	
	<listener>
		<listener-class>
			cn.longstudio.FelixFrameworkLauncher
		</listener-class>
	</listener>
</web-app>

        FelixFrameworkLauncher类的源码如下:

package cn.longstudio;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Properties;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.felix.framework.FrameworkFactory;
import org.apache.felix.framework.util.Util;
import org.apache.felix.main.AutoProcessor;
import org.osgi.framework.BundleContext;
import org.osgi.framework.launch.Framework;

import cn.longstudio.BundleContextHolder;

/**
 * <p>
 * Felix容器启动器
 * </p>
 * 
 * @version 1.0 2013-1-9
 * @author hewl
 * 
 */
public class FelixFrameworkLauncher implements ServletContextListener
{
    private static Framework m_fwk = null;

    public void contextDestroyed(ServletContextEvent event)
    {
        try
        {
            m_fwk.stop();
            m_fwk.waitForStop(0);
        }
        catch (Exception ex)
        {
        	throw new RuntimeException("Could not stop felix framework: " + ex);
        }

    }

    public void contextInitialized(ServletContextEvent event)
    {
        try
        {
            String configPropsFileValue = event.getServletContext()
                    .getInitParameter("felix.config.properties");
            // Read the properties file.
            Properties configProps = new Properties();
            InputStream is = null;
            try
            {
                // Try to load config.properties.
                is = event.getServletContext().getResourceAsStream(configPropsFileValue);
                configProps.load(is);
                is.close();
            }
            catch (Exception ex)
            {
                // Try to close input stream if we have one.
                try
                {
                    if (is != null)
                        is.close();
                }
                catch (IOException ex2)
                {
                    // Nothing we can do.
                }
                return;
            }
            // Perform variable substitution for system properties.
            for (Enumeration e = configProps.propertyNames(); e.hasMoreElements(); )
            {
                String name = (String) e.nextElement();
                configProps.setProperty(name,
                    Util.substVars(configProps.getProperty(name), name, null, configProps));
            }
            m_fwk = getFrameworkFactory().newFramework(configProps);
            m_fwk.init();
            AutoProcessor.process(configProps, m_fwk.getBundleContext());
            m_fwk.start();
            //set the BundleContext as a servlet context attribute
            event.getServletContext().setAttribute(BundleContext.class.getName(), m_fwk.getBundleContext());
            BundleContextHolder.setBundleContext(m_fwk.getBundleContext());
        }
        catch (Exception ex)
        {
        	throw new RuntimeException("Could not create felix framework: " + ex);
        }
    }

    private FrameworkFactory getFrameworkFactory() throws Exception
    {
        return new FrameworkFactory();
    }
    
}

  

BundleContextHolder的源码如下:

package cn.longstudio;

import org.osgi.framework.BundleContext;

/**
 * <p> 
 *  OSGI BundleContext存放器
 * </p>
 *
 * @version 1.0		2013-1-24
 * @author  hewl
 *
 */
public class BundleContextHolder
{
    private static BundleContext bundleContext;

    public static BundleContext getBundleContext()
    {
        return bundleContext;
    }

    public static void setBundleContext(BundleContext bundleContext)
    {
        BundleContextHolder.bundleContext = bundleContext;
    }
    
}

 
        

 config.properties文件内容如下:

#
# Framework config properties.
#
#Felix默认工作路径为当前工作路径,即系统变量user.dir的值
#当使用Web容器接管OSGI时,最好直接设置为绝对路径
Felix.Framework.Path=E:/felix-framework
org.osgi.framework.system.packages.extra=javax.servlet;javax.servlet.http;version=2.5
org.osgi.framework.bootdelegation=org.w3c.*,javax.xml.*
org.osgi.framework.bundle.parent=framework
felix.cache.rootdir=${Felix.Framework.Path}
org.osgi.framework.storage.clean=onFirstInit
felix.auto.deploy.action=install,start
felix.auto.deploy.dir=${Felix.Framework.Path}/bundle
felix.log.level=1
manager.root=/felix/console
obr.repository.url=http://felix.apache.org/obr/releases.xml

运行情况截图:

WEB容器托管OSGi容器(轻量级集成方式)WEB容器托管OSGi容器(轻量级集成方式)

WEB容器托管OSGi容器(轻量级集成方式)
 
 

示例源码Maven工程请查看附件(Felix发布包请自行解压并配置好对应目录根路径)