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.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发布包请自行解压并配置好对应目录根路径)