OSGi原理与最佳实践:第二章 基于Spring-DM实现Petstore(4)

 由  ValRay 发布

2.2 新一代Petstore的实现

在前面一节,我们完成了 Petstore 功能模块的设计,这在一节中,我们就动手来实现这个即插即 用、即卸即停的 Petstore。

2.2.1 环境准备

首先,我们要准备开发环境。在前面一章,我们已经详细介绍了在 Eclipse 环境下如何准备开发环境,也介绍了如何基于 Spring-DM 进行开发,这里就不再赘述。下面就列出 Petstore 需要用到的 Bundle。

 javax.servlet 
 org.apache.commons.logging  
 org.eclipse.equinox.http.jetty  
 org.eclipse.equinox.http.servlet  
 org.eclipse.osgi  
 org.eclipse.osgi.services  
 org.mortbay.jetty   
org.springframework.bundle.spring  
org.springframework.osgi.core  
org.springframework.osgi.extender  
org.springframework.osgi.io

其中 org.springframework.osgi.core,org.springframework.osgi.extender 和 org.springframework. osgi.io 这三个 bundle 是在 Spring-dm 的下载压缩文件中的,org.springframework.bundle.spring 是 SpringIDE 插件中的一个 bundle,我们这里使用这个 Bundle 是为了方便,这个 Bundle 包含了很多的内容,比如spring-aop,spring-context,spring-beans,spring-dao 等内容。

我们为每个模块创建一个插件工程,然后把这些工程放在一个 Workspace 中,并且把 Spring-DM 中我们要用到的包导入到 Eclipse 中,具体显示见图 2-7。

【图 2-7 工程显示 】
然后在 Run Configurations 中进行配置,加入要用到的 Bundle(见图 2-8)。

【图 2-8 运行配置的 Bundles 】

之后,我们就可以开始编码了。

2.2.2 Utils模块

首先我们从 Utils 模块开始,这是一个只依赖于外部 Bundle 的模块。

>在 Import Packages 中增加下面的 Package:

org.osgi.framework  org.osgi.service.http  
org.springframework.osgi.context

>新建 WebConfigMgr 接口,代码如下:

public interface WebConfigMgr { 
String getResourcePath(); }

>增加对 WebConfigMgr 的实现——WebConfigMgrImpl,代码如下:

public class WebConfigMgrImpl implements WebConfigMgr { private String resourcePath; 
 
public String getResourcePath() { return resourcePath; 
}  public void setResourcePath(String resourcePath) { this.resourcePath = resourcePath; 
} 
}

>增加 WebResourceMgr 类。这个类的功能是方便我们的模块注册自己的静态 Web 资源。这个类实现了 BundleContextAware 接口,在启动的时候能够获取 BundleContext;另外,这个类也实现了 ServiceListener 接口,并且在这个类 start 方法被调用的时候,通过 BundleContext 注册了对 HttpService 的 Listener,在 HttpService 发生变化的时候去进行相应的注册和注销的工作。

我们看下面这个类中比较重要的代码:

    private HttpService getHttpService(){ if (ref == null){ 
ref = bundleContext.getServiceReference(
   HttpService.   class.getName()); 
}   
if (ref != null){ 
 return (HttpService) bundleContext.getService(ref); 
}

上面是获取 HttpService 的代码,我们通过 BundleContext 获得 HttpService 的 Reference,然后再通过 BundleContext 获取到 HttpService 这个服务。

 public void start() throws InvalidSyntaxException{ 
    registerWebResource(); 
    this.bundleContext.addServiceListener(this, "(objectClass=" +  
       HttpService.class.getName() + 
    ")"); 
 
 }        return null; 
     
 }

上面的代码是 WebResourceMgr 这个类的 start 方法,WebResourceMgr 是会被配置为一个 Spring 的 Bean,start 方法是在 WebResourceMgr 这个 Bean 被初始化的时候调用的。可以看到,我们进行了 Web 资源注册,并且添加了 ServiceListener。

  private void registerWebResource(){ 
    try { 
         HttpService httpService = getHttpService();            
         if(null != httpService){ 
         httpService.registerResources(getResourceAlias(), 
         getName(), null);             
         }        
       }          
     catch (Exception e) { 
        e.printStackTrace(); 
    } 
  }

这段是注册 Web 资源的代码,其中 getResourceAlias()和 getName()返回的值都是用户在 Spring 中为 WebResourceMgr 配置的值,我们在不同的模块中会配置独立的 WebResourceMgr 的实例。后,我们再简单看一下注销 Web 资源的代码:

private void unregisterWebResource(){ 
    try { 
         HttpService httpService = getHttpService(); 
         if(null != httpService){ 
            httpService.unregister(getResourceAlias()); 
         }
       } 
   catch(Exception e){ 
        e.printStackTrace(); 
    } 
}

从以上代码可以看出:

在 Exported Packages 中增加 org.osgichina.petstore.util 包。

增加 Spring-DM 配置文件。
我们在 Petstore 中,在每个模块的 META-INF 下,都有一个 spring 的目录,然后在 spring 目录下都有两个 xml,类似如图 2-9 所示的情况。

【图 2-9 Spring-DM 配置目录 】

utils.xml 是一个普通的 Spring 的配置文件。utils-osgi.xml 是一个 Spring-DM 扩展的和 OSGi 服务相关的配置,我们下面简单地看下这两个配置文件的内容。

utils.xml

 <bean name="_webConfigMgr" 
 class="org.osgichina.petstore.#       util.WebConfigMgrImpl"> 
    <property name="resourcePath" value="/petstore/resource" /> 
 </bean>

可以看到,这个和普通的 spring 的配置文件是一样的。

Utils-osgi.xml

<osgi:service id="webConfigMgr"
ref="_webConfigMgr"  interface="org.osgichina.petstore.util.WebConfigMgr"> 
</osgi:service>

这里,我们把在 utils.xml 中定义的_webConfigMgr bean 发布为了一个 OSGi 的 service,供其他的模块来使用。

到此,我们已经完成了 Utils 模块。下面来看一下 Bootstrap 模块。

2.2.3 Bootstrap模块

1.Import Packages

代码如下:

javax.servlet
javax.servlet.http 
org.apache.commons.logging 
org.osgi.framework 
org.osgi.service.http 
org.osgichina.petstore.util 
org.springframework.beans.factory 
org.springframework.core.io 
org.springframework.osgi.context

2.Required Plug-ins

代码如下:

org.springframework.bundle.spring

3.增加接口定义

代码如下:

 org.osgichina.petstore.bootstrap.actionhandler.ActionHandler org.osgichina.petstore.bootstrap.actionhandler.ActionHandlerMap org.osgichina.petstore.bootstrap.menu.MenuItem org.osgichina.petstore.bootstrap.pagetemplate.DefaultPage org.osgichina.petstore.bootstrap.pagetemplate.PageFooter org.osgichina.petstore.bootstrap.pagetemplate.PageHeader

4.辅助的Bean的实现

在 Bootstrap 中,我们实现了两个辅助的 Bean:

MenuItemInfo,用户存储菜单项相关的信息。

DefaultActionHandlerMap。实现了 ActionHandlerMap,用于实现 URI 到 ActionHandler 的映射关系的管理。

5.PageHeader的实现-PageHeaderImpl

在 PageHeaderImpl 中,我们管理了其他模块的菜单和展示。从代码上来说主要分为两个部分: 一个是菜单对象的管理,一个是展示。那么我们先来看一下菜单对象的管理。

我们在 PageHeaderImpl 中有两个方法:onBind 和 onUnbind,分别是一个菜单对象绑定和接触绑 定会被调用的。代码如下:

public synchronized void onBind(MenuItem menuItem, Map<?,?>  
            serviceProps){ 
    List<MenuItem> list = new LinkedList<MenuItem>();
      if(menuItems != null){ 
        list.addAll(menuItems); 
      } 
      list.add(menuItem);
      menuInfoMap.put(menuItem, menuItem.getMenuItemInfo()); 
      Collections.sort(list, new Comparator<MenuItem>(){
         public int compare(MenuItem o1, MenuItem o2) {
           return menuInfoMap.get(o1).getPosition() - menuInfoMap.   
            get(o2).getPosition(); 
          }     
      }); 
      menuItems = list; 
}

可以看到这个方法做了四件事情:

复制目前的菜单项。

加入新的菜单项。

对菜单项列表排序。

更新菜单项列表对象。 而 onUnbind 做的就是相反的事情了。代码如下:

public synchronized void onUnbind(MenuItem menuItem, Map serviceProps){ List list = new LinkedList(); if(menuItems != null){ list.addAll(menuItems); } menuInfoMap.remove(menuItem); list.remove(menuItem); menuItems = list; }

在 onUnbind 中做了三件事情:

复制目前的菜单项。

删除接触绑定的菜单项。

更新菜单项列表对象。

可以看到在 onUnbind 中,相比 onBind,少了排序的动作。那么,onBind 和 onUnbing 为什么会 被调用到呢?这是因为我们 spring-dm 配置文件中的一个配置,我们看一下是如何配置的。

<osgi:list id="menuItem" interface="org.osgichina.petstore.bootstrap. 
  menu.MenuItem" cardinality="0..N" >
      <osgi:listener bind-method="onBind" 
                   unbind-method="onUnbind"   
                 ref="pageHeader" /> 
</osgi:list>

可以看到,我们在引用 menuItem 的时候,定义了一个 listener,指定了这个 listener 的 bean 以及在 bind 和 unbind 的时候要调用的方法。这样,我们的 PageHeaderImpl 的 onBind 和 onUnbind 方法就可以在 menuItem 的服务启动或者停止的时候被调用了。

在解决了 MenuItem 服务的引用问题后,就是展示的问题了。我们是在 PageHeaderImpl 的 getPageHeader 方法中生成了展示的 HTML。这里就不列出详细的代码了。

1.PageFooter的实现-PageFooterImpl

相对于 PageHeaderImpl,PageFooterImpl 的实现就非常简单了。在 PageFooterImpl 这个实现中, 我们只是在 getPageFooter 中返回了一个版权的信息。

public String getPageFooter(String servletPath, String resourcePath) {
 return "OSGi原理与最佳实践"; }

2.ControllerServlet实现

ControllerServlet 是我们 Petstore 中的控制 Servlet。在 ControllerServlet 中,负责响应用户的请求,并根据请求的 URI 把请求的处理派发给相应的 ActionHandler,负责渲染请求的响应结果。在 ControllerServlet 中,管理了 PageHeader、PageFooter,以及各个模块提供的 DefaultPage 的实现,重要的是一个 ActionHandlerMap,用于进行请求派发的处理。我们简单看下 ControllerServlet 中对于 HTTP 请求的处理部分。

  String pathInfo = request.getPathInfo();
  if(pathInfo != null && pathInfo.length() > 0){ 
      String tempString = null; 
      while(true){ 
        tempString = pathInfo.replaceAll("//", "/");
         if(tempString.equals(pathInfo)){ 
            break; 
        }         } 
    pathInfo = pathInfo.substring(1); 
    int index = pathInfo.indexOf("?");
     if(index > 0){ 
        pathInfo = pathInfo.substring(0, index - 1); 
    } 
}

上面这段代码对 HTTP 请求的 pathInfo 进行分析,得到去掉请求的参数后的路径信息。

if(null == pathInfo || pathInfo.length() == 0){
     if(this.defaultPages.size() > 0){ 
        pathInfo = this.defaultPages.get(0).getUri(); 
    } 
}

而这部分的代码是在没有路径信息的情况下使用默认页面。类似 http://localhost/petstore/app 这样的请求,得到的 pathInfo 是 null。

if(pathInfo != null){ 
   actionHandler = this.actionHandlerMap.get(pathInfo.toLowerCase()); 
}

如果获取了路径信息,则从 actionHandlerMap 中查询对应的 ActionHandler。

根据路径信息得到了对应的 ActionHandler 后,我们就开始渲染页面了,加入在返回 HTML 中的 信息,并且按照先后顺序加入 Header,页面内容和 Footer 信息。然后返回给用户。

3.数据初始化实现

为了简化例子,我们使用了 HSQLDB 作为数据库,在 Bootstrap 启动的时候,自动加载数据。主 要通过两个类来实现。

HsqldbServerBean

这个类的功能是以 Server 方式启动 HSQLDB。主要的启动代码在 afterPropertiesSet()这个方法中。

public void afterPropertiesSet() throws Exception {
     if ((params == null) || params.isEmpty()) { 
        throw new IllegalArgumentException("missing hsqldb params"); 
    } 
    server = new Server(); 
    HsqlProperties props = new HsqlProperties(params); 
    server.setProperties(props);
     server.setLogWriter(null); 
    server.setErrWriter(null);
     server.start();    
 }

DataLoader

这个类用于创建表并加载数据。主要的工作是从配置文件中读取 SQL,并执行 SQL(包括建表的 SQL 和初始化数据的 SQL)。具体的代码可以参考源码中的 PetStore\Bootstrap 下的 DataLoader.java。

4.Exported Packages

 org.osgichina.petstore.bootstrap.actionhandler
 org.osgichina.petstore.bootstrap.menu
 org.osgichina.petstore.bootstrap.pagetemplate

5.ClassPath

在 Bootstrap 中,我们要用到 commons-dbcp、commons-pool 和 hsqldb 这几个 jar 包。我们的方案是在 Bootstrap 中增加一个 lib 目录,把这三个 jar 包放到 lib 目录下,并且在 Bundle 的 ClassPath 中加入这三个 jar 包。

这个时候,我们已经可以启动了,下面来看目前的成果。

启动我们的工程,然后在浏览器中输入 http://localhost/petstore/app,将会看到类似如图 2-10 的现实。

【图 2-10 当前 PetStore 浏览器上的展示 】

恭喜你,我们的 Petstore 已经可以工作了。

查看评论