您的位置:首页 > 其它

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

2012-03-23 23:03 399 查看
本文介绍开发一个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
#framework的StartLevel
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()) {
//确保bundle的startLevel在自动启动的范围内
lvl= osgiConfig.getFrameworkStartLevel();

}

}

else {

if (lvl <= osgiConfig.getFrameworkStartLevel()) {
//设置bundle的startLevel大于framework的startLevel以禁止自动启动
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的研究和探索》等文章,在此致谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: