1、jndi 高级应用之 Name
1.1、什么是 Name?
这之前的文档中,我们所用的例子里,对象的名字都是 java.lang.String 类型的,也就是字符串类型。
在这个文档里,我们则会介绍一些其他的对象名字类型,如 Name,以及他们的使用方法。
我们首先会讲讲什么是字符串的名字和结构化的名字,以及它们的存在的必要性。
然后,我们会介绍2个结构化名字的类:(复合名字)composite 和 (混合名字)compound。
最后,我们介绍如何在对象名字中使用特殊字符,以及如何解析和构成一个复杂的对象的名字。
1.2、字符串名字 vs 结构化名字
在 Context 和 DirContext 接口里,每一个命名服务的方法都有2种方式,一个是接受字符串类型的名字,
一个是接受结构化的名字(Name 类的对象)。例如:
lookup(java.lang.String)
lookup(javax.naming.Name)
1.2.1、字符串名字
使用字符串类型的名字,可以让你不必再生成一个CompositeName类的对象。例如下面的代码是相同的:
- Object obj1 = ctx.lookup("cn=Ted Geisel, ou=People, o=JNDITutorial");
- CompositeName cname = new CompositeName(
- "cn=Ted Geisel, ou=People, o=JNDITutorial");
- Object obj2 = ctx.lookup(cname);
1.2.1、结构化的名字
结构化的名字的对象可以是 CompositeName 或 CompoundName 类的对象,或者是任何一个实现了 “Name ”
接口的类。
如果是 CompositeName 类的实例,那么就被认为是一个复合的名字(composite name)。所谓的复合名字就
是可以在一个名字里使用多个命名服务系统,而不是仅仅一个。
如果是 compound 类的实例,那么就被认为是混合的名字(compound name)。混合的名字只包含一个命名服务
系统。
1.2.2、那么该使用哪种名字呢?
一般来说,如果用户可以提供字符串类型的名字,那么就使用字符串类型的名字;可是如果用户提供的是一
个组合的名字,那么就应该使用结构化的名字了。
例如一个应用如果会涉及多个命名服务系统,那么就应该使用结构化的名字了。
1.3 复合名字(composite name)
复合名字就是跨越多个命名系统的名字。例如:
cn=homedir,cn=Jon Ruiz,ou=People/tutorial/report.txt
这个名字里包含了两个命名系统:一个 LDAP 系统 "cn=homedir,cn=Jon Ruiz,ou=People" 和一个文件系统
"tutorial/report.txt"。当你把这个字符串传递给 Context 的 look 方法,那么就会从 LDAP 系统里查找
那个文件,然后返回那个对象(一个文件对象的实例)。当然,这取决于特定的SPI。例如:
- File f = (File)ctx.lookup(
- "cn=homedir,cn=Jon Ruiz,ou=People/tutorial/report.txt");
1.3.1、字符串的表现形式
我们可以这样想像,一个复合的名字是由不同的“组件”组成的。所谓的“组件”,我们可以想象成是一个名字
的一小部分,每一个组件表示一个命名服务系统。每一个组件都由正斜杠“/”分开。
例如 :cn=homedir,cn=Jon Ruiz,ou=People/tutorial/report.txt
包含三个组件:
cn=homedir,cn=Jon Ruiz,ou=People
tutorial
report.txt
第一个组件属于LDAP命名服务系统,第2和第3个组件属于文件命名服务系统。正如我们从这个例子里看到的,
多个组件(tutorial 和 report.txt)可以属于同一个命名服务系统,但是一个组件是不能跨越多个命名服务
系统的。
除了使用正斜杠“/”外,复合名字还允许使用其它三个特殊符号:反斜杠“\",单引号"'",双引号" " "。
这三个符号是内置的符号,也就是说他们三个具有特殊的用途。
反斜杠“\”的作用是转译字符。反斜杠“\”之后的字符,会被认为是普通字符。
例如“a\/b”,其中的“/”是不会被认为是不同的命名服务系统的分隔符号。
双引号和单引号可以让引号内的字符成为普通字符,例如下面的用法都是一样的:
a\/b\/c\/d
"a/b/c/d"
'a/b/b/d'
可见,有的时候使用引号还是很方便的。
复合的名字也可以是空的,空的复合名字意味着没有组件,一般用空的字符串表示。
复合名字的组件也可以是空的,例如:
/abc
abc/
abc//xyz
1.3.2、复合名字的类
CompositeName 类是用来构成复合名字的类。你可以给它的构造函数传递一个复合名字的字符串。
例如:
- import javax.naming.CompositeName;
- import javax.naming.InvalidNameException;
- /**
- * Demonstrates how to construct a composite name given its
- * string representation
- *
- * usage: java ConstructComposite
- */
- class ConstructComposite {
- public static void main(String[] args) {
- if (args.length != 1) {
- System.out.println("usage: java ConstructComposite <string></string>");
- System.exit(-1);
- }
- String name = args[0];
- try {
- CompositeName cn = new CompositeName(name);
- System.out.println(cn + " has " + cn.size() + " components: ");
- for (int i = 0; i < cn.size(); i++) {
- System.out.println(cn.get(i));
- }
- } catch (InvalidNameException e) {
- System.out.println("Cannot parse name: " + name);
- }
- }
- }
运行这个例子,输出的结果就是:
a/b/c has 3 components:
a
b
c
CompositeName 类有许多方法,例如查看,修改,比较,以及得到一个复合名字的字符串表现形式。
1.3.3、访问复合名字里的组件
可以通过下列方法访问复和名字里的组件:
get(int posn)
getAll()
getPrefix(int posn)
getSuffix(int posn)
clone()
如果你想得到特定位置的组件的名字,那么使用 get(int posn) 方法就非常合适。
getAll() 方法可以返回全部的组件的名字。
例如:
- try {
- CompositeName cn = new CompositeName(name);
- System.out.println(cn + " has " + cn.size() + " components: ");
- for (Enumeration all = cn.getAll(); all.hasMoreElements();) {
- System.out.println(all.nextElement());
- }
- } catch (InvalidNameException e) {
- System.out.println("Cannot parse name: " + name);
- }
你可以用 getPrefix(int posn) 和 getSuffix(int posn)来从前端或者后端查找组件的名字,如:
- CompositeName cn = new CompositeName("one/two/three");
- Name suffix = cn.getSuffix(1); // 1 <= index < cn.size()
- Name prefix = cn.getPrefix(1); // 0 <= index < 1
运行结果:
two/three
one
1.3.4、修改一个复合名字
你可以通过下列方法修改一个复合名字:
add(String comp)
add(int posn, String comp)
addAll(Name comps)
addAll(Name suffix)
addAll(int posn, Name suffix)
remove(int posn)
当你创建了一个复合名字实例后,你可以对它进行修改。看看下面的例子:
- CompositeName cn = new CompositeName("1/2/3");
- CompositeName cn2 = new CompositeName("4/5/6");
- System.out.println(cn.addAll(cn2)); // 1/2/3/4/5/6
- System.out.println(cn.add(0, "abc")); // abc/1/2/3/4/5/6
- System.out.println(cn.add("xyz")); // abc/1/2/3/4/5/6/xyz
- System.out.println(cn.remove(1)); // 1
- System.out.println(cn); // abc/2/3/4/5/6/xyz
1.3.4、比较复合名字
你可以通过以下的方法对复合名字进行比较:
compareTo(Object name)
equals(Object name)
endsWith(Name name)
startsWith(Name name)
isEmpty()
你可以使用 compareTo(Object name) 方法对一个复合名字的列表进行排序。下面是一个例子:
- import javax.naming.CompositeName;
- import javax.naming.InvalidNameException;
- /**
- * Demonstrates how to sort a list of composite names.
- *
- * usage: java SortComposites [<name></name>]*
- */
- class SortComposites {
- public static void main(String[] args) {
- if (args.length == 0) {
- System.out.println("usage: java SortComposites [<names></names>]*");
- System.exit(-1);
- }
- CompositeName[] names = new CompositeName[args.length];
- try {
- for (int i = 0; i < names.length; i++) {
- names[i] = new CompositeName(args[i]);
- }
- sort(names);
- for (int i = 0; i < names.length; i++) {
- System.out.println(names[i]);
- }
- } catch (InvalidNameException e) {
- System.out.println(e);
- }
- }
- /**
- * Use bubble sort.
- */
- private static void sort(CompositeName[] names) {
- int bound = names.length-1;
- CompositeName tmp;
- while (true) {
- int t = -1;
- for (int j=0; j < bound; j++) {
- int c = names[j].compareTo(names[j+1]);
- if (c > 0) {
- tmp = names[j];
- names[j] = names[j+1];
- names[j+1] = tmp;
- t = j;
- }
- }
- if (t == -1) break;
- bound = t;
- }
- }
- }
equals() 方法可以让你比较两个复合名字是否相同。只有两个复合名字有相同的组件,而且顺序一样,
会返回 true。
使用 startsWith() 和 endsWith()方法,你可以判断复合名字是以什么字符串开头和以什么字符串结尾。
isEmpty() 方法可以让你知道一个复合名字是否为空。你也可以使用 size() == 0 来实现同样的功能。
下面是一些例子:
- CompositeName one = new CompositeName("cn=fs/o=JNDITutorial/tmp/a/b/c");
- CompositeName two = new CompositeName("tmp/a/b/c");
- CompositeName three = new CompositeName("cn=fs/o=JNDITutorial");
- CompositeName four = new CompositeName();
- System.out.println(one.equals(two)); // false
- System.out.println(one.startsWith(three)); // true
- System.out.println(one.endsWith(two)); // true
- System.out.println(one.startsWith(four)); // true
- System.out.println(one.endsWith(four)); // true
- System.out.println(one.endsWith(three)); // false
- System.out.println(one.isEmpty()); // false
- System.out.println(four.isEmpty()); // true
- System.out.println(four.size() == 0); // true
1.3.5、复合名字的字符串表现形式
你可以使用 toString() 方法来实现这个功能。
下面是一个例子:
- import javax.naming.CompositeName;
- import javax.naming.InvalidNameException;
- /**
- * Demonstrates how to get the string representation of a composite name.
- *
- * usage: java CompositeToString
- */
- class CompositeToString {
- public static void main(String[] args) {
- if (args.length != 1) {
- System.out.println("usage: java CompositeToString <string></string>");
- System.exit(-1);
- }
- String name = args[0];
- try {
- CompositeName cn = new CompositeName(name);
- String str = cn.toString();
- System.out.println(str);
- CompositeName cn2 = new CompositeName(str);
- System.out.println(cn.equals(cn2)); // true
- } catch (InvalidNameException e) {
- System.out.println("Cannot parse name: " + name);
- }
- }
- }
1.3.5、复合名字作为Context的参数
直接看一个例子,非常简单:
- // Create the initial context
- Context ctx = new InitialContext(env);
- // Parse the string name into a CompositeName
- Name cname = new CompositeName(
- "cn=homedir,cn=Jon Ruiz,ou=people/tutorial/report.txt");
- // Perform the lookup using the CompositeName
- File f = (File) ctx.lookup(cname);
1.4、混合名字
混合名字不是跨越命名系统的,例如:
cn=homedir,cn=Jon Ruiz,ou=People
它和复合名字有些类似,我们也可以把它看成是由不同的组件组成的,这个名字里包含了三个组件:
ou=People
cn=Jon Ruiz
cn=homedir
1.4.1、混合名字和复合名字的关系
当你给 Context.lookup() 传递一个字符串的时候,首先 lookup 方法会把这个字符串作为复合名字来看
待,这个复合名字可能只包含一个组件。但是一个组件可能会包含几个混合名字。
1.4.2、混合名字的字符串表现方式
正如上面所说的,一个混合名字是由很多的组件组成的。组件之间的分隔符号依赖于特定的命名服务系统。
例如在 LDAP 里,分隔符好就是“,”,因此,下面的这个混合名字
ou=People
cn=Jon Ruiz
cn=homedir
的字符串形式就是 cn=homedir,cn=Jon Ruiz,ou=People
1.4.3、混合名字的类
处理混合名字,我们可以使用 CompoundName 类。你可以向它的构造函数传递混合名字的字符串,并且还
得设置一些必要的属性,这些属性一般都是特定的命名服务系统的一些规则。
实际上,只有当你准备编写一个SPI的时候,才会去使用装个构造函数。作为一个开发者,一般你只是会
涉及混合名字里的各个组件而已。
下面是一个例子:
- import javax.naming.*;
- import java.util.Hashtable;
- /**
- * Demonstrates how to get a name parser and parse a name.
- *
- * usage: java ParseCompound
- */
- class ParseCompound {
- public static void main(String[] args) {
- // Set up environment for creating initial context
- Hashtable env = new Hashtable(11);
- env.put(Context.INITIAL_CONTEXT_FACTORY,
- "com.sun.jndi.ldap.LdapCtxFactory");
- env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
- try {
- // Create the initial context
- Context ctx = new InitialContext(env);
- // Get the parser for this namespace
- NameParser parser = ctx.getNameParser("");
- // Parse name
- Name compoundName = parser.parse("cn=John,ou=People,ou=Marketing");
- // List components in name
- for (int i = 0; i < compoundName.size(); i++) {
- System.out.println(compoundName.get(i));
- }
- // Close ctx when done
- ctx.close();
- } catch (NamingException e) {
- System.out.println(e);
- }
- }
- }
1.4.4、操作混合名字类
注意,上面的例子中,我们使用 NameParser.parse() 来返回一个实现了Name接口的对象。这个接口可以
是 CompositeName类 或 CompoundName类。这就意味着,你可以访问或者修改一个混合名字对象,就好
像我们在复合名字里做的一样!
下面是一个例子:
- // Get the parser for this namespace
- NameParser parser = ctx.getNameParser("");
- // Parse the name
- Name cn = parser.parse("cn=John,ou=People,ou=Marketing");
- // Remove the second component from the head
- System.out.println(cn.remove(1)); // ou=People
- // Add to the head (first)
- System.out.println(cn.add(0, "ou=East")); // cn=John,ou=Marketing,ou=East
- // Add to the tail (last)
- System.out.println(cn.add("cn=HomeDir")); // cn=HomeDir,cn=John,ou=Marketing,ou=East
输出结果:
ou=People
cn=John,ou=Marketing,ou=East
cn=HomeDir,cn=John,ou=Marketing,ou=East
需要的注意的是,LDAP系统里,组件的顺序是从右到左的。也就是右边是名字的开头,而左边是名字
的结尾!
下面这个例子是修改混合名字的:
- import javax.naming.*;
- import java.util.Hashtable;
- import java.io.File;
- /**
- * Demonstrates how to modify a compound name by adding and removing components.
- * Uses file system syntax.
- *
- * usage: java ModifyCompoundFile
- */
- class ModifyCompoundFile {
- public static void main(String[] args) {
- // Set up environment for creating initial context
- Hashtable env = new Hashtable(11);
- env.put(Context.INITIAL_CONTEXT_FACTORY,
- "com.sun.jndi.fscontext.RefFSContextFactory");
- try {
- // Create the initial context
- Context ctx = new InitialContext(env);
- // Get the parser for this namespace
- NameParser parser = ctx.getNameParser("");
- // Parse name
- Name cn = parser.parse("Marketing" + File.separator +
- "People" + File.separator +
- "John");
- // Remove 2nd component from head
- System.out.println(cn.remove(1)); // People
- // Add to head (first)
- System.out.println(cn.add(0, "East"));
- // East/Marketing/John
- // Add to tail (last)
- System.out.println(cn.add("HomeDir"));
- // /East/Marketing/John/HomeDir
- // Close ctx when done
- ctx.close();
- } catch (NamingException e) {
- System.out.println(e);
- }
- }
- }
这个例子使用了文件系统,而不是LDAP系统,输出结果:
People
East/Marketing/John
East/Marketing/John/HomeDir
1.4.5、混合名字作为Context的参数
我们只用一个例子就可以了:
- // Create the initial context
- Context ctx = new InitialContext(env);
- // Get the parser for the namespace
- NameParser parser = ctx.getNameParser("");
- // Parse the string name into a compound name
- Name compound = parser.parse("cn=Jon Ruiz,ou=people");
- // Perform the lookup using the compound name
- Object obj = ctx.lookup(compound);
1.4.6、取得完整的混合名字
有的时候,你可能需要根据一个混合名字来的完整的名字。例如一个DNS,你只知道一部分如“www.abc.com”,
但实际上它的完整的DNS是“www.abc.com.cn”。
其实这个功能已经超出了jndi 的 api 所控制的范围了,因为 jndi 是不能决定完整的名字到底是什么样子,
那必须依赖特定的命名服务系统。
但是,jndi 仍然提供了一个接口 Context.getNameInNamespace()。这个方法的返回结果,依赖于你所使用
的命名服务系统。
下面是一个例子:
- // Create the initial context
- Context ctx = new InitialContext(env);
- // Perform the lookup
- Context jon = (Context)ctx.lookup("cn=Jon Ruiz,ou=people");
- String fullname = jon.getNameInNamespace();
输出结果:
cn=Jon Ruiz,ou=people,o=JNDItutorial
1.5、名字解析
名字解析的意思就是根据一个名字的字符串形式,把它解析成结构化的形式,如混合名字或复合名字。
jndi API里包含了这样的接口,其接口的实现,依赖于特定的SPI。
1.5.1、解析复合名字
下面是一个例子:
- // Parse the string name into a CompositeName
- Name cname = new CompositeName(
- "cn=homedir,cn=Jon Ruiz,ou=people/tutorial/report.txt");
其实这个用法我们早就在前面看过了。
1.5.2、解析混合名字
为了解析混合名字,你必须使用 NameParser 接口,这个接口有一个方法:
- Name parse(String name) throws InvalidNameException
首先,你必须从 SPI 里得到一个 NameParser 接口的实现:
- // Create the initial context
- Context ctx = new InitialContext();
- // Get the parser for LDAP
- NameParser ldapParser =
- ctx.getNameParser("ldap://localhost:389/o=jnditutorial");
- // Get the parser for filenames
- NameParser fsParser = ctx.getNameParser("file:/");
一旦你得到了 NameParser 实例,你就可以用它来把字符串形式的名字转换成结构化的名字。
- // Parse the name using the LDAP parser
- Name compoundName = ldapParser.parse("cn=John Smith, ou=People, o=JNDITutorial");
- // Parse the name using the LDAP parser
- Name compoundName = fsParser.parse("tmp/tutorial/beyond/names/parse.html");
如果解析错误,那么会得到 InvalidNameException 。
尽管 parse() 方法可以返回一个结构化的名字,但是我们还是建议仅仅用它来处理混合名字,而不要
处理复合名字。
parse()返回的对象未必一定是 CompoundName 类,只要返回的对象实现了 Name 接口就可以--这依赖
于特定的SPI。
1.6、动态生成复合名字
之前我们介绍了访问,操作复合名字的方法。但是还有一些难以处理的情况需要我们去面对。
例如,如果你要给一个复合名字增加一个组件的话,你是增加复合名字的组件还是混合名字的组件?
如果是混合名字的组件,那么使用什么规则呢?
例如我们有这样一个复合名字:
cn=homedir,cn=Jon Ruiz/tutorial
现在你需要增加一个文件名字,如果是windows系统,那么就是:
cn=homedir,cn=Jon Ruiz/tutorial\report.txt
然后我们再增加一个LDAP混合名字 ou=People,那么就需要使用LDAP的规则:
cn=homedir,cn=Jon Ruiz,ou=People/tutorial\report.txt
在这个例子中,我们使用了不同的命名服务系统,我们需要知道什么时候因该使用什么样的命名系统
的规则,这真的很麻烦。
为了解决这个问题,jndi API 提供了一个接口 Context.composeName(),它用来动态的组装名字,
当然依赖于特定的SPI了。
你需要给它提供两个参数:一个是需要追加的组件,一个是被追加的组件的名字。
下面是一个例子:
// Create the initial context
Context ctx = new InitialContext(env);
// Compose a name within the LDAP namespace
Context ldapCtx = (Context)ctx.lookup("cn=Jon Ruiz,ou=people");
String ldapName = ldapCtx.composeName("cn=homedir", "cn=Jon Ruiz,ou=people");
System.out.println(ldapName);
// Compose a name when it crosses into the next naming system
Context homedirCtx = (Context)ctx.lookup(ldapName);
String compositeName = homedirCtx.composeName("tutorial", ldapName);
System.out.println(compositeName);
// Compose a name within the File namespace
Context fileCtx = (Context)ctx.lookup(compositeName);
String fileName = fileCtx.composeName("report.txt", compositeName);
System.out.printl