EdwardWong 2011-08-24
转载自http://www.ibm.com/developerworks/cn/web/1007_shaobing_flexosgi/index.html?ca=drs-
这篇文章的项目会有两个问题:
1. 报错velocity......的异常,因为缺少了velocity-tools-1.3.jar 包。
2. 如果无法上网时解析struts.xml会报错,请修改src下的struts.xml的dtd文件的url 为
<!DOCTYPE struts PUBLIC
"-//ApacheSoftwareFoundation//DTDStrutsConfiguration2.0//EN"
"file:/D:/saber/workspace/OSGISSH/dtd/struts-2.0.dtd">同时如果导入struts2-osgi-admin-bundle-2.2.3.jar ,也要修改这个包下的struts.xml的头!
引言
Open Service Gateway Initiative(OSGi) 是一个针对 Java 动态模块开发的规范。基于中间件的 OSGi 技术提供了一个面向服务,基于组件的开发环境,并提供标准化的方式来管理整个软件生命周期。OSGi 为那些需要长时间运行,动态更新并且对运行环境的影响尽可能小的系统提供了很好的支持。基于 OSGi 开发的系统具有复杂度低、可重用、动态部署、可持续运行、开发简单等特点。
OSGi 技术结合了上述特点的各方面来定义一个动态服务部署框架,可以进行远程管理。OSGi 技术起初只是关注于嵌入式领域,诸如机顶盒、服务网关、手机等应用环境。但是它完美地适用在任一模块化、面向组件、面向服务的项目。Eclipse V3.0 以后采用 OSGi 作为其模块化和动态化平台,设计了 Equinox 内核,使用 OSGi 技术帮助其进行类载入,大大提升了 Eclipse 的启动速度。在应用服务器上,WebSphere,Weblogic,JBOSS 等著名的服务器都支持或使用了 OSGi 技术。
Felix 是一个 Apache 旗下 OSGi 实现的开源框架,它的最终的目标是提供一个完全兼容的 OSGi 框架和标准服务的实现。Felix 当前实现了 OSGi 规范 4 的大部分内容,目前 Felix 提供的 OSGi 框架功能是非常稳定的。
采用 Spring DM 和 Jetty 等 Web 容器开发基于 OSGi 的 Web 应用的方法已经在很多书本或技术文章上提及。但是这种开发方法与传统的 Web 开发差别较大,开发人员很难转换到这种开发模式上,并且它的稳定性也没有得到充分的验证。
很多 Web 开发都采用 Struts 作为其控制层,很幸运的是,最新发布的 Struts2.1.8.1 中,加入了对 Felix OSGi 的支持,能够在传统的 Web 开发中集成 OSGi 的模块管理平台,而且开发方法没有太大的改变,开发后的应用程序仍像原先一样可以方便的部署在 Tomcat,JBoss 等容器上。
本文将通过下面的示例,详细讲述如何使用 Felix 和 Struts 开发 Web 应用。
回页首
使用 Felix 和 Struts 开发 Web 应用示例
下面讲解的示例是一个获取时间信息并在 Web 浏览器中显示的简单示例,该示例主要介绍了怎样使用 Felix 和 Struts 结合起来开发 Web 应用。该示例中有两个获取时间信息的 bundle,这两个 bundle 实现同一个接口服务,但是有不同的实现,这两个 bundle 可以在应用中动态部署。通过该示例,可以体现出基于 OSGi 开发的项目具有良好的模块化以及 OSGi 的 bundle 动态部署的能力,从而说明了 OSGi 适用于开发持续运行且需要动态更新的系统。
在这个示例中,一共包括五个工程,一个 Web Application 工程和四个 OSGi bundle 工程。Web Application 工程是用于 Web 部署。四个 OSGi bundle 中,包括一个 Web bundle,用于 Web 交互;一个 time service bundle,包含一个获取时间信息的接口服务;一个 local time service bundle,实现接口服务,用于获取本地时间信息;一个 utc time service bundle,用于获取世界标准时间(Universal Time Coordinated,UTC)信息。
本示例的结构原理如图 1 所示。在 Web Container 中注册了 Struts OSGi 的监听器,该监听器会去启动或停止 Apache Felix Host,Apache Felix Host 是 Struts OSGi Plugin 和 Felix Framework 的连接点。Felix Host 会去创建和初始化 Felix Framework,Felix Framework 负责管理系统中的其余的所有 bundle,Struts OSGi Plugin 会监听 bundle 的变化,如果发生变化的 bundle 是一个 Struts bundle, 则会去加载 Struts 的配置。
图1.示例结构原理图
建立 OSGi 的 Web 开发环境
本文示例使用的 Web 开发环境包括如下组件,部分框架可以 参考资料中下载。
Web Application 工程的创建方式与通常的 Web 工程类似,但是需要加入 Felix 的支持和 Struts2 OSGi Plugin. Felix 是 OSGi 的平台,用于管理整个系统中的所有的 bundle,而 Struts2 OSGi Plugin 是 Struts2 和 OSGi 连接的桥梁,通过 Struts2 OSGi Plugin 将 Felix 融入到 Struts2 框架中。另外,还需要加入 Struts2 OSGi Admin bundle,这个 bundle 向管理人员提供基于 Web 的管理 OSGi 平台中的 bundle 的操作入口。同时在 web.xml 中需要加入 Struts OSGi 监听器,这样 OSGi 平台中的 bundle 发生变化时,会触发该监听器去做一些与 Struts 相关测操作,例如增加 Action 或使 Action 失效。
web.xml 中过滤器和监听器部分的配置内容如清单 1:
清单 1. web.xml 过滤器和监听器配置
<filter> <filter-name>struts2-prepare</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter</filter-class> </filter> <filter> <filter-name>struts2-execute</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsExecuteFilter </filter-class> </filter> <filter-mapping> <filter-name>struts2-prepare</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>struts2-execute</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.apache.struts2.osgi.StrutsOsgiListener</listener-class> </listener> <listener> <listener-class>org.apache.struts2.dispatcher.ng.listener.StrutsListener </listener-class> </listener> |
Web Application 工程的目录结构如图 2 所示:
图2.WebApplication工程的目录结构
将 Web Application 部署到 Tomcat 上并启动 Tomcat,然后在浏览器中输入 http://localhost:8080/webapp/osgi/admin/bundles.action, (webapp 是项目部署到 Tomcat 中的名字 ) 如果看到了类似于 图 5的 bundles 列表,说明 OSGi 环境配置成功。
开发获取时间消息接口服务 bundle
消息接口服务 bundle 是提供消息服务的接口,该接口将被 Web bundle 所使用,其他 bundle 可以不同的形式实现该接口。在这里利用 eclipse 新建插件工程的功能来创建 OSGi bundle。需要特别设置 an OSGI framework 为 standard 方式,这种方式允许部署项目到标准的 OSGI 容器中。新建 OSGi 工程的向导如图 3 所示。
图3.新建OSGi工程向导图
在该项目中开发一个用于获取时间信息的接口,通过该接口可以获取字符串形式的时间信息。
清单 2. 获取时间服务接口代码
package com.example.time.service; public interface TimeService{ public String getTime(); } |
需要将该 bundle
中的服务包的类和接口就暴露给了其他的 bundle
,其他的 bundle
可以通过 import
这个包来使用其中的类和接口。
开发获取本地时间消息实现服务 bundle
获取本地时间消息服务 bundle 实现了时间消息接口服务。在该 bundle 种返回的时间消息是当前时区的时间信息。因为用到了接口服务包,所以需要在 Import-Package 中加入接口服务包。
清单 3. 获取本地时间实现代码
package com.example.time.local.service; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import com.example.time.service.TimeService; public class LocalTimeService implements TimeService{ @Override public String getTime(){ Calendar calendar = Calendar.getInstance(); Date date = calendar.getTime(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return "The local time:" + formatter.format(date); } } |
OSGi bundle 中的服务要能够被其他 bundle 使用,使用将服务发布出来。在该 bundle 的 Activator 的 start() 方法中注册该服务,可以发布这个服务。当这个 bundle 启动时,将获取本地时间发布为一个服务。服务发布的代码如清单 4 所示。
清单 4. 服务发布
public void start(BundleContext context) throws Exception{ context.registerService(TimeService.class.getName(), new LocalTimeService(), null); } |
开发获取 UTC 时间消息实现服务 bundle
获取 UTC 时间消息实现服务同样实现了时间消息接口服务,该 bundle 主要是用于和上一个 bundle 即获取本地时间消息服务进行动态的替换,用于表现 OSGi 的动态部署的能力。
清单 5. 获取 UTC 时间服务实现
public class UTCTimeService implements TimeService { @Override public String getTime() { Calendar calendar = Calendar.getInstance(); int zoneOffset = calendar.get(Calendar.ZONE_OFFSET); int dstOffset = calendar.get(Calendar.DST_OFFSET); calendar.add(Calendar.MILLISECOND, -(zoneOffset + dstOffset)); Date date = calendar.getTime(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm s"); return "The UTC time:" + formatter.format(date); } } |
开发 Web bundle
Web bundle 是系统 Web 交互的入口。在该 bundle 中需要使用时间消息接口服务 bundle 中的接口,所有需要在 MANIFEST.MF 的 Import-Package 加入时间消息接口服务包,另外,为了能够识别出该 bundle 是一个 Struts bundle, 需要将该 bundle 设置为可被 Struts2 支持 , 即在 MANIFEST.MF 中加入 Struts2-Enabled: true, 这样该 bundle 中的 struts.xml 就会被加载。最终的 MANIFEST.MF 的配置如清单 6。
清单 6. Web Bundle 的 MANIFEST.MF 配置
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: com-example-time-web Bundle-SymbolicName: com.example.time.web Bundle-Version: 1.0.0.qualifier Bundle-Vendor: keki Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Struts2-Enabled: true Import-Package: com.example.time.service, com.opensymphony.xwork2, org.apache.struts2.osgi.interceptor, org.osgi.framework;version="1.3.0" |
为了实现用户交互,还需要创建一个获取时间消息响应的 action。该 Action 的 execute() 方法代码如清单 7 所示。
清单 7. Action 实现方法
public String execute(){ ServiceReference ref = bundleContext.getServiceReference( TimeService.class.getName()); TimeService timeService = (TimeService) bundleContext.getService(ref); timeMessage = timeService.getTime(); return SUCCESS; } |
这个 Web bundle 中独立的建立一个 struts.xml,这个 struts.xml 将会为单独加载,需要注意的是 Struts 的 pacakge 继承 osgi-default 这个包,osgi-default 已在 struts2-osgi-plugin 这个 jar 包里面定义。
清单 8. Web Bundle struts.xml 的 action 定义
<struts> <package name="time-example" namespace="/time" extends="osgi-default"> <action name="time" class="com.example.time.web.action.TimeAction"> <result type="freemarker">time.ftl</result> </action> </package> </struts> |
打包部署
将开发好的四个 bundle 导出成 plugin 的包,并将它们放在 Web App 工程中 ,bundles 的目录结构如图 4 所示。
图4.WebApplication中的bundles目录结构
运行演示
启动 Tomcat,在浏览器地址栏输入 http://localhost:8080/webapp/osgi/admin/bundles.do, 可以看到所系统中所有的 bundle 的列表。
图5.部署的bundles列表
在浏览器地址栏输入 http://localhost:8080/webapp/time/time.do,可以获得时间信息,此时的时间信息为本地时间信息,当前 TimeService 这个服务有 local time service 和 UTC time service 两个实现,调用的是 local time service 这个实现。
图6.获取本地时间页面显示
此时,在浏览器地址栏输入 http://localhost:8080/webapp/osgi/admin/shell.do,然后输入命令 stop 1, 将 Local time service 这个 bundle 停止掉,输入命令 ps, 可以看到 local time service 这个 bundle 的 state 已经变为 Resolved.
图7.OSGiShell管理页面
在浏览器地址栏再次输入 http://localhost:8080/webapp/time/time.do 得到的结果如图 7 所示。
图8.获取UTC时间显示页面
通过上面的演示,我们可以看到 OSGi bundle 的动态部署能力。
回页首
bundle 的管理
通过 Felix 可以方便的管理项目中的 bundle,并且实现 bundle 的热部署,即插即用,即删即无的特性,特别适用于可持续运行的系统。
添加 bundle
输入命令 install <bundle-url>,然后输入 start <bundle-id> 即可。如 $install file:/k:/plugins/com.example.time.local_1.0.0.qualifier.jar , $start 7
更新 bundle
输入命令 update <bundle-id> <bundle-url> 即可。如
$ update 1 file:/k:/plugins/com.example.time.local_1.0.0.qualifier.jar
启动和停止 bundle
输入命令 start <bundle-id> 启动 bundle;输入命令 stop <bundle-id> 停止 bundle。如
$ start 2 , $ stop 1
卸载 bundle
若 bundle 处于 Installed 或 Resolve 状态,则直接输入命令 uninstall <bundle-id>。若 bundle 处于 Actived 状态,则先输入命令 stop <bundle-id> 停止 bundle, 再输入命令 uninstall <bundle-id>。如 $ uninstall 1
回页首
常见问题
如何修改 bundle 的最大启动级别
在上面的示例中,bundle 中最大的启动级别只能为 3。如果在 bundles 下面增加一个目录 4,即 bundles/4,则目录 4 中的 bundle 是无法启动的,而在很多时候,特别是在大型的项目中,最大启动级别为 3 是不能满足要求,此时可以 web.xml 中添加启动级别的参数。如下面把最大启动级别设置为 5。
清单 9. 启动级别配置
<context-param> <param-name>struts.osgi.runLevel</param-name> <param-value>5</param-value> </context-param> |
解决 Bundle 中的 struts.xml 的 Struts Configuration DTD 无法定位的问题
Struts.xml 的头部有 Struts Configuration DTD 的引用定义,一般 DTD 文档的 URL 为 http://struts.apache.org/dtds/XXX.dtd ,示例如下所示:
清单 10. struts.xml 头部 dtd
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> |
如果无法连接上 http://struts.apache.org/,那么在加载 Struts 的 bundle 时也将会出错,因为 bundle 与 Web Application 的 lib 的加载路径不一致,无法从 Web Application 的 lib 下面找到 XXX.dtd 文件。此时可以通过修改 dtd 文件的 URL 来解决,可以改成一个本地文件系统的 URI,如 file:/c:/webapp/dtds/struts-2.0.dtd,也可以改为本地的 Web 服务器或一个可以连接上的服务器的 URL,如 http://localhost/dtds/struts-2.0.dtd。
如何使用 Spring 进行对象管理
Spring DM 使得 Spring 和 OSGi 成为可能,在本文的开发环境中,也可以加入 Spring DM 来管理系统中的对象。首先加入 Spring DM 必要的 jar 包,如
清单 11. Spring 依赖包示例
com.springsource.org.aopalliance-1.0.0.jar, com.springsource.org.apache.commons.logging-1.1.1.jar, spring-aop-2.5.5.jar, spring-beans-2.5.5.jar, spring-context-support-2.5.5.jar, spring-core-2.5.5.jar, spring-osgi-core-1.1.2.jar, spring-osgi-extender-1.1.2.jar, spring-osgi-is-1.1.2.jar, spring-osgi-web-1.1.2.jar, spring-osgi-web-extender-1.1.2.jar, spring-web-2.5.5.jar |
然后需要在 Web Application 的 struts.xml 中加入对象工厂的配置,配置如下:
清单 12. 配置对象工厂
<constant name="struts.objectFactory" value="osgi" /> <constant name="struts.objectFactory.delegate" value="springOsgi" /> |
在 Web Application 的 web.xml 加入 Spring 的监听器 , 配置如下:
清单 13. 配置 Spring 监听器
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext </param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>osgibundle:/META-INF/spring/*.xml,/spring/*.xml </param-value> </context-param> <context-param> <param-name>parentContextKey</param-name> <param-value>parent-context-bean</param-value> </context-param> |
在 OSGi bundle 中,如果需要用 Spring 来管理对象,则把 Spring 对象的配置文件放在 /META-INF/spring/ 目录下面,并以 xml 为扩展名即可。Spring 对象配置文件的写法在很多 Spring DM 的书籍或文章中都有讲解,这里不再重复。最后需要在 MANIFEST.MF 中加入如下声明用来配置 Spring 上下文和对象创建机制,create-asynchronously 的值为 true 表示可以允许异步创建对象。
清单 14. 配置 Spring 对象创建方式
Spring-Context: *;create-asynchronously:=true |
回页首
小结
本文首先对 OSGi 和 Felix 进行了简要的介绍,然后通过一个示例详细介绍了使用 Felix 和 Struts 开发 Web 应用,演示了 OSGi 的动态部署特性。随后,讲解了 OSGi bundles 管理常用的命令操作,以及在开发过程中的几个常见的问题的解决方法。
<!-- CMA ID: 500011 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file: dw-article-6.0-beta.xsl -->参考资料
学习
讨论
作者简介
韦景琪,东北大学软件学院学生,目前为 IBM 中国研究院供应链分析与优化研究实习生,从事业务流程建模、Web 服务、RCP 相关的研发。
邵兵, IBM 中国研究院供应链分析与优化研究组研究员,目前从事供应链流程分析与优化方面的研究与开发工作。对 Eclipse RCP 开发有深入的了解。他的邮箱是:[email protected]。
周英,就职于 IBM 中国研究院,目前从事绿色数据中心的研究工作,关注传感器网络与虚拟化技术。
建议