適用於多種OSGi框架的WebConsole與OSGi嵌入到Web應用的實現

本文介紹開發一個web console以管理OSGi框架及bundles的實現方法,可適用於實現了OSGi規範的Equinox、Felix等開源框架。並介紹瞭如何把OSGi框架作爲一個組件嵌入到現有的未基於OSGi開發的Web應用當中,在Web應用中可獲取OSGi中的Service以增加應用的靈活性。

本文適用於具有OSGi基本知識的人員閱讀。

本例所述源代碼在http://download.csdn.net/detail/tsun7263/4167550http://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的研究和探索》等文章,在此致謝。

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章