适用于多种OSGi框架的WebConsole与OSGi嵌入到Web应用的实现

来源:互联网 发布:怎么看淘宝旗舰店真假 编辑:程序博客网 时间:2024/04/28 02:12

本文介绍开发一个web console以管理OSGi框架及bundles的实现方法,可适用于实现了OSGi规范的Equinox、Felix等开源框架。并介绍了如何把OSGi框架作为一个组件嵌入到现有的未基于OSGi开发的Web应用当中,在Web应用中可获取OSGi中的Service以增加应用的灵活性。

本文适用于具有OSGi基本知识的人员阅读。

本例所述源代码在http://download.csdn.net/detail/tsun7263/4167550或http://ishare.iask.sina.com.cn/f/23642252.html下载。

 

1      利用OSGi 开源框架本身提供的方法启动OSGi Framework

启动OSGi Framework有多种方法,如Equinox中就包含启动OSGi Framework的方法,可在服务器端开启一个命令行窗口,用于对bundles进行操作。

启动Equinox的代码:

// 根据要加载的bundle组装出类似a.jar@start,b.jar@3:start这样格式的osgibundles字符串来

       StringosgiBundles="";

      

       //配置Equinox的启动

       FrameworkProperties.setProperty("osgi.noShutdown","true");

       FrameworkProperties.setProperty("eclipse.ignoreApp","true"); 

        FrameworkProperties.setProperty("osgi.bundles.defaultStartLevel","4"); 

        FrameworkProperties.setProperty("osgi.bundles", osgiBundles); 

        // 根据需要设置bundle所在的路径 

        String bundlePath=""

        // 指定要加载的plugins所在的目录 

        FrameworkProperties.setProperty("osgi.syspath", bundlePath); 

        // 调用EclipseStarter,完成容器的启动,指定configuration目录 

        try {

           EclipseStarter.run(new String[]{"-configuration","configuration","-console"},null);

       }catch (Exception e) {

           e.printStackTrace();

      

        // 通过EclipeStarter获得BundleContext 

        context = EclipseStarter.getSystemBundleContext();

 

停止Equinox的代码:

try

           EclipseStarter.shutdown(); 

           context=null;

           System.err.println("osgi exit");

       

       catch (Exception e) {

           System.err.println("停止equinox容器时出现错误:" + e); 

           e.printStackTrace(); 

        }

 

利用OSGi实现框架本身提供的方法启动OSGi Framework的方法需要针对特定的OSGi实现编写代码,不具有通用性。

2      通用的OSGi Framework启动方法

设计思路:

1、建立一个ServletContextListener,监听到应用启动时,启动OSGi Framework。

2、根据OSGi 4.2规范,实现jar中放置一个文件META-INF/services/org.osgi.framework.launch.FrameworkFactory,这个文件中设置了实际的FrameworkFactory实现类的类名。通过读取这个文件加载FrameworkFactory。

3、利用OSGi Framework中提供的API执行启动等各种操作。

这种实现方式的好处是代码具有通用性,底层所使用的具体OSGi实现框架对应用是透明的,可以无需修改代码和配置即可实现OSGi框架的替换。

2.1    OSGi配置文件osgi.properties

osgi.properties为如下格式的文件:

 

#bundles的默认存放路径

osgi.bundles.defaultPath=WEB-INF/bundles

#要加载bundles,根据要加载的bundle组装出类似a.jar@start,b.jar@3:start这样格式的osgibundles字符串来

osgi.bundles=dict-query.jar,local-dict-query.jar@6:start,remote-dict-query.jar@5:start

#bundles的默认StartLevel

osgi.bundles.defaultStartLevel=4

#frameworkStartLevel

osgi.startLevel=6

 

2.2      osgi.properties的读取

BundleConfig.java 用于存放osgi.bundles中单条数据,BundleConfig.java用于存放osgi.properties中数据。

2.3    ServletContextListener的实现类ContainerListener

package demo.osgi;

 

import java.io.InputStream;

import java.util.Properties;

 

import javax.servlet.ServletContextEvent;

import javax.servlet.ServletContextListener;

 

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

 

public class ContainerListener implements ServletContextListener{

      privatestatic final Log log = LogFactory.getLog(OSGiAdmin.class);

     

      @Override

      publicvoid contextInitialized(ServletContextEvent event) {

             try{

                    StringosgiConfigPath =event.getServletContext().getInitParameter("osgiConfig");

                    if(osgiConfigPath == null) osgiConfigPath = "/osgi.properties";

                    Propertiesprop = new Properties();

                    InputStreamis = ContainerListener.class.getResourceAsStream(osgiConfigPath);

                    prop.load(is);

                    is.close();

                    OSGiConfigosgiConfig = new OSGiConfig();

                    osgiConfig.load(prop,event.getServletContext());

                   

                   

                    OSGiAdmin.startup(osgiConfig,event.getServletContext());

             }catch (Exception e) {

                    log.error("启动OSGi框架失败:" +e.getMessage(), e);

             }

      }

 

      @Override

      publicvoid contextDestroyed(ServletContextEvent event) {

             try{

                    OSGiAdmin.shutdown();

             }catch (Exception e) {

                    log.error("卸载OSGi框架失败:" +e.getMessage(), e);

             }

      }

 

}

 

2.4    web.xml中的设置

在web.xml中增加以下内容:

<context-param> 

       <param-name>osgiConfig</param-name> 

       <param-value>/osgi.properties</param-value> 

    </context-param>

    <listener>

        <listener-class>demo.osgi.ContainerListener</listener-class>

    </listener>

 

2.5    OSGiAdmin.java实现OSGiFramework的启动和停止

package demo.osgi;

 

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.util.ArrayList;

import java.util.Dictionary;

import java.util.HashMap;

import java.util.Hashtable;

import java.util.List;

import java.util.Map;

 

import javax.servlet.ServletContext;

 

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

 

import org.osgi.framework.Bundle;

import org.osgi.framework.BundleContext;

import org.osgi.framework.BundleException;

import org.osgi.framework.Constants;

import org.osgi.framework.ServiceReference;

import org.osgi.framework.Version;

import org.osgi.framework.launch.Framework;

import org.osgi.framework.launch.FrameworkFactory;

import org.osgi.framework.startlevel.BundleStartLevel;

import org.osgi.framework.startlevel.FrameworkStartLevel;

 

import com.googlecode.transloader.DefaultTransloader;

import com.googlecode.transloader.ObjectWrapper;

import com.googlecode.transloader.Transloader;

import com.googlecode.transloader.clone.CloningStrategy;

 

publicclassOSGiAdmin {

    privatestaticfinal Log log = LogFactory.getLog(OSGiAdmin.class);

   

    privatestatic OSGiConfig osgiConfig;

   

    privatestatic Framework framework =null;

   

    publicstaticvoid startup(OSGiConfig osgiConfig,ServletContext servletContext) throws Exception {

       if (log.isInfoEnabled())log.info("正在启动OSGi框架...");

      

       OSGiAdmin.osgiConfig = osgiConfig;

      

       //加载FrameworkFactory.class

       Class<FrameworkFactory>frameworkFactoryClass =loadClass(FrameworkFactory.class);

      

       if (frameworkFactoryClass ==null)thrownew Exception("加载classFrameworkFactory失败");

      

       FrameworkFactoryframeworkFactory = (FrameworkFactory)frameworkFactoryClass.newInstance();

       Map<String,String> cofiguration =newHashMap<String, String>();

       cofiguration.put(Constants.FRAMEWORK_BEGINNING_STARTLEVEL, String.valueOf(osgiConfig.getFrameworkStartLevel()));

       framework =frameworkFactory.newFramework(cofiguration);

      

       framework.init();

       FrameworkStartLevelframeworkStartLevel =framework.adapt(FrameworkStartLevel.class);

        frameworkStartLevel.setInitialBundleStartLevel(osgiConfig.getBundlesDefaultStartLevel());

       log.info("osgi bundles默认启动级别:" + osgiConfig.getBundlesDefaultStartLevel());

       log.info("osgi framework启动级别:" + osgiConfig.getFrameworkStartLevel());

      

      

       initFramework(framework, servletContext);

      

       List<BundleConfig>bundleConfigs = osgiConfig.getBundleConfigs();

       for (BundleConfig bundleConfig : bundleConfigs) {

           try {

              log.info("装载bundle " + bundleConfig.getLocationUrl() +" ...");

              Bundlebundle = installBundle(bundleConfig.getLocationUrl());

              BundleStartLevelbundleStartLevel = bundle.adapt(BundleStartLevel.class);

              int lvl = bundleConfig.getStartLevel();

              if (bundleConfig.isAutoStart()

                    &&(bundle.getHeaders().get(Constants.BUNDLE_ACTIVATOR) !=null || bundle.getHeaders().get("Service-Component") !=null)) {

                 

                  if (lvl > osgiConfig.getFrameworkStartLevel()) {

                    //确保bundlestartLevel在自动启动的范围内

                    lvl= osgiConfig.getFrameworkStartLevel();

                  }

              }

              else {

                  if (lvl <= osgiConfig.getFrameworkStartLevel()) {

                    //设置bundlestartLevel大于frameworkstartLevel以禁止自动启动

                    lvl= osgiConfig.getFrameworkStartLevel() + 1;

                  }

              }

              log.info("startLevel:" + lvl);

              bundleStartLevel.setStartLevel(lvl);

              bundle.start(Bundle.START_ACTIVATION_POLICY);

             

              log.info("装载bundle " + bundleConfig.getLocationUrl() +"成功");

           }catch (BundleException e) {

              log.info("装载bundle " + bundleConfig.getLocationUrl() +"失败:" +e.getMessage(), e);

           }

       }

      

       //启动Framework

       framework.start();

       log.info("osgi framework启动后的启动级别:" + frameworkStartLevel.getStartLevel());

      

       if (log.isInfoEnabled())log.info("启动OSGi框架成功");

    }

   

    @SuppressWarnings("unchecked")

    privatestatic <T> Class<T> loadClass(Class<T> clazz) throws IOException, ClassNotFoundException {

       ClassLoaderclassLoader = Thread.currentThread().getContextClassLoader();

       Stringresource ="META-INF/services/" +clazz.getName();

       InputStreamin  = classLoader.getResourceAsStream(resource);

       if (in ==null)returnnull ;

       try {

           BufferedReaderreader =new BufferedReader(new InputStreamReader(in));

           StringserviceClassName  =  reader.readLine();

           return(Class<T>)classLoader.loadClass(serviceClassName);

       }finally {

           in.close();

       }

    }

   

    publicstatic OSGiConfig getOSGiConfig() {

       returnOSGiAdmin.osgiConfig;

    }

   

   

    privatestaticvoid registerContext(BundleContextbundleContext, ServletContext servletContext) {

       Dictionary<String,String> properties =newHashtable<String, String>();

       properties.put("ServerInfo",servletContext.getServerInfo());

       properties.put("ServletContextName",servletContext.getServletContextName());

       properties.put("MajorVersion", String.valueOf(servletContext.getMajorVersion()));

       properties.put("MinorVersion", String.valueOf(servletContext.getMinorVersion()));

       bundleContext.registerService(ServletContext.class.getName(), servletContext, properties);

    }

   

    privatestaticvoid initFramework(Frameworkframework, ServletContext servletContext) throws IOException {

       BundleContextbundleContext  =  framework.getBundleContext();

        // ServletContext注册为服务

       registerContext(bundleContext,servletContext);

    }

   

   

    publicstaticvoid startup() throws Exception {

       framework.start();

    }

   

    publicstaticvoid shutdown() throws Exception {

       if (framework == null)return;

       if (log.isInfoEnabled())log.info("正在停止OSGi框架...");

      

       if (framework.getState() == Framework.ACTIVE)framework.stop();

       framework.waitForStop(0);

       //framework = null;

       log.info("OSGi框架已停止");

    }

   

    privatestatic BundleContext getBundleContext() {

       returnframework.getBundleContext();

    }

}

2.6    效果

把几个测试bundles放在WEB-INF/bundles下面。本例中是根据《OSGi 原理与最佳实践》中字典查询的示例编写的。

dict-query.jar封装了字典查询的接口

local-dict-query.jar是字典查询接口的本地实现

remote-dict-query.jar是字典查询的远程实现

启动应用服务器后,在后台看到如下效果:

 

INFO 2012-03-23 22:24:34,375 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:43) -正在启动OSGi框架...

INFO 2012-03-23 22:24:35,218 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:60) - osgi bundles 默认启动级别:4

INFO 2012-03-23 22:24:35,218 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:61) - osgi framework 启动级别:6

INFO 2012-03-23 22:24:35,234 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:69) -装载bundlefile:/E:/java/workspace1/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/osgiWebConsole/WEB-INF/bundles/dict-query.jar...

INFO 2012-03-23 22:24:35,234 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:87) - startLevel:7

INFO 2012-03-23 22:24:35,234 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:91) -装载bundlefile:/E:/java/workspace1/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/osgiWebConsole/WEB-INF/bundles/dict-query.jar成功

INFO 2012-03-23 22:24:35,234 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:69) -装载bundlefile:/E:/java/workspace1/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/osgiWebConsole/WEB-INF/bundles/remote-dict-query.jar...

INFO 2012-03-23 22:24:35,234 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:87) - startLevel:5

INFO 2012-03-23 22:24:35,234 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:91) -装载bundlefile:/E:/java/workspace1/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/osgiWebConsole/WEB-INF/bundles/remote-dict-query.jar成功

INFO 2012-03-23 22:24:35,234 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:69) -装载bundlefile:/E:/java/workspace1/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/osgiWebConsole/WEB-INF/bundles/local-dict-query.jar...

INFO 2012-03-23 22:24:35,234 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:87) - startLevel:6

INFO 2012-03-23 22:24:35,234 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:91) -装载bundlefile:/E:/java/workspace1/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/osgiWebConsole/WEB-INF/bundles/local-dict-query.jar成功

INFO 2012-03-23 22:24:35,328 demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:99) - osgi framework 启动后的启动级别:6

INFO  2012-03-23 22:24:35,328demo.osgi.OSGiAdmin.startup(OSGiAdmin.java:101) -启动OSGi框架成功

 

3       OSGi Web Console的实现

在OSGiAdmin.java中提供对Framewo、Bundle的操作,从Web页面上调用。

以下对Bundle操作的代码:

    publicstaticvoid startBundle(long id)throws BundleException {

       Bundlebundle =getBundle(id);

       if (bundle ==null)return;

       if (bundle.getState() != Bundle.UNINSTALLED && bundle.getState() != Bundle.ACTIVE) {

           bundle.start();

       }

    }

   

    publicstaticvoid stopBundle(long id)throws BundleException {

    Bundle bundle =getBundle(id);

       if (bundle ==null)return;

       if (bundle.getState() == Bundle.ACTIVE) {

           bundle.stop();

       }

    }

   

    publicstatic Bundle installBundle(String location) throws BundleException {

       BundleContextbundleContext =getBundleContext();

       Bundlebundle = bundleContext.installBundle(location);

       return bundle;

    }

   

    publicstaticvoid uninstallBundle(long id)throws BundleException {

    Bundle bundle =getBundle(id);

       if (bundle ==null)return;

       if (bundle.getState() == Bundle.UNINSTALLED)return;

       if (bundle.getState() == Bundle.ACTIVE) {

           bundle.stop();

       }

       bundle.uninstall();

    }

 

在Web页面控制OSGi Framework和Bundles,在osgi_console.jsp页面进行查看和操作,效果如下:


 

4      OSGi嵌入到Web应用的实现

对于既有的Web应用改造成纯OSGi应用可能会是一件耗时的工作,需要进行模块的划分等改造工作。在既有的Web应用中嵌入OSGi框架是一种可选的方法,把OSGi框架作为服务的提供者,Web应用调用服务完成业务逻辑的运算。

在OSGiAdmin.java中加入获取service的接口:

publicstatic ObjectgetService(Class<?> clazz){ 

       ServiceReference<?> serviceRef =context.getServiceReference(clazz);

       Object obj =null;

       if(serviceRef !=null) {

        obj =context.getService(serviceRef);

           context.ungetService(serviceRef);

       }

       return obj; 

    }

 

在web客户端进行调用,如dict_query.jsp中的调用:

Object obj =OSGiAdmin.getService(QueryService.class);

    if (obj !=null) {

         QueryServicqueryServic = (QueryServic)obj;

       Stringvalue = queryService.queryWord(key);

       if (value ==null) value="";

       out.println(value);

    }

    else {

       out.println("服务不存在");

    }

 

需要把dict-query.jar同时放入WEB-INF/lib下。运行时出现ClassCastException,这是什么原因呢?

了解OSGi的人应该知道,OSGi有自己的类加载方式,形成OSGi环境;而Web应用是使用的应用服务器的类加载方式,是非OSGi环境。Web应用中的demo.osgi.dictquery.QueryService是由应用服务器的ClassLoader加载的,OSGi框架中的demo.osgi.dictquery.QueryService是由OSGi的ClassLoader加载的,虽然类的名称是一样的,但因为是有不同ClassLoader加载的,在jvm中认为这是两个不同的Class,所以会出现ClassCastException。

解决方法有:

(1)在Web应用的客户端不使用强类型,仅得到服务的Object类型的实例,通过反射进行调用。

(2)把OSGi中Object复制到非OSGi环境中,这样就可以使用服务接口的强类型进行引用,但要求客户端能够加载服务接口的Class。

此处介绍一下TransLoader开源框架(http://code.google.com/p/transloader/)。它可用于ClassLoader间对象的复制、通过反射调用方法,最初是为OSGi环境与非OSGi环境通信而设计,但也适用于其他ClassLoader之间传输对象的情景。TransLoader开源框架可实现对象的深度复制、封装实例的最少复制。

TransLoader开源框架可以解决我们遇到的这个问题,对OSGiAdmin.java的getService方法改造如下:

    publicstatic <T> T getService(Class<T>clazz) {

    T result =null;

    BundleContext bundleContext =getBundleContext();

    ServiceReference<?> serviceRef =bundleContext.getServiceReference(clazz.getName());

 

    if (null != serviceRef) {

        Object resultObj =bundleContext.getService(serviceRef);

        if (resultObj !=null) {

            Transloader transloader =getTransloader();

            ObjectWrapper resultWrapped =transloader.wrap(resultObj);

                if (resultWrapped.isInstanceOf(clazz.getName())) {

                  result = (T)resultWrapped.makeCastableTo(clazz);

                }

        }

    }

   

    if (serviceRef !=null)bundleContext.ungetService(serviceRef);

   

    return result;

    }

 

Web应用的客户端代码修改为:

QueryService queryService =OSGiAdmin.getService(QueryService.class);

    if (queryService !=null) {

       Stringvalue = queryService.queryWord(key);

       if (value ==null) value="";

       out.println(value);

    }

    else {

       out.println("服务不存在");

    }

 

启动应用服务器后,在dict_query.jsp页面输入“temp”进行查询,显示如下:


在OSGi页面停止bundle remote-dict-query.jar,dict_query.jsp页面显示为:


这样就实现了bundle的热插拔,在不重启应用服务器的情况下实现服务的切换。

 

在本示例基础上,可以增加bundle的客户端上传等功能,实现更加实用的功能。

本文档参考了网络上《打造一个基于OSGi的WebApplication——在WebApplication中启动OSGi》、《从外部启动Equinox》、《基于 Equinox 的 OSGi Console的研究和探索》等文章,在此致谢。

 


原创粉丝点击