使用 Felix 和 Struts2 开发 Web 应用

引言

Open Service Gateway Initiative(OSGi) 是一个针对 Java 动态模块开发的规范。基于中间件的 OSGi 技术提供了一个面向服务,基于组件的开发环境,并提供标准化的方式来管理整个软件生命周期。OSGi 为那些须要长时间运行,动态更新而且对运行环境的影响尽量小的系统提供了很好的支持。基于 OSGi 开发的系统具备复杂度低、可重用、动态部署、可持续运行、开发简单等特色。html

OSGi 技术结合了上述特色的各方面来定义一个动态服务部署框架,能够进行远程管理。OSGi 技术起初只是关注于嵌入式领域,诸如机顶盒、服务网关、手机等应用环境。可是它完美地适用在任一模块化、面向组件、面向服务的项目。Eclipse V3.0 之后采用 OSGi 做为其模块化和动态化平台,设计了 Equinox 内核,使用 OSGi 技术帮助其进行类载入,大大提高了 Eclipse 的启动速度。在应用服务器上,WebSphere,Weblogic,JBOSS 等著名的服务器都支持或使用了 OSGi 技术。java

Felix 是一个 Apache 旗下 OSGi 实现的开源框架,它的最终的目标是提供一个彻底兼容的 OSGi 框架和标准服务的实现。Felix 当前实现了 OSGi 规范 4 的大部份内容,目前 Felix 提供的 OSGi 框架功能是很是稳定的。web

采用 Spring DM 和 Jetty 等 Web 容器开发基于 OSGi 的 Web 应用的方法已经在不少书本或技术文章上说起。可是这种开发方法与传统的 Web 开发差异较大,开发人员很难转换到这种开发模式上,而且它的稳定性也没有获得充分的验证。spring

不少 Web 开发都采用 Struts 做为其控制层,很幸运的是,最新发布的 Struts2.1.8.1 中,加入了对 Felix OSGi 的支持,可以在传统的 Web 开发中集成 OSGi 的模块管理平台,并且开发方法没有太大的改变,开发后的应用程序仍像原先同样能够方便的部署在 Tomcat,JBoss 等容器上。shell

本文将经过下面的示例,详细讲述如何使用 Felix 和 Struts 开发 Web 应用。apache

回页首浏览器

使用 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)信息。app

本示例的结构原理如图 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. 示例结构原理图

图 1. 示例结构原理图

创建 OSGi 的 Web 开发环境

本文示例使用的 Web 开发环境包括以下组件,部分框架能够 参考资料中下载。

  • Eclipse 3.5 for Java EE Developers
  • Sun JDK 1.6.0
  • Tomcat 6.0.24
  • Struts 2.1.8.1 (Essential Dependencies Only)
  • spring-osgi-1.1.2-with-dependencies (Only Need when adding Spring Support)

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. Web Application 工程的目录结构

图 2. Web Application 工程的目录结构

将 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 工程向导图

图 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. Web Application 中的 bundles 目录结构

图 4. Web Application 中的 bundles 目录结构

运行演示

启动 Tomcat,在浏览器地址栏输入 http://localhost:8080/webapp/osgi/admin/bundles.do, 能够看到所系统中全部的 bundle 的列表。

图 5. 部署的 bundles 列表

图 5. 部署的 bundles 列表

在浏览器地址栏输入 http://localhost:8080/webapp/time/time.do,能够得到时间信息,此时的时间信息为本地时间信息,当前 TimeService 这个服务有 local time service 和 UTC time service 两个实现,调用的是 local time service 这个实现。

图 6. 获取本地时间页面显示

图 6. 获取本地时间页面显示

此时,在浏览器地址栏输入 http://localhost:8080/webapp/osgi/admin/shell.do,而后输入命令 stop 1, 将 Local time service 这个 bundle 中止掉,输入命令 ps, 能够看到 local time service 这个 bundle 的 state 已经变为 Resolved.

图 7. OSGi Shell 管理页面

图 7. OSGi Shell 管理页面

在浏览器地址栏再次输入 http://localhost:8080/webapp/time/time.do 获得的结果如图 7 所示。

图 8. 获取 UTC 时间显示页面

图 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 管理经常使用的命令操做,以及在开发过程当中的几个常见的问题的解决方法。

相关文章
相关标签/搜索