JNDI在JavaEE中的角色_动力节点Java学院整理
虽然J2EE平台提高了普通企业开发人员的生活水平,但是这种提高是以不得不学习许多规范和技术为代价的,这些规范和技术则是J2EE为了成为无所不包的分布式计算平台而整合进来的。DollyDeveloper是众多开发人员中的一员,她已经发现了一个特性,该特性有助于缓解随企业级应用程序部署而带来的负担,这个特性就是JNDI,即 Java命名与目录接口(JavaNamingandDirectoryInterface)。让我们来看看Dolly在没有JNDI的时候是怎么做的,以及她是如何正确地应用JNDI来改善其状况的。
所有人都非常熟悉的旅程
DollyDeveloper正在编写使用JDBC数据源的Web应用程序。她知道自己正在使用MySQL,所以她将一个对MySQLJDBC驱动程序类的引用进行了编码,并通过使用适当的JDBCURL连接到其Web应用程序中的数据库。她认识到数据库连接池的重要性,所以她包含了一个连接池包,并把它配置成最多使用64个连接;她知道数据库服务器已经被设置成最多允许128台客户机进行连接。
Dolly在走向灾难
在开发阶段,每件事都进行得很顺利。但是,在部署的时候,开始失控。Dolly的网络管理员告诉她,她不能从她的桌面机访问生产服务器或登台服务器(stagingserver),所以她不得不为每个部署阶段开发不同的代码版本。因为这种情况,她需要一个新的JDBCURL,所以还要为测试、阶段和生产进行独立的部署。(一听到要在每个环境中建立单独部署,熟悉配置管理的人会战战兢兢的,但是既然这是种非常普遍的情况,所以他们也只好硬着头皮上了。)
就在Dolly认为通过不同的URL建立彼此独立的部署已经解决了自己的配置问题时,她发现她的数据库管理员不想在生产环境中运行MySQL实例。他说,MySQL用作开发还可以,但是对于任务关键型数据而言,业务标准是DB2®。现在她的构建不仅在数据库URL方面有所不同,而且还需要不同的驱动程序。
事情越变越糟。她的应用程序非常有用,并且变得非常关键,以致于它从应用服务器那里得到了故障恢复的能力,并被复制到4个服务器集群。但是数据库管理员提出了抗议,因为她的应用程序的每个实例都要使用64个连接,而数据库服务器总共只有200个可用连接——全部都被Dolly的应用程序占用了。更麻烦的是,DBA已经确定Dolly的应用程序只需要32个连接,而且每天只有一个小时在使用。随着她的应用程序规模扩大,应用程序遇到了数据库级的争用问题,而她的惟一选择就是改变集群的连接数量,而且还要做好准备,在集群数量增长或者应用程序复制到另一个集群时再重复一次这样的操作。看来她已经决定了如何配置应用程序,应用程序的配置最好是留给系统管理员和数据库管理员来做。
J2EE的角色
如果Dolly在开发应用程序时了解J2EE所扮演的角色,那么她就可能避免遭遇这种困境。J2EE规范把职责委托给多个开发角色:组件提供者(ComponentProvider)、应用程序组装者(ApplicationAssembler)、部署人员(Deployer)和系统管理员(SystemAdministrator)。(在许多公司中,组件提供者和组件组装者的角色是融合在一起的,部署人员和系统管理员的角色是融合在一起的。)在真正了解J2EE中的JNDI角色之前,掌握J2EE角色的作用非常重要。
组件提供者
这个角色负责创建J2EE组件,J2EE组件可以是Web应用程序、企业级JavaBean(EJB)组件,或者是应用程序客户机(例如基于Swing的GUI客户机应用程序)。组件提供者包括:HTML设计师、文档编程人员以及其他开发人员角色。大多数J2EE开发人员在组件提供者这一角色上耗费了相当多的时间。
应用程序组装者
这个角色将多个J2EE模块捆绑成一个彼此结合的、可以部署的整体:企业归档(EAR)文件。应用程序组装者要选择组件,分清它们之间的交互方式,配置它们的安全性和事务属性,并把应用程序打包到EAR文件中。许多IDE,例如WebSphere®Studio、IDEA、JBuilder、WebLogicWorkshop和其他IDE,都可以帮助应用程序组装者以交互方式配置EAR文件。
部署人员(Deployer)
这个角色负责部署,这意味着将EAR安装到J2EE容器(应用服务器)中,然后配置资源(例如数据库连接池),把应用程序需要的资源绑定到应用服务器中的特定资源上,并启动应用程序。
系统管理员(SystemAdministrator)
这个角色负责保证容器需要的资源可用于容器。
角色实战
假设有一个企业应用程序,该应用程序包含一个Web应用程序,还有一个负责业务逻辑和持久性的EJB组件。开发这个应用程序的组件供应商可能有许多,但是在许多情况下,可以由一个人来承担全部职责。组件可以包含数据传输对象(一个JAR文件)、EJB接口(另一个JAR文件)、EJB实现本身(另一个JAR文件),以及用户界面组件——servlet、JSP、HTML页面和其他静态Web内容。用户界面组件被进一步打包成Web应用程序,其中包含servlet类、JSP文件、静态内容,以及其他必需组件的JAR(包括EJB接口)。
这听起来好像用到的组件太多了,几乎超出了人的想像范围,尤其是在考虑构建一个典型的Web应用程序需要使用多少个JAR文件的时候。但是,重要的是认识到在这里必须小心地管理依赖性。接口和传输对象是Web应用程序和EJB实现可以依赖的对象,但是依赖性的方向应该是相同的;还要避免产生循环依赖。J2EE组件(例如WAR文件和EJBJAR文件)必须在它们的部署单元之外声明它们在资源上的依赖性。
应用程序组装者负责把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管理扩展(JavaManagementExtensions,JMX)也可以用作这个目的)。JNDI在J2EE应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。
Dolly的情况更糟了
现在我们重新来看一下Dolly的情况。在其简单的Web应用程序中,她直接从应用程序代码中使用了一个JDBC连接。参见清单1,我们可以看出,Dolly显式地把JDBC驱动程序、数据库URL以及她的用户名和口令编码到了servlet中:
清单1.典型(但是不好)的JDBC用法
Connectionconn=null; try{ Class.forName("com.mysql.jdbc.Driver", true,Thread.currentThread().getContextClassLoader()); conn=DriverManager.getConnection("jdbc:mysql://dbserver?user=dolly&password=dagger"); /*usetheconnectionhere*/ c.close(); } catch(Exceptione){ e.printStackTrace(); } finally{ if(conn!=null){ try{ conn.close(); }catch(SQLExceptione){} } }
如果不用这种方式指定配置信息,Dolly(以及她的同伴们)使用JNDI来查找JDBC DataSource 会更好一些,如清单2所示:
清单2.使用JNDI得到数据源
Connectionconn=null; try{ Contextctx=newInitialContext(); ObjectdatasourceRef=ctx.lookup("java:comp/env/jdbc/mydatasource"); DataSourceds=(Datasource)datasourceRef; Connectionc=ds.getConnection(); /*usetheconnection*/ c.close(); } catch(Exceptione){ e.printStackTrace(); } finally{ if(conn!=null){ try{ conn.close(); }catch(SQLExceptione){} } }
为了能够得到JDBC连接,首先要执行一些小的部署配置,这样我们才可以在本地组件的JNDI下文中查找 DataSource。这可能有点烦琐,但是很容易学。不幸的是,这意味着即使是为了测试组件,开发人员也必须涉足部署人员的领地,并且还要准备配置应用服务器。
配置JNDI引用
为了让JNDI解析 java:comp/env/jdbc/mydatasource 引用,部署人员必须把
清单3.resource-ref入口
DollysDataSource jdbc/mydatasource javax.sql.DataSource Container
这只定义了到外部资源的本地引用,还没有创建引用指向的实际资源。(在Java语言中,类似的情况可能是:
部署人员的工作就是创建 DataSource(或者是创建一个 Object 对象,让 foo 指向它,在我们的Java语言示例中就是这样)。每个容器都有自己设置数据源的机制。例如,在JBoss中,是利用服务来定义数据源(请参阅$JBOSS/server/default/deploy/hsqldb-ds.xml,把它作为示例),它指定自己是 DataSource 的全局JNDI名称(默认情况下是 DefaultDS)。在创建资源之后,第三步仍然很关键:把资源连接或者绑定到应用程序组件使用的本地名称。在使用Web应用程序的情况下,是使用特定于供应商的部署描述符扩展来指定这个绑定,清单4中显示了一个这样的例子。(JBoss用称为 jboss-Web.xml 的文件作为特定于供应商的Web应用程序部署描述符。)
清单4.用特定于供应商的部署描述符将资源绑定到JDI名称
jdbc/mydatasource java:DefaultDS
这表明应该将本地资源引用名称( jdbc/mydatasource)映射到名为 java:DefaultDS 的全局资源。如果全局资源名称出于某种原因发生了变化,而应用程序的代码无需变化,那么只需修改这个映射即可。在这里,有两个级别的间接寻址:一个定义并命名资源(java:DefaultDS),另一个把特定于本地组件的名称( jdbc/mydatasource)绑定到命名的资源。(实际上,当您在EAR级别上映射资源时,可能还存在第三级别的间接寻址。)
超越数据源
当然,J2EE中的资源并不局限于JDBC数据源。引用的类型有很多,其中包括资源引用(已经讨论过)、环境实体和EJB引用。特别是EJB引用,它暴露了JNDI在J2EE中的另外一项关键角色:查找其他应用程序组件。
试想一下这种情况:当一家公司从OrderOntologyProcessingServices(OOPS)购买了一个可部署的EJB组件来处理客户订单时,会发生什么。为了便于举例说明,我们把它叫做ProcessOrdersV1.0。ProcessOrders1.0有两部分:一组接口和支持类(home和remote接口,以及支持的传输类);实际EJB组件自身。选择OOPS是因为它在这个领域的专业性。
该公司遵照J2EE规范,编写使用EJB引用的Web应用程序。公司的部署人员把ProcessOrders1.0绑定到JNDI树中,将它用作ejb/ProcessOrders/1.0,并解析Web应用程序的资源名称,以指向这个全局JNDI名称。目前为止,这些都是EJB组件非常普通的用法。但是,当我们考虑公司的开发周期与公司供应商之间的交互时,事情就变得复杂起来。在这里,JNDI也能帮助我们。
我们假设OOPS发布了一个新版本,即ProcessOrdersV1.1。这个新版本有一些新功能,公司内部的一个新应用程序需要这些新功能,而且很自然地扩展了EJB组件的业务接口。
在这里,公司有以下几个选择:可以更新所有应用程序来使用新版本,也可以编写自己的版本,或者使用JNDI的引用解析,允许每个应用程序在不影响其他应用程序的情况下使用自己的EJB组件版本。立刻更新所有应用程序对维护来说是一场噩梦,它要求对所有组件都进行完整的回归测试,这通常是一项艰巨的任务,而且如果发生任何功能测试失败的话,那么还要进行另一轮调试。
编写内部(in-house)组件常常是没有必要的重复工作。如果组件是由在这个业务领域内具有专业知识的公司编写的,那么给定的IT商店不可能像专业的组件供应商那样精通业务功能。
正如您可能已经猜到的那样,最好的解决方案是用JNDI解析。EJB的JNDI引用非常类似于JDBC资源的引用。对于每个引用,部署人员都需要把新组件按特定的名称(比如说 ejb/ProcessOrders/1.1)绑定到全局树中,对于需要EJB组件的其他每个组件,还要为组件在部署描述符中解析EJB引用。依赖于V1.0以前的应用程序不需要进行任何修改,也不需要重新测试,这缩短了实现的时间、降低了成本并减少了复杂性。在服务趋于转换的环境中,这是一种很有效的方法。可以对应用程序架构中所得到的所有组件进行这类配置管理,从EJB组件到JMS队列和主题,再到简单配置字符串或其他对象,这可以降低随时间的推移服务变更所产生的维护成本,同时还可以简化部署,减少集成工作。