JNDI 在 J2EE 中的角色

把您从麻烦中解脱html

掌握 J2EE 是件使人生畏的事,由于它包含的技术和缩略语在不断地增加。Java 命名和目录接口(Java Naming and Directory Interface,JNDI)从一开始就一直是 Java 2 平台企业版(JEE)的核心,可是 J2EE 开发新手常常用很差它。本文将消除 JNDI 在 J2EE 应用程序中所扮演角色的神秘性,并展现它如何帮助应用程序从部署细节中解脱出来。java

虽然 J2EE 平台提升了普通企业开发人员的生活水平,可是这种提升是以不得不学习许多规范和技术为代价的,这些规范和技术则是 J2EE 为了成为无所不包的分布式计算平台而整合进来的。Dolly Developer 是众多开发人员中的一员,她已经发现了一个特性,该特性有助于缓解随企业级应用程序部署而带来的负担,这个特性就是 JNDI,即 Java 命名与目录接口(Java Naming and Directory Interface)。让咱们来看看 Dolly 在没有 JNDI 的时候是怎么作的,以及她是如何正确地应用 JNDI 来改善其情况的。mysql

全部人都很是熟悉的旅程

Dolly Developer 正在编写使用 JDBC 数据源的 Web 应用程序。她知道本身正在使用 MySQL,因此她将一个对 MySQL JDBC 驱动程序类的引用进行了编码,并经过使用适当的 JDBC URL 链接到其 Web 应用程序中的数据库。她认识到数据库链接池的重要性,因此她包含了一个链接池包,并把它配置成最多使用 64 个链接;她知道数据库服务器已经被设置成最多容许 128 台客户机进行链接。web

Dolly 在走向灾难

在开发阶段,每件事都进行得很顺利。可是,在部署的时候,开始失控。Dolly 的网络管理员告诉她,她不能从她的桌面机访问生产服务器或登台服务器(staging server),因此她不得不为每一个部署阶段开发不一样的代码版本。由于这种状况,她须要一个新的 JDBC URL,因此还要为测试、阶段和生产进行独立的部署。(一听到要在每一个环境中创建单独部署,熟悉配置管理的人会战战兢兢的,可是既然这是种很是广泛的状况,因此他们也只好硬着头皮上了。)sql

就在 Dolly 认为经过不一样的 URL 创建彼此独立的部署已经解决了本身的配置问题时,她发现她的数据库管理员不想在生产环境中运行 MySQL 实例。他说,MySQL 用做开发还能够,可是对于任务关键型数据而言,业务标准是 DB2®。如今她的构建不只在数据库 URL 方面有所不一样,并且还须要不一样的驱动程序。数据库

事情越变越糟。她的应用程序很是有用,而且变得很是关键,以至于它从应用服务器那里获得了故障恢复的能力,并被复制到 4 个服务器集群。可是数据库管理员提出了抗议,由于她的应用程序的每一个实例都要使用 64 个链接,而数据库服务器总共只有 200 个可用链接 —— 所有都被 Dolly 的应用程序占用了。更麻烦的是,DBA 已经肯定 Dolly 的应用程序只须要 32 个链接,并且天天只有一个小时在使用。随着她的应用程序规模扩大,应用程序遇到了数据库级的争用问题,而她的唯一选择就是改变集群的链接数量,并且还要作好准备,在集群数量增加或者应用程序复制到另外一个集群时再重复一次这样的操做。看来她已经决定了如何配置应用程序,应用程序的配置最好是留给系统管理员和数据库管理员来作。编程

 

J2EE 的角色

若是 Dolly 在开发应用程序时了解 J2EE 所扮演的角色,那么她就可能避免遭遇这种困境。J2EE 规范把职责委托给多个开发角色:组件提供者(Component Provider)、应用程序组装者(Application Assembler)、部署人员(Deployer)和系统管理员(System Administrator)。(在许多公司中,组件提供者和组件组装者的角色是融合在一块儿的,部署人员和系统管理员的角色是融合在一块儿的。)在真正了解 J2EE 中的 JNDI 角色以前,掌握 J2EE 角色的做用很是重要。安全

组件提供者
这个角色负责建立 J2EE 组件,J2EE 组件能够是 Web 应用程序、企业级 JavaBean(EJB)组件,或者是应用程序客户机(例如基于 Swing 的 GUI 客户机应用程序)。组件提供者包括:HTML 设计师、文档编程人员以及其余开发人员角色。大多数 J2EE 开发人员在组件提供者这一角色上耗费了至关多的时间。
应用程序组装者
这个角色将多个 J2EE 模块捆绑成一个彼此结合的、能够部署的总体:企业归档(EAR)文件。应用程序组装者要选择组件,分清它们之间的交互方式,配置它们的安全性和事务属性,并把应用程序打包到 EAR 文件中。许多 IDE,例如 WebSphere® Studio、IDEA、JBuilder、WebLogic Workshop 和其余 IDE,均可以帮助应用程序组装者以交互方式配置 EAR 文件。
部署人员(Deployer)
这个角色负责部署,这意味着将 EAR 安装到 J2EE 容器(应用服务器)中,而后配置资源(例如数据库链接池),把应用程序须要的资源绑定到应用服务器中的特定资源上,并启动应用程序。
系统管理员(System Administrator)
这个角色负责保证容器须要的资源可用于容器。

角色实战

假设有一个企业应用程序,该应用程序包含一个 Web 应用程序,还有一个负责业务逻辑和持久性的 EJB 组件。开发这个应用程序的组件供应商可能有许多,可是在许多状况下,能够由一我的来承担所有职责。组件能够包含数据传输对象(一个 JAR 文件)、EJB 接口(另外一个 JAR 文件)、EJB 实现自己(另外一个 JAR 文件),以及用户界面组件 —— servlet、JSP、HTML 页面和其余静态 Web 内容。用户界面组件被进一步打包成 Web 应用程序,其中包含 servlet 类、JSP 文件、静态内容,以及其余必需组件的 JAR(包括 EJB 接口)。服务器

这听起来好像用到的组件太多了,几乎超出了人的想像范围,尤为是在考虑构建一个典型的 Web 应用程序须要使用多少个 JAR 文件的时候。可是,重要的是认识到在这里必须当心地管理依赖性。接口和传输对象是 Web 应用程序和 EJB 实现能够依赖的对象,可是依赖性的方向应该是相同的;还要避免产生循环依赖。J2EE 组件(例如 WAR 文件和 EJB JAR 文件)必须在它们的部署单元以外声明它们在资源上的依赖性。网络

应用程序组装者负责把 Web 应用程序中的依赖内容包含进来,并把它们总体打包成单个企业应用程序。工具在这里帮助很大。IDE 能够协助建立反映模块和 JAR 依赖性的项目结构,还容许您随意指定包含或排除的模块。

部署人员负责确保部署环境中存在组件所需的资源,并将组件绑定到平台的可用资源上。例如,Web 应用程序中的外部 EJB 引用(部署描述符中的 ejb-ref)就是在此时被绑定到实际部署的 EJB 组件 —— 并且是当即绑定。

外部资源的后绑定

任何不平凡(nontrivial)的 J2EE 应用程序都须要访问描述它指望使用环境的信息。这意味着开发和测试组件时,为了临时测试代码,开发人员要承担一些部署方面的职责。重要的是要理解:这么作的时候,您就走出了开发人员的领域。不然,能够试着依靠 JDBC 驱动程序,或 URL、JMS 队列名称,或者其余具备无心识的、偶尔多是灾难性暗示的机器资源。

 

JNDI 前来援助

Dolly 的问题的解决方案是从她的应用程序中清除全部对数据存储的直接引用。没有对 JDBC 驱动程序的引用,没有服务器名称,没有用户名称或口令 —— 甚至没有数据库池或链接管理。Dolly 须要编写代码来忽略将要访问的特定外部资源,只须要知道其余人会提供使用这些外部资源所需的连接便可。这容许部署人员(任何处在这个角色的人)把数据库链接分配给 Dolly 的应用程序。Dolly 没有必要参与其中。(从数据库安全性到遵照 Sarbanes-Oxley 法案,她都没有参与进来,她这样作也有充足的业务理由。)

许多开发人员知道:代码和外部资源之间的紧密耦合是潜在的问题,可是在实践中却常常忘记角色的划分。在小型开发工做中(指的是团队规模或部署规模),即便忽视角色划分也能得到成功。(毕竟,若是应用程序只是我的的应用程序,并且您不许备依靠它,那么把应用程序锁定在特定的 PostgreSQL 实例上也挺好的。)

J2EE 规范要求全部 J2EE 容器都要提供 JNDI 规范的实现。JNDI 在 J2EE 中的角色就是“交换机” —— J2EE 组件在运行时间接地查找其余组件、资源或服务的通用机制。在多数状况下,提供 JNDI 供应者的容器能够充当有限的数据存储,这样管理员就能够设置应用程序的执行属性,并让其余应用程序引用这些属性(Java 管理扩展(Java Management Extensions,JMX)也能够用做这个目的)。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层,这样组件就能够发现所须要的资源,而不用了解这些间接性。

Dolly 的状况更糟了

如今咱们从新来看一下 Dolly 的状况。在其简单的 Web 应用程序中,她直接从应用程序代码中使用了一个 JDBC 链接。参见清单 1,咱们能够看出,Dolly 显式地把 JDBC 驱动程序、数据库 URL 以及她的用户名和口令编码到了 servlet 中:

清单 1. 典型(可是很差)的 JDBC 用法
Connection conn=null;
try {
  Class.forName("com.mysql.jdbc.Driver",
                true, Thread.currentThread().getContextClassLoader());
  conn=DriverManager.getConnection("jdbc:mysql://dbserver?user=dolly&password=dagger");
  /* use the connection here */
  c.close();
} 
catch(Exception e) {
  e.printStackTrace();
} 
finally {
  if(conn!=null) {
    try {
      conn.close();
    } catch(SQLException e) {}
  }
}

  

若是不用这种方式指定配置信息,Dolly(以及她的同伴们)使用 JNDI 来查找 JDBC DataSource 会更好一些,如清单 2 所示:

清单 2. 使用 JNDI 获得数据源
Connection conn=null;
try {
  Context ctx=new InitialContext();
  Object datasourceRef=ctx.lookup("java:comp/env/jdbc/mydatasource");
  DataSource ds=(Datasource)datasourceRef;
  Connection c=ds.getConnection();
  /* use the connection */
  c.close();
} 
catch(Exception e) {
  e.printStackTrace();
} 
finally {
  if(conn!=null) {
    try {
      conn.close();
    } catch(SQLException e) { }
  }
}

  

为了可以获得 JDBC 链接,首先要执行一些小的部署配置,这样咱们才能够在本地组件的 JNDI 下文中查找 DataSource。这可能有点烦琐,可是很容易学。不幸的是,这意味着即便是为了测试组件,开发人员也必须涉足部署人员的领地,而且还要准备配置应用服务器。

配置 JNDI 引用

为了让 JNDI 解析 java:comp/env/jdbc/mydatasource 引用,部署人员必须把 <resource-ref> 标签插入 web.xml 文件(Web 应用程序的部署描述符)。 <resource-ref> 标签的意思就是“这个组件依赖于外部资源”。清单 3 显示了一个示例:

清单 3. resource-ref 入口
<resource-ref>
  <description>Dollys DataSource</description>
  <res-ref-name>jdbc/mydatasource</res-ref-name>
  <res-ref-type>javax.sql.DataSource</res-ref-type>
  <res-auth>Container</res-auth>
</resource-ref>

  

<resource-ref> 入口告诉 servlet 容器,部署人员要在 组件命名上下文(component naming context) 中设置一个叫作jdbc/mydatasource 的资源。组件命名上下文由前缀 java:comp/env/ 表示,因此彻底限定的本地资源名称是:java:comp/env/jdbc/mydatasource.

这只定义了到外部资源的本地引用,尚未建立引用指向的实际资源。(在 Java 语言中,相似的状况多是: <resource-ref> 声明了一个引用,好比 Object foo,可是没有把 foo 设置成实际引用任何 Object。)

部署人员的工做就是建立 DataSource(或者是建立一个 Object 对象,让 foo 指向它,在咱们的 Java 语言示例中就是这样)。每一个容器都有本身设置数据源的机制。例如,在 JBoss 中,是利用服务来定义数据源(请参阅 $JBOSS/server/default/deploy/hsqldb-ds.xml,把它做为示例),它指定本身是 DataSource 的全局 JNDI 名称(默认状况下是 DefaultDS)。在建立资源以后,第三步仍然很关键:把资源链接或者绑定到应用程序组件使用的本地名称。在使用 Web 应用程序的状况下,是使用特定于供应商的部署描述符扩展来指定这个绑定,清单 4 中显示了一个这样的例子。(JBoss 用称为 jboss-Web.xml 的文件做为特定于供应商的 Web 应用程序部署描述符。)

清单 4. 用特定于供应商的部署描述符将资源绑定到 JDI 名称
<resource-ref>
   <res-ref-name>jdbc/mydatasource</res-ref-name>
   <jndi-name>java:DefaultDS</jndi-name>
</resource-ref>

这代表应该将本地资源引用名称( jdbc/mydatasource)映射到名为 java:DefaultDS 的全局资源。若是全局资源名称出于某种缘由发生了变化,而应用程序的代码无需变化,那么只需修改这个映射便可。在这里,有两个级别的间接寻址:一个定义并命名资源(java:DefaultDS),另外一个把特定于本地组件的名称( jdbc/mydatasource)绑定到命名的资源。(实际上,当您在 EAR 级别上映射资源时,可能还存在第三级别的间接寻址。)

 

超越数据源

固然,J2EE 中的资源并不局限于 JDBC 数据源。引用的类型有不少,其中包括资源引用(已经讨论过)、环境实体和 EJB 引用。特别是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另一项关键角色:查找其余应用程序组件。

试想一下这种状况:当一家公司从 Order Ontology Processing Services(OOPS)购买了一个可部署的 EJB 组件来处理客户订单时,会发生什么。为了便于举例说明,咱们把它叫作 ProcessOrders V1.0。ProcessOrders 1.0 有两部分:一组接口和支持类(home 和 remote 接口,以及支持的传输类);实际 EJB 组件自身。选择 OOPS 是由于它在这个领域的专业性。

该公司遵守 J2EE 规范,编写使用 EJB 引用的 Web 应用程序。公司的部署人员把 ProcessOrders 1.0 绑定到 JNDI 树中,将它用做ejb/ProcessOrders/1.0,并解析 Web 应用程序的资源名称,以指向这个全局 JNDI 名称。目前为止,这些都是 EJB 组件很是普通的用法。可是,当咱们考虑公司的开发周期与公司供应商之间的交互时,事情就变得复杂起来。在这里,JNDI 也能帮助咱们。

咱们假设 OOPS 发布了一个新版本,即 ProcessOrders V1.1。这个新版本有一些新功能,公司内部的一个新应用程序须要这些新功能,并且很天然地扩展了 EJB 组件的业务接口。

在这里,公司有如下几个选择:能够更新全部应用程序来使用新版本,也能够编写本身的版本,或者使用 JNDI 的引用解析,容许每一个应用程序在不影响其余应用程序的状况下使用本身的 EJB 组件版本。马上更新全部应用程序对维护来讲是一场噩梦,它要求对全部组件都进行完整的回归测试,这一般是一项艰巨的任务,并且若是发生任何功能测试失败的话,那么还要进行另外一轮调试。

编写内部(in-house)组件经常是没有必要的重复工做。若是组件是由在这个业务领域内具备专业知识的公司编写的,那么给定的 IT 商店不可能像专业的组件供应商那样精通业务功能。

正如您可能已经猜到的那样,最好的解决方案是用 JNDI 解析。EJB 的 JNDI 引用很是相似于 JDBC 资源的引用。对于每一个引用,部署人员都须要把新组件按特定的名称(好比说 ejb/ProcessOrders/1.1)绑定到全局树中,对于须要 EJB 组件的其余每一个组件,还要为组件在部署描述符中解析 EJB 引用。依赖于 V1.0 之前的应用程序不须要进行任何修改,也不须要从新测试,这缩短了实现的时间、下降了成本并减小了复杂性。在服务趋于转换的环境中,这是一种颇有效的方法。能够对应用程序架构中所获得的全部组件进行这类配置管理,从 EJB 组件到 JMS 队列和主题,再到简单配置字符串或其余对象,这能够下降随时间的推移服务变动所产生的维护成本,同时还能够简化部署,减小集成工做。

 

结束语

有一个古老的计算机科学笑话:每一个编程问题均可以仅仅用一个抽象层(或间接的)来解决。在 J2EE 中,JNDI 是把 J2EE 应用程序合在一块儿的粘合剂,但尚未紧到没法让人很容易地把它们分开并从新装配。JNDI 提供的间接寻址容许跨企业交付可伸缩的、功能强大且很灵活的应用程序。这是 J2EE 的承诺,并且通过一些计划和预先考虑,这个承诺是彻底能够实现的。实际上,它要比许多人想像的容易得多。

 

原文:http://www.ibm.com/developerworks/cn/java/j-jndi/index.html