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.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
运行情况截图:
示例源码Maven工程请查看附件(Felix发布包请自行解压并配置好对应目录根路径)