适用于多种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
#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的研究和探索》等文章,在此致谢。
- 适用于多种OSGi框架的WebConsole与OSGi嵌入到Web应用的实现
- WEB.Http服务器嵌入到OSGI框架
- 【OSGi】OSGi框架的三个层次
- 【OSGi】OSGi框架的三个层次
- osgi框架的结构
- OSGI框架嵌入Http服务器的环境配
- OSGi 框架嵌入 Http 服务器的运行环境配置
- OSGi介绍及OSGi的Bundle应用
- OSGi介绍及OSGi的Bundle应用
- OSGi规范的实现
- Osgi的简单实现
- OSGI框架嵌入Http服务器
- Http服务器嵌入到OSGI框架环境配置
- OSGI框架自身的ClassLoader
- OSGi 框架的组件运行机制
- OSGi 框架的组件运行机制
- OSGi框架的三个层次
- OSGi框架的三个层次
- 数据自动排布
- Ogre实现无缝地图要改的地方 记下来 用的时候可以看
- jQuery
- ubuntu安装rails报错`bin_path': can's find executable rails for rails-3.2.2 (Gem::Exception)
- MongoDB分片布属(Linux)
- 适用于多种OSGi框架的WebConsole与OSGi嵌入到Web应用的实现
- 深入浅出VMware的组网模式
- Mac OS X 卸载Mono 的方法
- 学习笔记——XML Schema简介及命名空间
- vc++ linux c 编程
- MongoDB分片群集(windows)
- 女维护工程师 巾帼不让须眉
- 入学三个月后……
- 深入研究Servlet线程安全性问题