JNDI在J2EE中的角色 (经典讲述,来自IBM developerWork)

JNDI J2EE中的角色

Kirk Pepperdine (kirk@javaperformancetuning.com),首席技术官, JavaPerformanceTuning.com

Joseph Ottinger (joeo@enigmastation.com),咨询顾问

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

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

所有人都熟悉的旅程

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

Dolly在走向灾难

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

就在 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应用程序、企业级 JavaBeanEJB)组件,或者是应用程序客户机(例如基于 Swing GUI客户机应用程序)。组件提供者包括:HTML设计师、文档编程人员以及其他开发人员角色。大多数 J2EE开发人员在组件提供者这一角色上耗费了相当多的时间。 

应用程序组装者

这个角色将多个 J2EE模块捆绑成一个彼此结合的、可以部署的整体:企业归档(EAR)文件。应用程序组装者要选择组件,分清它们之间的交互方式,配置它们的安全性和事务属性,并把应用程序打包到 EAR 文件中。许多 IDE,例如 WebSphere® StudioIDEAJBuilderWebLogic Workshop 和其他 IDE,都可以帮助应用程序组装者以交互方式配置 EAR 文件。 

部署人员(Deployer

这个角色负责部署,这意味着将 EAR安装到 J2EE容器(应用服务器)中,然后配置资源(例如数据库连接池),把应用程序需要的资源绑定到应用服务器中的特定资源上,并启动应用程序。 

系统管理员(System Administrator

这个角色负责保证容器需要的资源可用于容器。

角色实战

假设有一个企业应用程序,该应用程序包含一个 Web应用程序,还有一个负责业务逻辑和持久性的 EJB组件。开发这个应用程序的组件供应商可能有许多,但是在许多情况下,可以由一个人来承担全部职责。组件可以包含数据传输对象(一个 JAR文件)、EJB接口(另一个 JAR文件)、EJB实现本身(另一个 JAR文件),以及用户界面组件 —— servletJSPHTML页面和其他静态 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 驱动程序,或 URLJMS队列名称,或者其他具有无意识的、偶尔可能是灾难性暗示的机器资源。

JNDI前来援助

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

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

J2EE规范要求所有 J2EE容器都要提供 JNDI规范的实现。JNDI J2EE中的角色就是交换机” —— J2EE 组件在运行时间接地查找其他组件、资源或服务的通用机制。在多数情况下,提供 JNDI供应者的容器可以充当有限的数据存储,这样管理员就可以设置应用程序的执行属性,并让其他应用程序引用这些属性(Java管理扩展(Java Management ExtensionsJMX)也可以用作这个目的)。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。这可能有点烦琐,但是很容易学。不幸的是,这意味着即使是为了测试组件,开发人员也必须涉足部署人员的领地,并且还要准备配置应用服务器。

配置JDNI引用

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

清单3resource-ref入口

 

<resource-ref>

  <description>Dollys DataSource</description>

  <res-ref-name>jdbc/mydatasource</res-ref-name>

  <res-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。)

例如在tomcat中,我们可以通过设置~/conf/context.xml文件创建引用指向的实际资源:

<Context>
    <Resource name="jdbc/mydatasource" auth="Container" type="javax.sql.DataSource"  
              username="devsys"  password="devsys" driverClassName="oracle.jdbc.driver.OracleDriver"  
              url="jdbc:oracle:thin:@192.168.1.52:1521:oracle"/>
</Context>

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

清单 4.用特定于供应商的部署描述符将资源绑定到 JNDI名称

<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 ServicesOOPS)购买了一个可部署的 EJB 组件来处理客户订单时,会发生什么。为了便于举例说明,我们把它叫做 ProcessOrders V1.0ProcessOrders 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的承诺,而且经过一些计划和预先考虑,这个承诺是完全可以实现的。实际上,它要比许多人想像的容易得多。

参考资料

  • 您可以参阅本文在 developerWorks全球站点上的 英文原文 
  • Sun Microsystems公司的 The JNDI Tutorial 可以学习更多 JNDI的知识。 
  • “ Using JNDI Outside J2EE摘自 Teach Yourself J2EE in 21 DaysSams Publishing2004年),演示了如何定义正在使用的名称服务类型,以及如何定义网络上的服务器的主机名称和端口号。 
  • developerWorks 专栏作家 Brian Goetz在他的文章“ J2EE or J2SE? JNDI works with both JavaWorld2002 4 月)中,讨论了如何使用 JNDI J2SE进行应用程序配置。 
  • Daniel通过解释 J2EE应用程序中使用的 JNDI命名上下文,展示了如何“ 走出 JNDI迷宫 
  • JNDI 用于 WebSphere Application Server J2EE瘦客户端应用程序 展示了如何在 J2EE应用程序客户机中使用 JNDI,以便与远程 JNDI提供者进行通信。 
  •  developerWorks Java技术专区 可以找到数百篇有关 Java各个方面的技术文章。 
  • 请访问 Developer Bookstore,以获得技术书籍的完整清单,其中包括数百本 Java相关主题 的书籍。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值