EJB 错误?不要慌!
您已经在自己所钟爱的 Java 书籍中读过了关于企业 Javabean 技术的那一章,也已经练习过了简单的 HelloWorld bean,并遵循所建议的部署过程发布了它。现在您得编写一个客户机,以便通过这个客户机来调用这个杰作。因此您写出了类似清单 1 中的代码:
清单 1. 一个调用 bean 的非常简单的客户机
1
InitialContext ic
=
new
InitialContext();
2
Object or
=
ic.lookup(
"
ejb/HelloWorldHome
"
);
3
if
(or
!=
null
) {
4
//
Narrow the return object to the Home class type
5
HelloWorldHome home
=
6
(HelloWorldHome)PortableRemoteObject.narrow(or,
7
HelloWorldHome.
class
);
8
//
Create an EJB object instance using the home interface.
9
HelloWorld hw
=
home.create();
10
//
Invoke the method
11
System.out.Println(hw.hello());
12
}
13
在命令行中运行这个客户机,使用手头最方便的一个 Java 安装 ―― 即应用服务器 style="COLOR: #000000" href="http://server.it168.com/" target=_blank>服务器使用的那一个。所有事情都很完美!带着成功的喜悦,您转移到第二台计算机上运行您的客户机。这回,您得到了一个可怕的错误消息。首先,您可能得到 java.lang.NoClassDefFoundError: javax/ejb/EJBObject ,然后是一大堆其他的 NoClassDefFoundError s,因为您忘记提交一个带有必需的 stub 和 tie 的 JAR 文件,并且没有提供或者考虑到其他各种 EJB 相关的内容。不过最终,您的客户机运行到了第一行有意思的代码( InitialContext ic = new InitialContext(); )。在到达这一行时得到的异常 ―― 您几乎肯定会得到一个异常 ―― 将会根据您所选择的特定 上下文 provider而有所不同。
解释这些术语
在我们继续往下之前,定义几个术语会很有帮助。计算世界使用的都是一些奇怪的术语、时髦的语汇和首字母缩写词,Java 技术也不例外(也许这应该是 JavaIsNoException ?)。如果您遇到了上面所说的问题,那么这里面的术语可能会让您感到有些无所适从。所以让我们讨论在本文中将会遇到的术语,搞明白它们的意思是一个好主意。
名称空间、上下文、初始上下文和子上下文 这些术语都是有关位置的 ―― 是从客户机的角度看时 EJB 组件所在的概念性的位置。将一个 名称空间 想像为一个城镇,城镇中的商店由 EJB home接口(我们将在稍后讨论它)表示。 上下文是城镇中的一个位置。 初始上下文 是您开始时所在的位置 ―― 就像它是到城镇的道路。而 子上下文是街道名。
home接口(home interface)和远程接口(remote interface) 企业 JavaBean 组件有三个部分。首先是 bean 代码本身。然后是 home接口,它定义了创建您自己的 EJB bean 的方法。home接口是在名称空间中发布的。当您有了home接口后,就可以调用 Create() 以从应用服务器获得远程接口。获得了远程接口后,就可以调用构成实际的 EJB 代码的方法了。
如何将这些术语应用到您的城镇模拟中去呢?到达正确的城镇并找到正确的地址后,您需要走进商店或者按铃(调用 Create() )。这个过程对于您要去的所有商店都是一样的,不过,您所收到的响应取决于是由谁来提供服务 ―― 比如是一位屠夫、一位面包师还是一位烛台制作者。这个响应代表了 远程接口。每个人都是不同的并且可以要求他提供不同的东西。您必须知道与您交谈的人(即 bean)的职业才能提出正确的问题(即调用正确的方法) ―― 向一位屠夫要一条面包可不妥当。
CosNaming、LDAP 和 JNDI Java 命名和目录接口(Java Naming and Directory Interface JNDI)提供了一个标准接口,它指明您需要如何与名称空间交互。我们所提到的 LDAP和 CosNaming 就是 JDNI 名称空间类型。现在扩展我们的比喻:JNDI 是城镇的模板,而 CosNaming 和 LDAP 是特定的城镇。它们以相似的方式操作,但是有不同的布局。
属性提供了一个映射
让我们看一看如何使用所有这些元素以成功地从远程计算机上调用我们的 EJB 组件上的方法。为了让客户程序连接到您精心打造的 EJB 组件,需要几样东西。首先,它需要客户代码的所有 JAR 文件、一般性的 EJB 相关 JAR 文件如 J2EE.jar 以及在部署 bean 时生成的 stub 和 tie。这些文件让您的客户机可以一直到达初始上下文。
接下来您的客户机需要的信息是一些属性的值。首先,您将需要几个 java.naming.factory.initial 的值。该属性指向一个提供初始上下文工厂的类。该属性的一个典型值是 com.sun.jndi.cosnaming.CNCtxFactory ,这也是我们在这里的几个例子中所使用的值。这个类存在于 rt.jar 中,因而它是基本 JVM 的一部分。工厂是由 CosNaming 命名服务器所使用的,但是 JVM 还包括一个 LDAP 工厂。我们在后面将会看到,不同的应用服务器提供它们自己的初始上下文工厂。
这个类连同命名服务器 URL 和端口号的详细信息,用于生成与名称空间交互的 InitialContext 类。不过,如果没有 provider URL,那么它将连接到 localhost 的 900 端口(或者您的上下文工厂的其他默认端口)。要连接到远程服务器,您需要有属性 java.naming.provider.url 的一个值。
新程序员对于所有这些觉得很难理解的原因是:不管您在应用服务器本地运行任何东西,这东西通常都会听话地工作。这是由于环境照管了一切,当您要求一个 InitialContext 时,环境就会给您提供您想要的那个。但是当您将客户即转移到不同的计算机上时,就得靠自己了。您需要知道拷贝哪一个 JAR 文件,以及要做哪些设置。我知道有些人为使他们的客户机正确工作,将应用服务器上的所有 JAR 文件都拷贝到第二台计算机上!
在默认情况下, InitialContext 工厂是在 jndi.properties 中定义的,这个工厂类有默认的服务器 URL 和端口号默认值。这个文件在类路径中(这一般意味着在本地目录)或者在您的类路径中的任何 JAR 中。不同的应用服务器可能在不同的 JAR 文件中提供它们的默认值,WebSphere Application Server 在 namingclient.jar 中储存一个默认副本。要指定您自己的默认值,只需要编辑在类路径中的第一个副本。这是配置属性的一种方法,如果缺少命令行或者代码驱动的设置,那么客户机将使用 jndi.properties 中的值。不过,虽然这可能适合于简单的设置,但是如果处理多个服务器和名称空间,那么您可能希望一个客户一个客户地进行配置。
这些属性是如何根据我们要使用的名称空间而使用不同的值的呢?正如前面提到的,有两种形式的 JNDI 名称空间:CosNaming 和 LDAP。其中每一个都有与之相关联的传输:分别是 IIOP 和 LDAP。一个 LDAP 名称空间使用 LDAP 传输(您将用一个像 ldap://myldapnameserver 这样的 URL 连接到它),而 CosNaming 使用一个 IIOP 传输(您将用一个像 iiop://mycosnamingserver 这样的 URL 连接到它)。CosNaming 的默认端口号是 900,而 LDAP 的默认端口号是 389。不过,任何给定的名称空间服务器实现使用的默认值可能是不同的。