用命令行配置属性
让我们看一下如何用命令行配置属性。如果您要在家里自己练习,进入 JDK 安装中的 bin 目录。在这个文件夹中,可以找到一个名为 tnameserv.exe 的程序(对于 Windows)或者只是 tnameserv (对于基于 UNIX 的系统)。通过执行这个程序将会在端口 900 启动一个示例 CosNmaing 命名服务器。
现在正好可以用一个可以查看 CosNaming 名称空间的实用工具来装备您自己。我本人使用 Eclipse 作为开发环境,我在下面的 参考资料 部分中提供了到 JNDI 浏览器插件的链接。理论上,您应该可以将一个名称空间浏览器指向自己计算机的端口 900,并看到一个非常无聊的空名称空间(尽管一些应用服务器在默认情况下会用很多不同的内容填充名称空间)。为了丰富我们的名称空间,我们现在将编写一个简单的程序以在它里面放一些内容,如清单 2 所示:
清单 2. 一个简单的 cosNaming 名称空间交互
2
3 import javax.naming.InitialContext;
4
5 public class Publish {
6
7 public static void main(String[] args) {
8 //
9 //This example creates a subcontext in a namespace
10 //
11 try{
12 InitialContext ic = new InitialContext();
13 ic.createSubcontext("Test");
14 }catch(Exception e){
15 System.out.println(e);
16 e.printStackTrace();
17
18 }
19 }
20 }
21
这个应用程序将假定为得到正确的初始上下文件所需的所有属性都是可用的。所以现在可以从命令行运行它并在运行时提供这些属性(其中 URL 要根据您的环境作调整):
2 -Djava.naming.provider.url=iiop://mymachine:900
3 example.publisher.Publish
4
一切正常,我们的客户会找到示例名称空间的上下文并创建名为 Test 的子上下文。您可以用名称空间浏览器确认这一点。
现在试着在一台计算机上运行命名服务器,用同一个命令行(当然,对 URL 再次做了调整)在另一台计算机上运行清单 2 中的应用程序。它运行起来应该没有问题(您可能需要修改这个例子以改变所限定的内容,甚至删除子上下文而不是创建它,这样在第二次运行时您就可以确信它已经起过作用了)。
在应用程序中配置属性
那么,如果不希望在命令行中设置这些属性怎么办?还有另外一个方法。可以在程序中显式地声明这些属性。这意味着您不需要为 java 命令提供特殊的选项。改变清单 2 中的代码以显式地设置所需要的属性后,它看起来与清单 3 中的代码一样:
清单 3. 简单的 cosNaming 名称空间交互,在应用程序代码中设置属性
2
3 import javax.naming.InitialContext;
4
5 public class Publish {
6
7 public static void main(String[] args) {
8 //
9 //This example creates a subcontext in a namespace
10 //
11 try{
12 Properties prop = new Properties();
13 prop.setProperty("java.naming.factory.initial",
14 "com.sun.jndi.cosnaming.CNCtxFactory");
15 prop.setProperty("java.naming.provider.url",
16 "iiop://mymachine:900");
17 InitialContext ic = new InitialContext(prop);
18 ic.createSubcontext("Test");
19 }catch(Exception e){
20 System.out.println(e);
21 e.printStackTrace();
22
23 }
24 }
25 }
26
现在这个程序不再需要长长的命令行配置,不过要记住,以这种方式编写的应用程序硬编码了这些设置。
寻找通往 bean 的道路
到目前为止,我们已经看到了几个可以证明我们已连接到远程名称空间并完成一些任务的例子,尽管这些任务是相当无聊的 ―― 创建一个子上下文。在实际中,一般是由工具来为您完成所有的创建和发布工作,您 真正 需要的做是查找一个对象。在这一节,我们将在 CosNaming 名称空间中获得已发布的 HelloWorld bean 的 Home 接口。然后我们再看一下如何在 LDAP 名称空间中找到它的 Home 接口。
为了说明问题,我们假设您已经部署了 HelloWorld bean,它的home接口 HelloWorldHome 发布在 example/HelloWorldHome 。
在上一节,我们进行了连接到命名服务器的艰苦工作,现在我们所需要的就只是查询 EJB 组件了。这需要我们向查询方法传递一个字符串,它表示从 InitialContext (您在城镇中的出发点)到想要去的 HomeInterface (房屋或者商店)的方向。听起来简单 ―― 但是这里您所选择的特定上下文工厂就要产生影响了。像 WebSphere 这样的应用服务器所带的工厂类并不总是把您放到名称空间的根上。所以我们为了查询 HomeInterface 而需要的字符串会根据 InitialContext 将您所放到城镇中的位置而变化。并且,在本地服务器上,上下文工厂可能将您放到与在远程服务器上不同的起始位置。
因为这个原因,我建议您不要像在清单 3 中那样硬编码所使用的查询字符串,而是用命令行或者属性文件传递。特别是对于具有多步的体系结构更应如此。例如,您可能有一个调用一个 EJB 组件的客户机,这个 bean 可能又需要调用也许是在不同的服务器上的第二个 EJB 组件!在这种情况下,属性应该在每一步中传递。这为反复实验(trial-and-error)查询提供了一种简单的机制,并且只需要相对较少的改变就可以得到最终应用程序的灵活性。因此让我们看一个示例查询应用程序。在清单 4 中,属性是在程序中设置的,但是它又以命令行值为依据。这样命令行与在我们前一个例子中使用的稍有不同,如我们在下面所看到的。
清单 4. 查询一个home接口
2 import java.util.Properties;
3 import javax.naming.InitialContext;
4 import javax.rmi.PortableRemoteObject;
5
6 import example.HelloWorld;
7 import example.HelloWorldBean;
8 import example.HelloWorldHome;
9 import javax.naming.InitialContext;
10
11 public class Lookup {
12
13 public static void main(String[] args) {
14 //
15 //This example looks up the home interface of an EJB to a namespace
16 //
17 try{
18 Properties prop = new Properties();
19 prop.setProperty("java.naming.factory.initial",args[0]);
20 prop.setProperty("java.naming.provider.url",args[1]);
21 InitialContext ic = new InitialContext(prop);
22 Object or = ic.lookup(args[2]);
23 if (or != null) {
24 // Narrow the return object to the Home class type
25 HelloWorldHome home =
26 (HelloWorldHome)PortableRemoteObject.narrow(or,
27 HelloWorldHome.class);
28 // Create an EJB object instance using the home interface.
29 HelloWorld hw = home.create();
30 // Invoke the method
31 System.out.Println(hw.hello());
32 }
33 }catch(Exception e){
34 System.out.println(e);
35 e.printStackTrace();
36
37 }
38 }
39 }
40
这个程序是用三个参数调用的:要使用的上下文工厂、provider URL 和包含要查询的名字的字符串。我们已经知道前两个是什么,那么第三个呢?
如果您仍然使用 tnameserv 作为命名服务器,那么您很可能将 bean 直接发布到 /example/HelloWorldHome 。在这种情况下,只要将 /example/HelloWorldHome 作为第三个参数传递就可以进行成功的查询。不过,如果您使用的命名服务器有一个更复杂的命名空间,那么可能会存在由所使用的部署工具增加的额外的层。例如,WebSphere 在默认情况下将 JavaBean 部署到 ejb/ ,但是这不是名称空间的根,并且只有当使用 WebSphere 的上下文工厂时,通过传入字符串 /ejb/example/HelloWorldHome 才会使您处于名称空间中的正确位置。 如果您使用一个与应用服务器提供的不同的上下文工厂(例如在一台只有标准 Java 安装的计算机上运行客户机时就需要这样做)时,这个问题会更加恶化。不过,应用服务器的命名服务器文档应当说明在查询 EJB 组件时将会从名称空间的什么地方开始。看一下文档中的例子,再用浏览器查看名称空间以确定其客户机的 InitialContext 会将它们放到什么地方。名称空间往往会循环,这样您就可以试着沿着一个分枝到无限。这意味着从最开始的上下文可以找到一条回家的道路。
总之,下面是传递相应参数给清单 4 中的应用程序的命令行:
2 iiop://mymachine:900 example/HelloWorldHome
3
在 CosNaming 中,名称空间子上下文由斜线(/)字符分隔,这与标准 URL 一样。LDAP 的语法则不同,我们在下面将会看到。
介绍 LDAP
现在让我们再加上 LDAP。就本文的内容来讲,LDAP 是另一个 JNDI 名称空间,但是它的结构的表示方法与 CosNaming 名称空间的表示方法截然不同。它还需要一个不同的上下文工厂 ―― 但是这不成问题,因为我们总会在命令行指定正确的工厂(在我们的例子中,我们将使用属于基本 JVM 一部分的工厂,但是要记住不同的应用服务器可能有自己的工厂)。并且它需要一个指向不同命名服务器的指针 ―― 并且,幸运的是,我们也是在命令行中指定它的。当然,表示home接口位置的字符串是不同的,但是您猜如何?是的,我们还是在命令行中指定它。您可以看到使用这些命令行调用的好处:所有要做的只是改变调用我们的测试程序的方式,理论上我们可以到达任何 JDNI 命名服务器,甚至可以顺利地从 CosNaming 转移到 LDAP 而不用改变任何代码。是的,这是只是理论,当然无论如何,关键是要有正确的参数。
一些命名服务器会保护部分命名空间,这意味着只能发布到允许的区域。假设您有一个运行的 LDAP 服务器,它的细节如下:
URL: ldap://mymachine:1389
BaseDN: c=myldap
LDAP 名称空间中的树结构一般来说像下面这样:
2
不过,当我们将这个字符串传递给程序时,我们需要反转它(不要问我为什么)。所以我们使用的字符串看起来是这样的:
2
BaseDN 表示在名称空间中您希望开始的位置。对于给定的 LDAP 命名服务器来说这可以是很多位置,这取决它是如何构造的。在这个例子中,我们直接到 c=myldap 的根。但是如果我们希望跳到名称空间中的一个树,那么可以指定 ibm-wsnTree=myTree,c=myldap 作为 BaseDN 而不是跳到那一点。
这样,我们将传递给程序的命令行参数就像下面这样:
2 ibm-wsnName=HelloWorldHome,ibm-wsnName=MyServer
这里我们指定一个 LDAP 上下文工厂 ,然后传递 LDAP 服务器的名字以及我们想要开始的位置。然后是到要查询的 EJB 组件的反转的路径。我们可以用这个命令行调用在 CosNaming 例子中使用的同一段代码( 清单 4)。
当然,本文中使用的代码没有理由不能构成一个助手类的一个方法 ―― 它带三个参数,并且返回 Object or ,这个对象是在试图做任何事情之前调用 ic.lookup(args[2]) 时返回的。然后,当您需要进行一次查询时,只需使用这个助手类,向它传递适合于当前情况的适当参数,取回您所需要的对象引用,并准备将它窄化到实际的类。( 注意:我不保证这种类的性能,而只是提供这段原本就是如此的代码,我或者 IBM 对此不作任何保证。)当然,可以通过反射实现一种完全通用的方式,但这会使事情复杂得多,也超出了本文的范围。
在我们结束之前还有最后一件事要考虑。您可以编写一个结合了我们在清单 3 和 4 中使用的技术的客户机。它会检查命令行中是否给出了一个值;如果有,它就设置这些值,如果没有,它就使用硬编码的值。这样,在程序中可以有有意义的默认值,但是如果需要,也可以用命令行选项覆盖它们。只需要对代码进行微不足道的更改。
安全到家
作为回顾:下面是在有多个 EJB 查询和多个应用服务器的情况下,要使任何系统运行而应该有或者应该知道的最重要的四件事情:
在任何给定阶段,下一阶段的所有 stub 和 tie 都必须在类路径上。除非环境知道这个类是什么样的,否则您不能窄化一个对象以使用它。
每一阶段都需要与 EJB 相关的一般性 JAR 文件,如 J2EE.jar 。
以参数的形式传递上下文工厂类型、命名服务器名和 JDNI 查询字符串。以便能够轻松顺应变化。
知道您的名称空间。记住您的 JDNI 查询字符串需要将您从在名称空间中开始的位置移动到您的对象所储存的位置。但是您并不总是在同一位置开始!用一个工具浏览名称空间,并了解在本地和远程查询中是从什么位置开始的。
对于习惯于编写全部在同一台计算机上执行的代码的开发人员来说,浏览远程名称空间可能是一个困难的过程。希望本文的提示和代码可以帮助您设置并运行您的分布式 EJB 应用程序。当您掌握了 JNDI 名称空间后,再去看一下 developerWorks 上由 Brett McLaughlin 所写的 EJB 非常好的实践系列,以获得用于优化代码的一些很棒的技巧。