WEB开发网
开发学院软件开发Java Apache Geronimo 上全局目录树实现以及自定义资源... 阅读

Apache Geronimo 上全局目录树实现以及自定义资源开发与使用

 2010-02-24 00:00:00 来源:WEB开发网   
核心提示:服务器端全局目录树什么是全局目录树JNDI(Java Naming and Directory Interface)是一组应用程序访问命名和目录服务的 API, 通过它客户程序可以以统一的方式访问多种目录服务,Java EE 应用服务器通常都会集成一个 目录树的实现 , 以维护一个服务器端全局共享目录树,Apache

服务器端全局目录树

什么是全局目录树

JNDI(Java Naming and Directory Interface)是一组应用程序访问命名和目录服务的 API, 通过它客户程序可以以统一的方式访问多种目录服务。Java EE 应用服务器通常都会集成一个 目录树的实现 , 以维护一个服务器端全局共享目录树。 以此 , 服务器可以将一些资源绑定到目录树上 , 比如说数据库连接池,JMS 资源等 , 而客户端应用程序则可以通过标准的 JNDI 接口等方式来获取这些资源引用。所谓全局 , 是相对 java:comp/env 这个本地目录而言 , 对于后者 , 每个程序组件都有属于自己空间 , 换句话说 , 每个应用程序组件在检索 java:comp/env 获取的内容是独立和不相关的。 在本文的后续示例中 , 我们会介绍如何将全局目录树上的对象映射到本地目录。

XBean-Naming

XBean-Naming 是 Geronimo 子项目 XBean 中的一个组件 , 关于 Apache XBean, 可参阅 http://geronimo.apache.org/xbean/ 。它提供了一个单虚拟机的目录树实现,同时还提供了一些扩展实现功能。

由于采用了 XBean-Naming 作为 Geronimo 目录树实现,故而在 Geronimo 启动时,会将以下属性设置为系统属性:

java.naming.factory.initial = org.apache.xbean.naming.global.GlobalContextManager java.naming.factory.initial 包含 Initial Context Factory 类名

java.naming.factory.url.pkgs = org.apache.xbean.naming java.naming.factory.url.pkgs 以冒号间隔多个包名前缀 , 将用于 URL 上下文工厂中的载入操作。

java.naming.provider.url = rmi://localhost:1099 java.naming.provider.url 提供命名服务注册地址

运行于 Geronimo 中的用户程序 , 如果需要访问全局目录树 , 则在创建 InitialContext 对象时 , 无需指定以上属性 , 设置会自动从系统属性中读取。 此处需要强调地是 , 用户在 Geronimo 运行环境中如需读取其他 JNDI 目录 , 则需显式指定以上属性值 , 例如在 Geronimo 中读取远端 EJB 对象引用的时候 , 则需要指定 OpenEJB 专有的上述属性值。

Geronimo 全局目录树上资源绑定实现机制

神奇的 ResourceBinding GBean 和 ResourceSource 接口

在 Geronimo 中 , 每当通过控制台或者部署文件创建一个数据库连接池或者 JMS 资源后 , 对应的 ConnectionFactory 和 JMS Queue/Topic 将会以一定的格式绑定到全局的目录树上。那么 Geronimo 平台是如何感知到新的资源被创建并将其绑定到全局目录树上的呢? 起关键作用的就是 ResourceBinding GBean 和 ResourceSource 接口。我们来具体看看这两个类的结构和实现。

图 1. ResourceBinding 类继承结构
Apache Geronimo 上全局目录树实现以及自定义资源开发与使用

如 图 1所示 , ResourceBinding 最上层的父类是 XBean-Naming 项目的 WritableContext, 名如其义 , 可以通过其提供的方法往目录树上绑定和卸载资源。 从继承结构而知 , ResourceBinding GBean 没有直接继承 WritableContext。 继承链多了 KernelContextGBean 和 GBeanFormatingBinding 两个类。在关注这些类的作用之前 , 首先看一下 ResourceBinding GBean 运行时的配置文件。 如 清单 1所示 , 它共有三个属性值 , 分别是 :

format, 用于指定被绑定资源在全局目录树中的路径格式。

nameInNamespace, 用于指定当前绑定资源所在的命名空间。

abstractNameQuery 用户指定资源查询条件。

清单 1. ResourceBinding 配置片段

<gbean name="ResourceBindings" class="org.apache.geronimo.gjndi.binding.ResourceBinding"> 
  <attribute name="format">{groupId}/{artifactId}/{j2eeType}/{name}</attribute> 
  <attribute name="nameInNamespace">jca:</attribute> 
  <attribute name="abstractNameQuery"> 
    org.apache.geronimo.naming.ResourceSource 
  </attribute> 
 </gbean> 

首先来看 KernelContextGBean, 众所周知 , 在 Geronimo 的世界中 , 几乎所有组件和资源都以 GBean 的形式存在并运行于 Geronimo 内核之中 , 显而易见 , 在将资源绑定到全局目录树之前 , Geronimo 必需从内核中先检索到资源对应的 GBean 对象 , 而 KernelContextGBean正是应此而生 , 它实现了从内核中检索 GBean, 进而将其绑定到全局目录树的功能。

如 清单 2所示 , 在 KernelContextGBean启动时 , 在其 doStart 方法中 , 它首先向 Geronimo 内核注册一个监听器 , 监听内容正是清单 1 中所示 abstractNameQuery 属性所指定的“org.apache.geronimo.naming.ResourceSource”, 即所有实现了 ResourceSource接口的 GBean。

当 KernelContextGBean向内核注册了该监听器之后 , Geronimo 内核会在符合条件的 GBean 生命周期的各个阶段时 , 回调监听器中的对应方法。 例如 , 当一个实现 ResourceSource接口的 GBean 在内核中成功处于运行状态后 , ContextLifecycleListener的 running 方法将会被调用 , 而该 GBean 停止后 , stopped 方法即会被调用。而往全局目录树上绑定和卸载资源的操作正是在这些回调方法中被执行。 当代表资源的 GBean 成功启动后 , 即表明它以及初始化完毕 , 此时在 running 方法中将其绑定到全局目录树上 ; 相反 , 当该 GBean 停止后 , 表示其所代表的资源以及被销毁 , 那就需要将其从全局目录树上卸载掉。

由此 , Geronimo 实现了透明绑定对象至全局目录树 , 对于资源的创建者而言 , 不需要考虑往全局目录树上的绑定和卸载操作 , 只要对于 GBean 实现了 ResourceSource接口 , 一切均由 KernelContextGBean代劳。

值得注意的是 , 在 doStart 方法中还显式查询当前内核中实现了 ResourceSource接口的 GBean , 并执行绑定操作 , 原因是可能会有符合条件的 GBean 在注册监听器之前已经在内核中运行。

清单 2. KernelContextGBean 代码片段

…… 
 public synchronized void doStart() { 
  kernel.getLifecycleMonitor().addLifecycleListener(listener, abstractNameQuery); 
  Set<AbstractName> set = kernel.listGBeans(abstractNameQuery); 
  for (AbstractName abstractName : set) { 
    try { 
      if (kernel.isRunning(abstractName)) { 
        addBinding(abstractName); 
      } 
    } catch (NamingException e) { 
      log.error("Error adding binding for " + abstractName, e); 
    } 
  } 
 } 
…… 
 private class ContextLifecycleListener extends LifecycleAdapter { 
  public void running(AbstractName abstractName) { 
    try { 
      addBinding(abstractName); 
    } catch (NamingException e) { 
      log.error("Error adding binding for " + abstractName); 
    } 
  } 
  public void stopping(AbstractName abstractName) { 
    removeBinding(abstractName); 
  } 
  …… 
 } 
…… 
 protected Map<Name, Object> createBindings(AbstractName abstractName, Object 
  value) throws NamingException { 
  // generate a name for this binding 
  Name name = createBindingName(abstractName, value); 
  …… 
  // give sub classes a chance to preprocess the value 
  value = preprocessValue(abstractName, name, value); 
  …… 
 } 

在了解了 KernelContextGBean的作用之后 , 我们将目光先移至 ResourceSource和 ResourceBindingGBean 上来。 如 清单 3所示,ResourceSource接口比较简单 , 只有一个 $getResource()方法。从 清单 4所示可得知 , 该方法会在 ResourceBinding的 preprocessValue中被调用 , 而 preprocessValue又会在 KernelContextGBean的 createBindings方法中被调用。 以此可以得出 , 最终绑定到全局目录树上的并不是该资源 GBean 对于本身 , 而是调用其 $getResource方法的返回值。

一个有趣的现象是 , ResourceSource接口中定义的 $getResource方法以少见的 $ 符号开始 , 原因之一是尽量避免与用户的自定义类中的方法同名。 用户可以通过继承现有的类并实现 ResourceSource接口达到资源绑定的目的。

清单 3. ResourceSource 接口

 package org.apache.geronimo.naming; 
 public interface ResourceSource<E extends Throwable> { 
  Object $getResource() throws E; 
 } 


清单 4. ResourceBinding 代码片段

 @Override 
 protected Object preprocessValue(AbstractName abstractName, Name name, 
  Object value) { 
  if (!(value instanceof ResourceSource)) { 
    log.info("value at " + abstractName + " is not a ResourceSource: " + 
      value.getClass().getName()); 
    return null; 
  } 
  try { 
    return ((ResourceSource) value).$getResource(); 
  } catch (Throwable throwable) { 
    log.info("Could not get resource from gbean at " + abstractName,throwable); 
    return null; 
  } 
 } 

资源绑定路径格式

在 Geronimo 中,用户可以通过管理控制台调试视图下的 JNDI 查看器浏览全局 JNDI 目录树上绑定的所有资源,如 图 2所示:

图 2. 全局目录树资源列表
Apache Geronimo 上全局目录树实现以及自定义资源开发与使用

查看原图(大图)

如 图 2所示 , 在 Geronimo 的全局目录树上绑定了大量的对象 , 我们主要关注以 jca: 为前缀的资源 , 这些资源正是通过 清单 1所定义的 ResourceBinding GBean 绑定的。 通过观察 jca: 命名空间中的资源 , 它们的绑定路径似乎都有统一的格式。 那么绑定的路径是如何确定的呢 ? 起作用的正是 图 1中所显示 GBeanFormatBinding 类。 它会通过解析 清单 1所示的 format 属性值来生成对应的绑定路径。

以 图 2中一个 JMS 的 ConnectionFactory 为例 , 其显示的完整路径是 : /jca:/org.apache.geronimo.configs/activemq-ra /JCAManagedConnectionFactory/DefaultActiveMQConnectionFactory。

表格 1. 资源路径组成表

 描述
jca:命名空间 , 由 ResourceBinding 的 nameInNamespace 属性指定
org.apache.geronimo.configs资源所在应用程序的 groupId
activemq-ra资源所在应用程序的 artifactId
JCAManagedConnectionFactory资源对应的 GBean 的 j2eeType 值
DefaultActiveMQConnectionFactory资源对应的 GBean 的名称

事实上 , 用户还可以通过修改 var/config/ config-substitutions.properties 文件中的 ResourceBindingsFormat属性来实现自定义的路径格式 , 在下次启动时 , Geronimo 会从该配置文件中读取最新的路径格式。 例如 , 我们希望在路径中包含应用程序版本的信息 , 则修改该属性的值为 {groupId}/{artifactId}/{j2eeType}/{version}/{name}。 重新启动 Geronimo 服务器后 , 通过控制台的 JNDI 查看器 , jca 命名空间下的所有资源的绑定路径中包含了版本信息 , 如 图 3所示 :

图 3. 修改路径格式后的全局目录树资源列表
Apache Geronimo 上全局目录树实现以及自定义资源开发与使用

查看原图(大图)

回到 图 2, 如果细心观察 , 可以发现 MailSession 这个资源对象同时被绑定在 ger:/MailSession 和 jca:/org.apache.geronim.configs/javamail/JavaMailResource/mail/MailSession 上。 后者是通过 ResourceBindingGBean 的自动绑定实现的 , 而前者则是如何实现的呢 ? 事实上 , 用户可以通过标准的 Context 接口 , 以编程的方式将资源绑定到其他全局路径上。

如 清单 5和 清单 6所示 , 在配置文件中 , 通过 jndiName 属性指定了绑定路径 , 而在 doStart方法中 , 通过标准 Context接口的 bind方法 , 将资源绑定在 jndiName属性指定的路径上。 通过控制台的 JNDI 查看器可以看到 , MailSession 除了默认的绑定位置之外 , 还同时被绑定在 ger:/MailSession 路径上。

清单 5. Mail Session 代码片段

 public void doStart() throws Exception { 
  …… 
  String jndiName = getJndiName(); 
  if (jndiName != null && jndiName.length() > 0) { 
    // first get the resource incase there are exceptions 
    Object value = $getResource(); 
    // get the initial context 
    Context context = new InitialContext(); 
    Name parsedName = context.getNameParser("").parse(jndiName); 
    // create intermediate contexts 
    for (int i = 1; i < parsedName.size(); i++) { 
      Name contextName = parsedName.getPrefix(i); 
      if (!bindingExists(context, contextName)) { 
        context.createSubcontext(contextName); 
      } 
    } 
  // bind 
  context.bind(jndiName, value); 
  …… 
  } 
 } 


清单 6. Mail Session 配置片段

 <gbean name="mail/MailSession" class="org.apache.geronimo.mail.MailGBean"> 
  <attribute name="transportProtocol">smtp</attribute> 
  <attribute name="jndiName">ger:/MailSession</attribute> 
  <reference name="Protocols"/> 
 </gbean> 

如何编写自定义资源以及访问

上面已经介绍了 Geronimo 中全局目录树上资源绑定实现的机制 , 事实上 , 在 Geronimo 中 , 资源不仅仅限定于常见的数据库连接池等 , 从上文介绍的实现来看 , 它可以是任何 Java 对象 , 只需要实现 ResourceSource接口。 下面我们来具体举例说明如何定义自己的资源并通过上述的特性将其自动绑定到全局目录树上,然后通过各种方式来访问该资源。以此达到提高应用程序开发效率和提升应用程序结构灵活性的目的。

这里我们假设一个蛋糕制作和订购的场景 , 在该场景中存在两种角色 , 一个是蛋糕厂商 , 另外一个是顾客。 蛋糕厂商将提供制作多种蛋糕的实现 , 而顾客则通过一个 Web 客户端 , 通过指定蛋糕的类型订购所需蛋糕。

定义对象:蛋糕工厂

清单 7. CakeFactory 接口

 package org.apache.geronimo.resource; 
 public interface CakeFactory { 
  public Cake createCake(String type) throws CakeCreationException; 
 } 


清单 8. CakeMaker 接口

 package org.apache.geronimo.resource; 
 public interface CakeMaker { 
  public Cake makeCake() throws CakeCreationException; 
  public String getSupportedCakeType(); 
 } 


清单 9. CreamCakeMaker 代码片段

 public class CreamCakeMaker implements CakeMaker { 
  public Cake makeCake() throws CakeCreationException { 
    return new CreamCake(); 
  } 
  public String getSupportedCakeType() { 
    return "cream"; 
  } 
  …… 
 } 

在 清单 7中 , 定义了 CakeFactory接口 , 用户通过其 createCake方法 , 用户可以指定蛋糕类型来订购蛋糕 , 后续我们将实现该接口 , 并将其作为资源绑定到全局目录树上。

在 清单 8中 CakeMaker则为蛋糕厂商实现的接口 , 其定义了 makeCake和 getSupportedCakeType的方法 , 分别提供制作蛋糕的功能和返回当前实现支持制作的蛋糕类型。 在 清单 9中 , 我们实现了一个 CreamCakeMaker, 用于制作奶油蛋糕。

清单 10. GenericCakeFactoryGBean 代码片段

 public class GenericCakeFactoryGBean implements CakeFactory, 
  ResourceSource<CakeCreationException>, GBeanLifecycle { 
  …… 
  public void doStart() throws Exception { 
    lifecycleListener = new ContextLifecycleListener(); 
    AbstractNameQuery abstractNameQuery = new AbstractNameQuery(new 
      URI("?#org.apache.geronimo.resource.CakeMaker")); 
    kernel.getLifecycleMonitor().addLifecycleListener(lifecycleListener, 
      abstractNameQuery); 
    Set<AbstractName> set = kernel.listGBeans(abstractNameQuery); 
    for (AbstractName abstractName : set) { 
      try { 
        if (kernel.isRunning(abstractName)) { 
          addCakeMaker(abstractName); 
        } 
      } catch (Exception e) { 
      } 
     } 
  } 
  …… 
  private void addCakeMaker(AbstractName abstractName) { 
    try { 
      CakeMaker cakeMaker = (CakeMaker) kernel.getGBean(abstractName); 
      cakeTypeMakerMap.put(cakeMaker.getSupportedCakeType(), cakeMaker); 
      abstractNameTypeMap.put(abstractName, 
        cakeMaker.getSupportedCakeType()); 
    } catch (GBeanNotFoundException e) { 
    } 
  } 
  …… 
  public Object $getResource() throws CakeCreationException { 
      return this; 
  } 
 } 

而 清单 10中 , 则是 CakeFactory的实现 , 类似上文的 ResourceBindingGBean 的实现 , 在 doStart方法中注册了对实现 CakeMaker 接口的 GBean 的监听 , 任何 CakeMaker 的实现在启动后都会被放到以蛋糕类型为键值的 Map 中 , 当用户订购指定类型的蛋糕时 , 通过查询 Map可以获取 CakeMaker的实现。 尤其要注意的是 , GenericCakeFactoryGBean除了实现 CakeFactory接口之外 , 还实现了上文提及的 ResourceSource接口 , 这样 , Geronimo 会被告知该资源需要绑定到全局目录树上 , 绑定的内容为 $getResource 方法的返回值 , 在本例中 , $getResource 直接返回当前实例的引用。

部署资源

通过部署描述文件部署资源,如 清单 11

清单 11. 蛋糕工厂部署文件 geronimo-service.xml

 <?xml version="1.0" encoding="UTF-8"?> 
 <module xmlns="http://geronimo.apache.org/xml/ns/deployment-1.2"> 
  <environment> 
    <moduleId> 
      <groupId>org.apache.geronimo.resource</groupId> 
      <artifactId>cakeFactory</artifactId>       
      <version>1.0</version> 
      <type>car</type> 
    </moduleId> 
    <dependencies> 
      <dependency> 
        <groupId>org.apache.geronimo.framework</groupId> 
        <artifactId>rmi-naming</artifactId> 
      </dependency> 
    </dependencies> 
  </environment> 
  <gbean name="GenericCakeFactory" 
    class="org.apache.geronimo.resource.GenericCakeFactoryGBean"> 
  </gbean> 
  <gbean name="JamCakeMaker" 
    class="org.apache.geronimo.resource.JamCakeMaker"> 
  </gbean> 
  <gbean name="CreamCakeMaker" 
    class="org.apache.geronimo.resource.CreamCakeMaker"> 
  </gbean> 
 </module> 

创建 清单 11所示的部署文件 geronimo-service.xml, 并将其放置于 META-INF 目录下。 通过控制台部署后 , 在 JNDI 查看器中应该可以看到自定义的 CakeFactory 已经被绑定到全局的目录树上 , 如 图 4所示。

图 4. 应用部署后的全局目录树结构图
Apache Geronimo 上全局目录树实现以及自定义资源开发与使用

查看原图(大图)

访问资源:蛋糕订购客户端

蛋糕订购客户端是一个简单的 Web 应用程序 , 用户通过界面可以选择类型 , 并返回指定的蛋糕。 我们将着重介绍如何通过多种方式在客户端获取到 CakeFactory 的实现 , 本实例中的相关逻辑是在一个 Servlet 中实现。

通过检索 Context 获取资源

既然 CakeFactory 已经被绑定到全局目录上 , 则可以通过标准的 JNDI 接口获取。 从 Context 中读取有两种方式 , 一种是直接从全局目录树中读取 , 另外一种是将其映射到本地目录结构中再读取。

清单 12. 全局目录读取方式

 private CakeFactory getCakeFactoryByGlobalJNDILookUp() { 
  try { 
    Context context = new InitialContext(); 
    CakeFactory cakeFactory = (CakeFactory) 
     context.lookup( 
        "jca:/org.apache.geronimo.resource/cakeFactory/GBean/GenericCakeFactory"); 
    return cakeFactory; 
  } catch (NamingException e) { 
    return null; 
  } 
 } 


清单 13. 本地目录读取方式

 private CakeFactory getCakeFactoryByLocalJNDILookUp() { 
  try { 
    Context context = new InitialContext(); 
    CakeFactory cakeFactory = (CakeFactory) 
      context.lookup("java:comp/env/GenericCakeFactory"); 
    return cakeFactory; 
  } catch (NamingException e) { 
    return null; 
  } 
 } 

如 清单 12所示 , 通过标准的 JNDI 接口并指定 CakeFactory 在全局目录树上的完整路径即可获取其引用。 同样 , 通过在配置文件中配置 gbean-ref 和 resource-env-ref, 将其从全局目录树映射到本地目录 java:comp/env/GenericCakeFactory, 也可以获取其引用。 比较这两种方式 , 本地目录读取方式显得更加灵活和可配置。 当全局绑定路径发生变化后 , 后者通过修改配置文件 , 无需改动代码即可正常工作。

通过资源注入获取资源

在 Java EE 5 规范中 , 引入了通过 Resource 标识注入的方式 , 其后台实现等同与本地目录读取方式 , 但是对于开发人员而言 , 代码显得更加简洁 , 无需使用繁琐的 JNDI 接口获取引用。 如清单 14 所示 , 通过 Resource 标识 , 并指定其在本地目录中的名称 , 即可以获取 CakeFactory 的引用。 当然 , 由于规范的限定 , Java EE 平台只支持在 Servlet, ManagedBean, EJB 等组件中使用资源注入 , 对于用户自定义的对象 , 只能通过标准 JNDI 接口获取资源的引用。

清单 14. 通过资源注入获取方式

 @Resource(name = "GenericCakeFactory") 
 private CakeFactory genericCakeFactory; 
 private CakeFactory getCakeFactoryByInjectionResource() { 
  return genericCakeFactory; 
 } 

通过 Geronimo Kernel 获取资源

如前文所介绍 , 在 Geronimo 中 , 任何资源都是以 GBean 的形式运行于 Geronimo 内核之中 , 故而可以通过 Kernel 对象获取 CakeFactory的引用。

清单 15. 通过 Geronimo Kernel 获取方式

 private CakeFactory getCakeFactoryByKernelLookUp() { 
  try { 
    Kernel kernel = KernelRegistry.getSingleKernel(); 
    CakeFactory cakeFactory = (CakeFactory) kernel.getGBean("GenericCakeFactory"); 
    return cakeFactory; 
  } catch (Exception e) { 
    return null; 
  } 
 } 

依次部署本文所附示例中的 simpleResource.jar 和 SimpleWebApp.war 后 , 打开 http://localhost:8080/SimpleWebApp 页面后 , 如 图 5所示 , 用户可以选择使用何种方式获取 CakeFactory 资源引用 , 并指定蛋糕类型来订购蛋糕。

图 5. 示例页面
Apache Geronimo 上全局目录树实现以及自定义资源开发与使用

查看原图(大图)

结束语

本文以 Apache Geronimo 为平台,介绍了其全局目录树的实现以及资源的绑定方式 , 并举例说明如何编写自定义的资源以及在应用程序中获取资源的应用。 在实际的应用场景中 , 用户可以使用该方式定义并部署线程池 , 对象池等资源 , 并依据具体场景采用合适的方式获取资源。 在多个应用程序中共享这些全局资源 , 既使资源得以高效利用 , 同时提高了应用程序灵活度和可配置性。

本文示例源代码或素材下载

Tags:Apache Geronimo 全局

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接