OSGI进阶:第三章 与流行的 Java B/S 体系架构进行集成(二)

 由  ValRay 发布

3.3. 解决和Webwork的集成

要集成 Webwork,要解决的问题和与 Hibernate 的集成基本类似,集中在如下三个方面: Webwork 要求所有的 Action 配置文件都需要在其统一的 xwork.xml 做 include 配置,而基于 OSGi 后需要达到的目标是:各模块维护自己的 xwork.xml,以某种方式绑定到 webwork 上去;
Webwork 加载 Action Class 要求这些 Action Class 必须处于同一 Classloader 下,而基于 OSGi 后 Action Class 的 Classloader 与 Webwork 的则不同,还要解决的一个问题是如何让这些 Action Class 能够注入 Spring bean 或者 OSGi 服务; Velocity 加载页面模板文件时可采用 classloader 和文件路径两种方式进行加载,基于 OSGi 后各模块的页面位于不同的 classloader 和不同的文件路径下; 要解决这三个问题,就得分析 webwork 是如何装载、解析 Action 配置文件;如何加 载 Action Class 以及相关的 Interceptor Class: 如何装载、解析 Action 配置文件

Webwork 基于 Xwork 而构建,Action 配置文件的装载、解析均由 Xwork 来完成,跟踪 webwork 的启动流程后,在 Xwork 的 DefaultActionProxy 的构造器中可看到 这么一行代码:

config  = 
ConfigurationManager.getConfiguration().getRuntimeConfiguration().getActionConfi g(namespace, actionName);

继续跟踪 ConfigurationManager,在 getConfiguration 方法中可看到实例化了 DefaultConfiguration,并调用了它的 reload 方法; 跟踪 DefaultConfiguration.reload 方法,可看到如下方法:

for (Iterator iterator = ConfigurationManager.getConfigurationProviders().iterator();              iterator.hasNext();) { 
        
ConfigurationProvider provider = (ConfigurationProvider) 

iterator.next();             provider.init(this); 
   
 }

回到 ConfigurationManager 的 getConfigurationProviders 方法,默认情况下 Xwork 仅使用了 XmlConfigurationProvider,跟踪查看 XmlConfigurationProvider 的 init 方法,在其中可找到如下代码:

loadConfigurationFile(configFileName, null);

跳至此方法,在这个方法中,Xwork 完成了加载和解析 Action 配置文件的工作,在此方法中首先读取位于 classpath 下的 xwork.xml,并解析此 xml 中的内容,如所碰到的子元素节点为 package,则直接调用 addPackage 来加载 Package 中的元素信息;如所碰到的子元素节点为 include,则再次调用 loadConfigurationFile 来 加载该文件中的元素,这个方法中为关键的代码是这行:

is = getInputStream(fileName);

这行表现了 XmlConfigurationProvider 是如何寻找 Action 配置文件的, getInputStream 方法通过这么一行来将指定名称的 Action 配置文件解析为 InputStream:

return FileManager.loadFile(fileName, this.getClass());

查看 FileManager 的 loadFile 方法,可发现后是通过 com.opensymphony.util 包中的 ClassLoaderUtil.getResource 来获取文件流的,查看 ClassLoaderUtil 的 getResource 方法:

public static URL getResource(String resourceName, Class callingClass)     { 
    URL url = 

Thread.currentThread().getContextClassLoader().getResource(reso urceName); 
    if(url == null)             url = 

(com.opensymphony.util.ClassLoaderUtil.class) .getClassLoader().g etResource
(resourceName); 
    
if(url == null) 
    
{ 
       
ClassLoader cl = callingClass.getClassLoader();             if(cl != null) 
           
 url = cl.getResource(resourceName); 
   
 } 
   
 if(url == null && resourceName != null && resourceName.charAt(0) != '/') 
      
  return getResource('/' + resourceName, callingClass);         else             

return url;     }

根据这个方法可看出,Xwork 在加载 Action 配置文件的顺序为: 使用当前线程的 classloader 加载文件; 使用 ClassLoaderUtil 类的 classloader 加载文件; 使用调用类的 classloader 加载文件; 尝试在文件名前加上“/”,再次调用 getResource 进行尝试。 经过上面的分析,可看出 Xwork 通过 XmlConfigurationProvider 来完成 Action 配 置文件的装载和解析。 如何加载 Action Class 以及相关的 Interceptor Class 跟踪 Webwork 的执行流程,可看到是通过 DefaultActionInvocation.invoke 来执行 Action 的,DefaultActionInvocation 通过 createAction 方法来创建 Action 的实例, 在这个方法中重要的代码如下所示:

action  =   ObjectFactory.getObjectFactory().buildAction(proxy.getActionName(), 

proxy.getNamespace(), proxy.getConfig(), contextMap);

跟踪 ObjectFactory 的执行,发现无论是 Action Class 还是 Interceptor Class,终都是通过 getClassInstance 方法来加载类的,这个方法的代码如下:
if (ccl != null) { return ccl.loadClass(className); } return ClassLoaderUtil.loadClass(className, this.getClass()); 对于 Continuation 方式的 package 而言,通过 ContinuationsClassLoader 来加载 class,而普通方式的 package,则通过 ClassLoaderUtil 来加载 class,ClassLoaderUtil 在加载 class 时采用如下顺序: 使用当前线程的 classloader 加载; 直接使用 Class.forName 的方式加载; 使用 ClassLoaderUtil 类所在的 classloader 加载; 使用调用 class 的 classloader 进行加载。 如何让Action Class能够注入Spring bean或OSGi Service是我们为关注的问题, 所幸的是 Webwork 本身就提供了和 Spring 的集成的支持,加上之前已经完成了 OSGi 和 Spring 的集成,因此这里我们只需要考虑如何让 Webwork 能够和 Spring-OSGi 来进行集成了,跟踪 Webwork 和 Xwork 的代码可发现 Webwork 实现和 Spring 的无缝集成是通过 Webwork 中的 WebworkSpringObjectFactory 和 Xwork 的 SpringObjectFactory 来实现的,也就是说如果你将 webwork 的 objectfactory 设置为 spring 时,Xwork 在加载 Action Class 时就不会使用之前的 ObjectFactory 了,而是使用 WebworkSpringObjectFactory 来加载,要实现与 Spring-OSGi 的集成,可通过编写一个新的 SpringOSGiObjectFactory 来实现了。 在了解了 webwork 如何加载、解析 Action 配置文件和如何加载 Action Class 后,来写一个封装 webwork 的 Bundle,从而支持 Action 配置文件和 Action 类在各个模块中独立编写。 要支持各模块中编写的 Action 配置文件和 Action 类能够绑定到 webwork 上,同时又不修改 webwork 模块的东西,可以通过扩展点的方式将 Action 配置文件注册到 webwork 模块,并同时编写一个支持动态加载 Action 配置文件的对象,至于 Action 配置文件的解析方法可以完全照抄 XmlConfiguration 的写法;另外要解决的就是

webwork 模块加载 Action/Interceptor 类的问题了,如果只是加载 Action/Interceptor 类的话,可以通过 DynamicImport-Package 的方式来实现,但现在我们还得实现 Action Class 注入 Spring bean 和 OSGi 服务的支持,就得参照 WebworkSpringObjectFactory 来编写一个 SpringOSGiObjectFactory 来实现了。 定义绑定 Action 配置文件的扩展点 在定义扩展点之前,要首先创建封装 Webwork 的 Bundle,此 Bundle 需要放入 webwork 编译和运行所依赖的 jar 包,在完成此步工作后,开始定义扩展点的工作; 根据上面的分析可知,在扩展点中需要定义的为需要加载的 Action 配置文件,在封装 webwork 的 Bundle 中增加一个 ActionExtension 的扩展点配置,在此扩展点中增加一个 action 的节点元素,该节点元素的属性仅需要一个,即 Action 配置文件的路径和名称。 定义好扩展点后就可以来编写加载和解析 Action 配置文件的类了,此类和默认的 XmlConfiguration 不同的地方不是通过 xwork.xml 来加载 action 的配置文件,而 是通过监控扩展点的方式来进行加载,在扩展点发生变化时需要动态的进行 action 配置的加载和卸载,由于其他的功能和 XmlConfiguration 相同,复制 XmlConfiguration 进 行 修 改 来 完 成 此 类 的 编 写 , 并 重 新 命 名 为 XmlExtensionConfigurationProvider,首先在 XmlExtensionConfigurationProvider 初始化时调用 IExtensionRegistry 获取到系统中的 Action 的扩展,并调用 loadConfigurationFile 方法来完成对于 Action 配置文件的加载;接着实现 IRegistryChangeListener 接口,以实现动态的根据 Action 扩展点实现的变化来做 出相应的处理,此接口方法实现的代码示例如下:
/ (non-Javadoc) @see org.eclipse.core.runtime.IRegistryChangeListener#registryCha nged(org.eclipse.core.runtime.IRegistryChangeEvent) / public void registryChanged(IRegistryChangeEvent event) { IExtensionDelta[] deltas=event.getExtensionDeltas(NAMESPACE,EXTENSION_POINT); for (int i = 0; i < deltas.length; i++) { IConfigurationElement[] elements=deltas[i].getExtension().getConfigurationElements() ; switch (deltas[i].getKind()) { case IExtensionDelta.ADDED: for (int j = 0; j < elements.length; j++) {
loadConfigurationFile(elements[i].getAttribute("configFile "), null); } break; case IExtensionDelta.REMOVED: for (int j = 0; j < elements.length; j++) { includedFileNames.remove(elements[j]); String fileName=elements[j].getAttribute("configFile");
if(configFilePackages.containsKey(fileName)){ List packages=(List) configFilePackages.get(fileName); for (Iterator iter = packages.iterator(); iter .hasNext();) { String packageName = (String) iter.next();
configuration.removePackageConfig(packageName); } } } break; default: break; } } }

另外一个问题就是这些配置文件的加载,在分析webwork对于Action配置文件 的加载时xwork是使用ClassLoaderUtils来获取的,要在OSGi中让Webwork模块能够加载到其他模块中的Action配置文件,就得将这些Action配置文件放入 对外提供的package中,同时在webwork模块中加上DynamicImport-Package: *,这样就可以在webwork中通过ClassLoaderUtils来加载到其他模块中的Action 配置文件了。 支持加载其他模块中的 Action/Interceptor 类,并与 Spring-OSGi 进行集成 在加载 Action/Interceptor 类时,Webwork 是通过 ObjectFactory 来完成的,在此编写一个新的 SpringOSGiObjectFactory 的类,以便加载的 Action 能和 Spring-OSGi 进行集成,这个 SpringOSGiObjectFactory 对于两个方法的实现如下:

/ (non-Javadoc)

@see com.opensymphony.xwork.ObjectFactory#buildBean
(java.lang.Str ing, java.util.Map)
/ public Object buildBean(String className, Map extraContext) throws Exception {
BundleContext context=WebworkActivator.getContext(); ServiceReference[]

serviceRefs=context.getAllServiceReferences(IAction.class.ge tName(),

"(command="+className+")");
if((serviceRefs==null)||(serviceRefs.length==0)){ Class

clazz=getClassInstance(className); return super.buildBean(clazz, extraContext); }
else if(serviceRefs.length>1){ throw new Exception("可用的Action服务:【"+className+"】大于1"); }
return context.getService(serviceRefs[0]); } / (non-Javadoc) @see com.opensymphony.xwork.ObjectFactory#getClassInstance(java.lang.Strin g) / public Class getClassInstance(String className) throws ClassNotFoundException

{   Class clazz=null;   if(useClassCache)    clazz=(Class) classes.get(className);   if(clazz==null){ 
   BundleContext context=WebworkActivator.getContext(); 
  try{ 
ServiceReference[] 

serviceRefs=context.getAllServiceReferences(IAction.class.getName(), 

"(command="+className+")");     if((serviceRefs==null)||

(serviceRefs.length==0)){      clazz=super.getClassInstance(className);      if

(useClassCache) 

  classes.put(className, clazz); 
} 

else if(serviceRefs.length>1){      throw new Exception("可用的Action服务:【"+className+"】大于1"); 
} 

else{ 

 clazz=context.getService(serviceRefs[0]).getClass();

} }
catch(Exception e){ throw new ClassNotFoundException(e.toString()); } }
return clazz; }

从上面的方法中可以看出,对于Action类的加载以及和Spring-OSGi的集成采用的方法是将 Action 以 OSGi 服务的方式对外 Export,这种方法和之前 SimpleMVCFramework的方法是基本相同的,在加载Action上和Webwork 本身的加载方式还有一个不同的地方在于 Webwork 采用的是通过 Action 的 ClassName 进行加载,而 SpringOSGiObjectFactory 则是将这个 className作为OSGi服务的filter来使用的,也就是在action的配置文件中对于action的class中应使用的是和Action OSGi服务中的command 属性相同的值。 在解决了上面两个问题后,需要将 Webwork 改为使用上面两个类来加载 Action 配置文件信息和加载 Action 类: 使用 XmlExtensionConfigurationProvider 来加载 Action 配置文件信息 由于在 ConfigurationManager 中使用的默认的 XmlConfigurationProvider 不是配置出来的,因此只能复制 ConfigurationManager 的代码到封装 webwork 的 bundle 中, 并将其中的 getConfigurationProviders 方法中的 configurationProviders.add(new XmlConfigurationProvider()); 修 改 为 configurationProviders.add(new XmlExtensionConfigurationProvider ());。 使用 SpringOSGiObjectFactory 来加载 Action 类 参照 Webwork 在集成 Spring 时的做法,需要修改 DispatcherUtils 类 init(ServletContext)方法,在 if(className.equals("spring")) {这段中再加上 else if(className.equalsIgnoreCase("springosgi")){ className = "cn.org.osgi.xwork.springosgi.SpringOSGiObjectFactory"; }

在 完 成 上 面 的 修 改 后 , 只 需 将 webwork.properties 中 的 webwork.objectFactory 修改为 springosgi,在以后的运行中就会使用 SpringOSGiObjectFactory来加载Action类。

做 好 了 这 些 准 备 后 , 以 留 言 板 列 表 模 块 为 例 来 将 以 前 基 于 SimpleMVCFramework+Spring+Hibernate 的 结 构 重 构 为 基 于 Webwork+Spring+Hibernate 的结构。 去除留言板列表模块中使用到 SimpleMVCFramework 的部分 在留言板列表模块中使用到 SimpleMVCFramework 有 Controller 的注册和 WebCommand 的部分。

Controller 的注册部分是用于完成 Servlet 的注册的, Webwork 采用 ServletDispatcher 作为 Controller ,因此需要通过 HttpService 来注册 ServletDispatcher,在封装 Webwork 的 Bundle 需要将注册 Controller 部分作为扩展点对外提供,这样基于 webwork 的应用才可以注册自己的 web context,这里要注意的是 HttpService 目前尚不支持这种通配符的注册方式,也就是说不能像通常使用 webwork 时采用.action 这样的方式来做 servlet-mapping,另外目前 HttpService 暂时不支持 filter,因此也没办法直接使用 webwork 新版本的 filter 的 处理方式。

在封装 webwork 的 bundle 中编写 webwork controller 的扩展点,该扩展点对外提 供的为 web context 的注册,在此扩展点管理的组件中要注意的是在初始化 ServletDispatcher 后,在 httpService 注册此 Servlet 后需要显式的调用 ServletDispatcher 的 init 方法:

ServletDispatcher controller=new ServletDispatcher();
try { httpService.registerServlet(webcontext, controller, null, null); controller.init(); info("已注册:"+webcontext); }

catch (Exception e) { error("注册扩展的:"+webcontext+"失败",e); }

在留言板列表模块中通过修改 plugin.xml 来注册 controller 的扩展,将其映射到 /bulletin 的 web context 上:

<extension point="WebworkModule.ControllerExtension">

打开留言板列表模块的 MANIFEST.MF,去除其中对于 SimpleMVCFramework Export Package 的 Import。 重构 Action 在去除了对 SimpleMVCFramework 的依赖后,开始重构 Action 部分的代码,对 于留言板列表模块而言也就是 BulletinListCommand 类,将 BulletinListCommand 重构为符合 webwork 的 BulletinListAction,对于使用 request 提取的参数改为使用注入的方式获取,在重构完毕后相应的修改 springbeans.xml 和 osgiservices.xml 中关于此 Action 的描述。 在重构完 Action 后,要做的事就是编写 Action 配置文件,编写完毕后示例如下:




type="velocity">/cn/org/osgi/bulletin/list/pages/list.vm

</package>

这个配置文件中之前没交代的部分为result页面跳转的部分,在留言板中采用的是velocity的方式,velocity在加载其模板页面时支持以文件路径的加载方式和classpath的加载方式,在这里采用classpath方式进行加载,这样各模块只需将其模板页面所在的package对外export,velocity即可加载到相应的模板页面,否则会出现ResourceNotFound的错误。 在重构完Action后,将Action、Action配置文件以及Action所用到的模板页面所在的package对外Export。 注册 Action 按照封装 webwork bundle 提供的扩展点注册 Action 配置文件,在 plugin.xml 中加 上如下内容:

<extension         point="WebworkModule.ActionExtension"> 
 
<action 

configFile="cn/org/osgi/bulletin/list/action/xwork.xml"/> 

</extension>

完成了上面的步骤后,重新启动留言板系统,此时暂时先不安装其他的留言板模块和 SimpleMVCFramework模块,通过http://IP:PORT/bulletin/list.action这样的方式来访问留 言板列表,运行后会出现如下错误:

HTTP ERROR: 404 Not Found 这是为什么呢?跟踪 Webwork 的代码发现是由于 DefaultActionMapper在处理 uri 时是 按照*.action 这样的 servlet-mapping 的方式来获取 Action 名称的,而由于 HttpService 暂时不支持,因此要修改 DefaultActionMapper 对于 getUri 这个地方的处理,查看 webwork.properties 发现这个是可以配置的,因此编写一个新的 DefaultActionMapper,对 getUri 的部分进行修改,将 getUri 中的这行注释掉:

//uri = RequestUtils.getServletPath(request);

修 改 webwork.properties , 将 webwork.mapper.class 修 改 为 使 用 新 的 DefaultActionMapper。

重新启动留言板系统,再次访问留言板列表页面,OK,成功看到和之前一样的页面了,到此为止,宣告重构完成,可按照同样的方法对其他的留言板模块进行重构。

3.4. 小结

经过上面的步骤后,完成了 OSGi 与 Hibernate、Spring 和 Webwork 的集成,建立起了 OSGi+Hibernate+Spring+Webwork(OHSW)的脚手架,来全面的看看基于这个脚手 架的环境的搭建、开发的方式以及部署的方式,以在项目/产品中能够使用 OSGi+Hibernate+Spring+Webwork(OHSW)这样的结构体系:

3.4.1. 开发环境的搭建

开发环境的搭建其实就是完成 OSGi+Hibernate+Spring+Webwork 脚手架的搭建,在 经过了上面的分析和封装的过程后,整个脚手架的搭建还是比较容易的。

将Spring-OSGi所需的bundle(具体请参见解决和Spring的集成)、HibernateModule 以及WebworkModule部署至OSGi框架中即可,终形成的OHSW脚手架由以下 Bundle构成:
org.springframework.osgi.aopalliance.osgi

org.springframework.osgi.aspectjrt.osgi     

org.springframework.osgi.backport-util-concurrent   

org.springframework.osgi.spring-aop     

org.springframework.osgi.spring-aspects     

org.springframework.osgi.spring-beans   

org.springframework.osgi.spring-context     

org.springframework.osgi.spring-core    

org.springframework.osgi.spring-dao     

org.springframework.osgi.spring-osgi-core   

org.springframework.osgi.spring-osgi-extender   

org.springframework.osgi.spring-osgi-io HibernateModule     WebworkModule javax.servlet     

org.apache.commons.logging      

org.eclipse.equinox.common      

org.eclipse.equinox.ds      

org.eclipse.equinox.http.jetty      

org.eclipse.equinox.http.registry   

org.eclipse.equinox.http.servlet    

org.eclipse.equinox.log     

org.eclipse.equinox.registry    

org.eclipse.equinox.servlet.api     

org.eclipse.osgi    

org.eclipse.osgi.services 

org.mortbay.jetty

3.4.2. 开发方式

持久层的开发(基于Hibernate) 持久层的开发方式和传统的基于 Hibernate 的开发不会有什么太大的区别,仅在 于将以前修改 hibernate.cfg.xml 来绑定 po 的方式改为了通过编写 plugin.xml 来绑定 PO,并将 PO 所在的 package 对外 export。 业务层的开发(基于Spring) 业务层的开发上对于熟练使用 Spring 的开发人员也没有太多的变化,只是要掌握什么情况下需要发布和引用 OSGi 服务(通常来讲就是要在模块外使用的服务就发布为 OSGi 服务,需要在模块内引用其他模块的服务就通过 OSGi 服务的方式来引用),并充分保持使用 OSGi 服务的 bean 的动态性,记住 OSGi 服务是随时来、随时走的。 而对于不熟悉Spring的开发人员而言则应根据自己的需求来学习Spring的相关部分,例如学习 Spring-DAO 等;另外还需要学会将不需要作为 OSGi 服务的组件通过编写 Spring bean 配置文件来实现依赖注入。
Web 响应层的开发(基于Webwork) Web 响应层方面的开发方式和传统的基于 Webwork 的开发方式基本上没有什么区别,不同之处在于不再通过在 xwork.xml 中增加 include 元素来绑定 Action 配置文件,而是修改 plugin.xml 的方式来绑定 Action 配置文件,同时要记得将 Action 类、Action 配置文件以及页面模板文件所在的 package 对外 Export。

3.4.3. 部署方式

和传统的Hibernate+Spring+Webwork的部署方式不同的地方在于,现在各个模块是以独立的project的方式存在和部署的,并且所有的一切都可以动态的进行,部署 的方法遵照OSGi Bundle的部署方式。 基 于 OSGi+Hibernate+Spring+Webwork 这 样 的 脚 手 架 使 得 熟 悉 Hibernate+Spring+Webwork 的人员很容易就能获得 OSGi 所带来的好处,当然目前的 这个脚手架还有很多值得完善的地方,也欢迎大家来根据自己项目/产品的情况共同完善这个脚手架。 本章节较为详细的分析了如何实现 OSGi 与 Hibernate、Spring、Webwork 这些流行开源框架的集成,按照文中类似的方法相信大家能够很好的实现 OSGi 与 struts2、 tapestry、iBatis 等等集成的脚手架,当然,好还是大家用到的这些开源框架都能提 供和 OSGi 集成的版本,就像 Spring-OSGi 一样,目前有的好消息是 Apache 的 Struts 2 准备开始实现和 OSGi 的集成,鉴于 struts 的巨大用户群,这对于 OSGi 的推广使用无疑会起到很大的作用。 在此推荐几篇网上的关于 OSGi 与流行 Java 框架集成的例子的文章: Developing Equinox/Spring-OSGi/Spring Framework Web Application: http://www.blogjava.net/Phrancol/articles/143084.html

DWR+OSGi 整合: http://www.cnblogs.com/ycoe/archive/2007/09/19/898092.html

OSGi 上部署 Hibernate 的四种方式: http://www.javaeye.com/topic/40364 Wicket 如何通过 OSGi 框架注入 Jetty: http://www.javaeye.com/topic/116150

让 OSGi 支持 JSF Web 开发: http://www.blogjava.net/Andyluo/archive/2007/10/08/jsf-support-in-osgi.html

查看评论