java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf ·...

69
国产中间件标准体系 Java企业版命名服务规范 北京大学软件所

Upload: others

Post on 26-Dec-2019

29 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

国产中间件标准体系Java企业版命名服务规范

北京大学软件所

Page 2: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

国产中间件标准体系Java企业版命名服务规范北京大学软件所版权 © 2010 北京大学

Page 3: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

iii

目录

声明 .............................................................. vii引言 ............................................................. viii范围 ............................................................... ix术语、定义和缩略语 .................................................. x1. JNDI概述 ......................................................... 1

1.1. 体系结构 ................................................... 11.2. 设计原则 ................................................... 2

1.2.1. 保持一致性和直观性 ................................... 21.2.2. 按功能划分层级结构 ................................... 21.2.3. 有限支持常见命名服务及其特性 ......................... 21.2.4. 无缝集成 ............................................. 21.2.5. 支持轻量目录访问协议 ................................. 3

2. JNDI API ........................................................ 42.1. 使用示例 ................................................... 4

2.1.1. Context使用示例 ...................................... 42.1.2. DirContext使用示例 ................................... 42.1.3. LdapContext使用示例 .................................. 5

2.2. 基本概念 ................................................... 62.2.1. 命名上下文 ........................................... 62.2.2. 名称 ................................................. 62.2.3. 绑定 ................................................. 72.2.4. 名称解析 ............................................. 72.2.5. 目录对象 ............................................. 82.2.6. 初始上下文 ........................................... 8

2.3. 接口概述 ................................................... 82.3.1. 命名包(javax.naming) ............................... 82.3.2. 目录包(javax.naming.directory) .................... 122.3.3. 事件包(javax.naming.event) ........................ 162.3.4. LDAP包(javax.naming.ldap) ......................... 18

2.4. 配置 ...................................................... 202.4.1. 环境属性 ............................................ 202.4.2. 上下文环境 .......................................... 21

2.5. 安全考虑 .................................................. 232.5.1. JNDI类 .............................................. 232.5.2. 共享命名上下文 ...................................... 232.5.3. 动态类加载 .......................................... 24

3. JNDI SPI ....................................................... 253.1. 服务提供者 ................................................ 253.2. 创建服务提供者 ............................................ 26

3.2.1. 实现命名上下文接口 .................................. 263.2.2. 对象支持 ............................................ 263.2.3. 联合支持 ............................................ 303.2.4. 转介支持 ............................................ 333.2.5. 模式支持 ............................................ 343.2.6. 事件支持 ............................................ 353.2.7. 上下文环境支持 ...................................... 35

Page 4: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

国产中间件标准体系Java企业版命名服务规范

iv

3.3. JNDI框架 .................................................. 363.3.1. 初始上下文 .......................................... 373.3.2. 工厂机制 ............................................ 40

A. 标准JNDI环境属性 ............................................... 48B. 服务提供者示例 ................................................. 50

B.1. 命名上下文实现 ........................................... 50B.2. 名称解析器 ............................................... 55B.3. 初始上下文工厂 ........................................... 56

Page 5: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

v

插图清单

1.1. JNDI体系结构 ................................................... 12.1. 复合命名空间示例 ............................................... 72.2. 模式树示例 .................................................... 163.1. 在同类型的Context中贯通解析 ................................... 303.2. 贯通解析到Context扩展类型 ..................................... 313.3. 在创建对象时处理结构化引用 .................................... 423.4. 在创建对象时处理例外情况 ...................................... 44

Page 6: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

vi

表格清单

A.1. 标准JNDI环境属性 ............................................. 48

Page 7: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

vii

声明本规范兼容JNDI 1.2规范。为此,本规范中使用的术语、Java类名或接口名与

JNDI 1.2规范保持一致或为JNDI 1.2规范中相应的中文表示。在组织结构和内容构成上,为方便阅读和理解,本规范在JNDI 1.2规范的基础上有较大调整,删减了JNDI 1.2规范中部分属于建议性的内容,添加了一些JNDI 1.2规范中没有的内容,修改了对JNDI 1.2规范中某些内容的表述方式。

本声明的修改权、最终解释权归北京大学软件研究所所有。

Page 8: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

viii

引言中间件是国家信息化建设的核心基础软件,在众多行业的国家信息化工程中发

挥着重要作用。现有市场上的中间件产品主要遵循国外制定的标准规范,这些规范主要由国际中间件行业跨国公司主导制定,基于国外中间件市场行业经验和其垄断中间件产品技术保护需求,难以完全满足国内各行业的信息化建设需要和国内中间件产业快速发展的需要。因此,需要建立国际兼容的、具有自主知识产权的国产中间件标准体系,进而在该标准体系指导下构建国产中间件参考实现与平台,从技术、产品与产业运作机制等方面探索适合国产中间件发展的一体化方案。

本规范是上述国产中间件标准体系的组成部分,为设计和实现Java企业版应用服务器中的命名服务提供指南。

Page 9: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

ix

范围本规范定义了JNDI的命名服务访问接口和命名服务接入方式,介绍了JNDI框架

的工作机制。本规范主要面向两类人群:通过JNDI访问命名服务的应用程序开发人员;想要在JNDI中接入命名服务的服务提供者开发人员。

Page 10: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

x

术语、定义和缩略语• JNDI (Java Naming and Directory Interface)

一套为Java应用提供命名和目录服务访问能力的编程接口,通过使用该套接口,Java应用能够以一致的方式访问各种不同的命名服务。

• 命名服务

本规范中如不特别说明,一律指代“命名或目录服务”。命名或目录服务是对外提供对象注册、查询接口的进程或服务,如域名服务器提供的域名管理和解析服务、LDAP服务器提供的用于发布、查询目录信息的目录服务等。

• 服务提供者

本规范中如不特别说明,一律专指“命名服务提供者”。命名服务提供者在JNDI和具体的命名服务之间充当协议转换器的角色,执行协议转换逻辑。另外,命名服务提供者还可以实现为自身即包含命名服务的功能。

• 命名系统

相互连接的相同类型(有相同命名约定)的命名上下文的集合,提供了一个具有相同语义的操作集,对外提供命名服务。

• 命名上下文

维护了名称与对象(对于目录上下文可能还有对象属性)之间映射信息的集合,是JNDI应用进行各种命名操作的基础。

• 命名空间

命名系统中所有名称的集合。在JNDI中命名空间具有树形结构,在该树形结构中,中间节点表示命名上下文,叶节点表示命名上下文或绑定到命名上下文中的对象。

• 命名对象

在命名服务中注册的对象,在JNDI的不同层次可能表现为不同的形式,如在JNDI应用中是Java对象,而在底层的命名服务中可能就是存储在磁盘上的对象引用信息。

• 名称

在命名服务中注册的对象的标识。JNDI中存在两类不同的名称:组合名称,在单个命名系统的局部命名空间内标识对象;复合名称,跨越了多个命名系统的名称。

• 绑定

名称和命名对象(对于目录上下文可能还有对象属性)之间的关联关系。

Page 11: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

术语、定义和缩略语

xi

• 名称解析

JNDI使用名称在命名上下文中查找与名称关联的对象的过程。

• 初始上下文

一个命名上下文,维护了一些常用的绑定,是Java应用使用JNDI的起点。

• 目录对象

一种特殊类型的命名对象,可以关联属性,每个属性都有一个字符串类型的标识符和一个值集,属性的值可以是任意类型。

• 模式

描述定义命名空间的结构和命名空间中的对象所关联属性的规则。

• 命名事件

一个由命名服务生成的事件。

• 命名监听者

一个注册命名事件的对象,提供命名事件的处理逻辑。

• 扩展操作

LDAP v3协议(Internet RFC 2251)支持的由标准组织或产品供应商定义的特定于LDAP服务器的操作。

• 控件

LDAP v3协议(Internet RFC 2251)支持的由标准组织或产品供应商定义的包含在LDAP服务器和客户端之间的请求或响应中用于修改所传送操作的行为或指示结果类型的修饰符。

• 主动通告

以异步方式从服务器发送到客户端、且不属于任何客户端请求的消息。

• 环境属性

JNDI应用为命名上下文提供的用于改变命名上下文的行为的一些首选项。

• 上下文环境

JNDI应用为命名上下文提供的所有环境属性的集合。

• 联合

为了完成一个操作,多个命名系统参与名称解析过程。

• 引用

Page 12: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

术语、定义和缩略语

xii

JNDI提供的用于代替那些不适合直接绑定的对象被绑定到命名服务中的一种数据类型,包含在创建或获取实际被绑定对象时需要的参考信息,如命名服务的地址等。

• 转介

将一个来自JNDI应用的请求重定向到另一个位置,在JNDI API中表现为一个异常,命名上下文或JNDI应用通过捕获该异常处理转介:忽略转介或执行转介。

• 转介上下文

转介发生后,用来执行转介的命名上下文,通过表示转介的异常能够获得转介上下文。

• 贯通解析

在名称解析过程中,将不被某些中间命名上下文支持的操作传递到支持该操作的命名上下文去执行的过程。

• 接续操作

在一个跨越了多个命名空间的名称上执行一个操作时,中间命名系统的命名上下文将操作传递到下一个命名系统的过程。

• 接续上下文

在接续操作的过程中,能够将操作从当前命名系统传递到下一个命名系统的命名上下文。

• 对象工厂

能够使用存储在命名空间中的信息创建对象的一类Java对象,实现了JNDI提供的特定Java接口。

• 状态工厂

能够将对象转换成适合命名上下文实现存储的形式的一类Java对象,实现了JNDI提供的特定Java接口。

• 响应控件工厂

能够将只包含控件标识符(OID)和原始字节序列的控件转换成包含更多可以在JNDI应用中直接使用的有意义信息的控件的一类Java对象,实现了JNDI提供的特定Java抽象类。

Page 13: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

1

第�1�章�JNDI概述

命名或目录服务提供了关于用户、机器、网络、服务和应用等多方面信息的访问入口,在企业内网和因特网中扮演至关重要的角色。JNDI (Java Naming andDirectory Interface)是一套为Java应用提供命名和目录服务访问能力的编程接口。该接口的定义独立于任何具体命名或目录服务的实现,Java应用可以用一种通用的方式访问各种不同的命名或目录服务。另外,JNDI也为命名或目录服务提供了动态接入机制,通过这一机制,各种命名或目录服务可以轻易地向使用JNDI的Java应用提供服务。

为叙述方便,如不特别说明,本规范后续部分提到的“命名服务”一律指代“命名或目录服务”。

1.1.�体系结构

JNDI由JNDI API和JNDI SPI两部分组成。JNDI API允许Java应用以一种通用的方式访问各种不同的命名服务。JNDI SPI被各种服务提供者使用,支持各种命名服务以透明的方式为使用JNDI API的Java应用提供服务。图1.1展示了JNDI的体系结构。

图 1.1. JNDI体系结构

在JNDI体系结构中,处于顶层的是Java应用,Java应用使用JNDI API访问具体的命名服务。处于底层的是各个具体命名服务的服务提供者,服务提供者一方面使用JNDI SPI接入JNDI,为使用JNDI的Java应用提供服务,另一方面以服务特定的协议与特定命名服务通信,在JNDI和具体命名服务之间起转接和桥梁作用。作为示例,图中列出了几种常见命名服务,如RMI、CORBA、LDAP、NDS的服务提供者。需要指出的是,服务提供者本身也可以实现为一项命名服务。附录B中给出的服务提

Page 14: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI概述

2

供者示例即是属于这种情况。JNDI框架,核心是命名管理器,处在Java应用和各个服务提供者中间,对上提供JNDI API供Java应用访问命名服务,对下提供JNDI SPI供服务提供者接入命名服务。

JNDI的这种体系结构一方面统一了Java应用访问各种不同命名服务的方式,从而简化了应用程序的工作,另一方面使得命名服务动态可插拨,只要有需要,任何命名服务都可以通过相应的服务提供者透明地集成到JNDI中来,而不需要应用程序做太多修改,提高了应用程序的适用性和扩展性。

1.2.�设计原则

JNDI API的设计遵循了几条重要的原则。

1.2.1.�保持一致性和直观性

只要有可能,都使用Java开发环境中已有的组件,这不仅使得JNDI和已有的Java平台核心类保持一致,还避免了不必要的类增殖。

Java编程语言面向对象的本质,为设计简单、直观的API提供了可能。在JNDIAPI中,目录服务的功能是通过扩展基本的命名服务(不涉及目录相关操作)的功能提供的。

1.2.2.�按功能划分层级结构

JNDI API被设计成具有层级结构,一个应用程序员如果仅对某一项特定的命名服务功能感兴趣,就不需要知道比这更高级的功能。JNDI API尽量保持低层的简洁性,并把常用的功能放到低层,而把相对复杂的功能放到高层。

1.2.3.�有限支持常见命名服务及其特性

这一原则包含了两层意思:首先,支持常见命名服务使得Java应用可以使用大量已有的命名服务,如DNS、NDS、NIS(YP)、X.500和LDAP;其次,支持具体命名服务的部分而不是全部特性,这有助于限制具体命名服务的对外表现形式。

为各种命名服务提供统一接口并不表示Java应用不能使用命名服务独有的特性。尽管JNDI API是按照一般情况来设计的,那些确切地知道底层命名服务类型的Java应用同样可以使用JNDI API来访问底层命名服务的独有特性。

1.2.4.�无缝集成

无缝集成非常重要,这不仅是因为集成到JNDI中的各种命名服务存在差异,还因为新的Java应用和服务程序员需要以一种统一的方式导出自己的命名空间和目录对象。JNDI还希望Java应用能够自由选择服务提供者实现,却不需要花费额外开销。例如,一个瘦客户端应用可能需要通过一个代理服务器来访问具体的命名服务,这种情况下就该使用基于代理型协议的实现来简化客户端的工作,而一个注重性能的胖客户端应用就可能使用能够直接访问不同服务器的实现。然而,这种选择操作对应用来说应该是透明的,而且应该推迟到程序运行的过程中执行。

Page 15: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI概述

3

1.2.5.�支持轻量目录访问协议

轻量目录访问协议(LDAP)(Internet RFC 2251)已经在协议级别成为目录访问的标准。所有主流的目录供应商都有支持这个协议的产品。使用JNDI的应用应该能够访问这个标准提供的所有特性。并且,如果有可能,那些已经在这个标准中定义的约定(像指定搜索查询/筛选的约定)也应该被JNDI支持。

Page 16: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

4

第�2�章�JNDI APIJNDI API是Java应用通过JNDI访问命名服务的一套编程接口。本章介绍

JNDI API,应用开发人员在编写JNDI应用之前应该阅读本章内容,需要在JNDI中接入命名服务的开发人员在阅读JNDI SPI相关内容之前也应该对本章内容有基本的了解。JNDI API主要涉及编写JNDI应用需要用到的各个Java类和接口以及JNDI环境配置。

本章首先针对JNDI支持的三种命名服务给出一般情况下的使用示例;接着解释JNDI中的基本概念;第3节具体介绍编写JNDI应用需要用到的Java接口和Java类,这也是应用开发人员阅读的重点;第4节介绍JNDI环境配置;最后说明几点安全相关的考虑。

2.1.�使用示例

2.1.1.�Context使用示例

JNDI使用Context对基本的命名服务提供支持。假定存在一个命名服务在组织内部提供可用网络打印机的注册、查询服务,并提供了合适的服务提供者。服务提供者为Java应用提供的用于表示打印机的Java接口有如下定义。

public interface Printer { public void print(java.io.InputStream dataStream) throws java.io.IOException;}

这样,JNDI应用就可以像下面这样使用JNDI的Context接口在命名服务中查找打印机,并通过查到的Printer对象指示打印机执行打印操作。

java.util.Properties env = new java.util.Properties();// set environment properties in env herejavax.naming.Context ctx = new javax.naming.InitialContext(env);Printer printer = (Printer)ctx.lookup("some-printer-name");java.io.InputStream dataStream = null;// open data stream and assign its reference to dataStreamprinter.print(dataStream);

2.1.2.�DirContext使用示例

JNDI使用DirContext对目录服务提供支持。再次考虑前面的打印机例子,可能命名服务不仅维护了被服务提供者用来创建Printer对象需要的打印机地址信息,还维护了打印机本身的一些固有属性,如打印机类型、型号、分辨率等。如果JNDI应用需要查找分辨率为600×600dpi的激光打印机,而且服务提供者也提供了合适的DirContext实现,JNDI应用就可以像下面这样使用DirContext。

java.util.Properties env = new java.util.Properties();// set environment properties in env herejavax.naming.directory.DirContext ctx = new javax.naming.directory.InitialDirContext(env);

Page 17: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

5

// create Resolution instanceResolution resolution = new Resolution(600, 600);// get PrinterType instancePrinterType printType = PrintType.LASER;javax.naming.directory.Attributes attrs = new javax.naming.directory.BasicAttributes();javax.naming.directory.Attribute attr1 = new javax.naming.directory.BasicAttribute( "resolution", resolution);javax.naming.directory.Attribute attr2 = new javax.naming.directory.BasicAttribute( "print-type", printType);// set attribute indicating a 600×600dpi resolutionattrs.put(attr1);// set attribute indicating a laser printerattrs.put(attr2);javax.naming.NamingEnumeration<javax.naming.directory.SearchResult> enum = ctx.search("some-name-of-the-start-context", attrs);if(enum.hasMore()) { javax.naming.directory.SearchResult result = enum.next(); Printer printer = (Printer)result.getObject(); java.io.InputStream dataStream = null; // open data stream and assign its reference to dataStream printer.print(dataStream);} else { // handle empty result}

2.1.3.�LdapContext使用示例

JNDI使用LdapContext对使用LDAP v3协议的目录服务提供支持。继续考虑前面的打印机例子,可能维护打印机信息的命名服务使用LDAP v3协议,而服务提供者也提供了合适的LdapContext实现,这时JNDI应用就可以使用LDAP v3协议中的控件来影响命名服务执行某种Context或DirContext操作的行为。

假定服务提供者为JNDI应用提供了用来指示命名服务对搜索结果排序的LDAPv3请求控件SortControl,而JNDI应用需要对打印机搜索结果按打印机型号排序,这时JNDI应用就可以像下面这样使用LdapContext。

java.util.Properties env = new java.util.Properties();// set environment properties in env herejavax.naming.ldap.LdapContext ctx = new javax.naming.ldap.InitialLdapContext(env);SortControl control = new SortControl(new String[] { "printer-model"}); //create sort controlctx.setRequestControls( new javax.naming.ldap.Control[] {control});javax.naming.NamingEnumeration<javax.naming.directory.SearchResult> enum = ctx.search("some-name-of-the-start-context", null);

Page 18: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

6

while(enum.hasMore()) { javax.naming.directory.SearchResult result = enum.next(); String name = result.getName(); String model = result.getAttributes(). get("printer-model").get(); // output the <name, model> pair}

2.2.�基本概念

2.2.1.�命名上下文

命名上下文维护了名称与对象(对于目录上下文可能还有对象属性)之间的映射信息,是JNDI应用进行各种命名操作的基础,因此服务提供者必须提供命名上下文的实现。

在此,有必要明确命名或目录系统、命名服务、命名空间以及命名上下文之间的关系。命名或目录系统是相同类型(有相同命名约定)的命名上下文的集合,提供了一个具有相同语义的操作集,对外提供命名服务。每个命名服务都有自己的局部命名空间。命名空间是命名或目录系统中所有名称的集合,在JNDI中命名空间具有树形结构,在该树形结构中,中间节点表示命名上下文,叶节点表示命名上下文或绑定到命名上下文中的对象。

2.2.2.�名称

JNDI使用名称来命名对象,JNDI命名空间中的每个对象都至少有一个名称。几乎所有JNDI应用通过命名上下文执行的操作都需要指定一个名称,命名上下文使用这个名称来定位操作的目标对象。需要强调的是,命名上下文是一种特殊的对象,因此也拥有自己的名称。

每个名称都是由一系列称为命名约定的语法规则生成的。命名约定可以在JNDI应用中设定或使用JNDI的默认值,但对于任何命名约定都适用的是名称的构成方式。

在JNDI中,任何名称都是一个有序的组件序列。两类常用的名称是组合名称和复合名称。组合名称中的组件是由命名约定定义的不可分割的原子名称。复合名称中的组件则可以是原子名称或组合名称。

组合名称通常用来在单个命名系统的局部命名空间内命名对象。例如,在UNIX的路径名中,原子名称是从左到右排列的,并由斜线(“/”)分割。UNIX路径名“usr/local/bin”就是一个由原子名称“usr”、“local”和“bin”组成的组合名称。在因特网域名系统中,原子名称从右到左排列,并由点号(“.”)分隔。因此,域名“www.pku.edu.cn”就是一个由“cn”、“edu”、“pku”和“www”四个原子名称组成的组合名称。

复合名称通常是跨越了多个命名系统的名称,名称中的每个组件都是来自一个命名系统的局部命名空间的一个名称。例如,“wang.stuff:/home/wang/abc”可以是一个复合名称表示,该复合名称包含一个主机命名空间的主机名“wang.stuff”以及一个UNIX文件命名空间的文件名“/home/wang/abc”。再举一个例子,因特网URL“http://www.pku.edu.cn/public/index.html”是一个复合

Page 19: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

7

名称表示,该复合名称包含一个来自URL方案标识命名空间中的方案标识“http”、运行Web服务的机器在DNS命名空间中的名称“www.pku.edu.cn”和文件系统命名空间中的文件名“public/index.html”。复合名称将多个命名系统的局部命名空间连接在一起,构成复合命名空间。图2.1展示了一个复合命名空间的示例。

图 2.1. 复合命名空间示例

2.2.3.�绑定

原子名称和对象(对于目录上下文可能还有对象属性)之间的关联称为绑定。一个命名上下文维护了一个绑定的集合,并且通过这些绑定实现名称与对象之间的映射。

2.2.4.�名称解析

名称解析是JNDI使用名称在命名上下文中查找与名称关联的对象的过程。在这个过程中,命名上下文主要参考在内部维护的绑定。一个命名上下文只能解析名称

Page 20: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

8

中对应于当前命名上下文的一个原子名称,即是部分地解析名称,并获得部分解析结果,这个结果可能是一个新的命名上下文,下一个原子名称的解析工作在这个命名上下文中进行。为了完全解析名称得到最终的对象,可能需要一个命名系统中的多个命名上下文甚至多个命名系统的命名上下文参与到这个过程中,这取决于名称的类型以及名称中组件的个数。例如,如果名称是一个组合名称,可能就只需要单个命名系统中的命名上下文参与解析过程;如果名称是一个跨越了多个命名系统的复合名称,就需要来自多个命名系统的命名上下文参与解析过程。

2.2.5.�目录对象

目录对象是一种特殊类型的对象。和普通对象一样,目录对象也可以被绑定到命名上下文。另外,目录对象还可以关联属性。与目录对象关联的每个属性都有一个字符串类型的标识符和一个值集,属性的值可以是任意类型。目录对象需要由合适的命名上下文提供支持。

需要注意的是,命名上下文实现在实现属性模型时可以有两种选择:目录对象必须是一个命名上下文,以便在目录对象内维护目录对象的属性;目录对象可以是任意类型的对象,目录对象的属性由目录对象被绑定到的命名上下文维护。具体是哪种情况,JNDI应用需要参考命名上下文实现的说明文档。

2.2.6.�初始上下文

初始上下文是JNDI应用使用JNDI的起点,JNDI中的所有命名操作都可以通过初始上下文执行。从JNDI应用的角度来看,初始上下文与一般的命名上下文一样,维护了一个绑定的集合。初始上下文中的所有绑定由服务提供者预先设定,以使JNDI应用可以通过初始上下文方便地取得常用的对象,如某些频繁访问的命名上下文。在前面的复合命名空间示例中,初始上下文就绑定了来自DNS命名空间和LDAP命名空间的命名上下文。

2.3.�接口概述

根据功能的不同,JNDI API提供的所有Java类和接口被划分到四个包中。

• 命名包(javax.naming),包含访问基本的命名服务需要用到的Java类和接口。

• 目录包(javax.naming.directory),扩展了命名包,包含访问目录服务额外需要用到的Java类和接口。

• LDAP包(javax.naming.ldap),包含为LDAP v3扩展操作和控件提供支持的Java类和接口。

• 事件包(javax.naming.event),包含为命名服务中的事件通告模型提供支持的Java类和接口。

2.3.1.�命名包(javax.naming)

命名包(javax.naming)包含访问基本的命名服务需要用到的Java类和接口。

Page 21: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

9

2.3.1.1.�命名上下文

命名上下文在JNDI API中对应Context接口,该接口是JNDI API中的核心接口。Context接口定义了一些基本的操作,如增加一个名称/对象绑定、查找绑定到指定名称的对象、列出命名上下文中的所有绑定、移除一个名称/对象绑定、创建或删除一个与当前命名上下文相同类型的子命名上下文等。

public interface Context { public Object lookup(Name name) throws NamingException; public void bind(Name name, Object obj) throws NamingException; public void rebind(Name name, Object obj) throws NamingException; public void unbind(Name name) throws NamingException; public void rename(Name old, Name new) throws NamingException; public NamingEnumeration listBindings(Name name) throws NamingException; public Context createSubcontext(Name name) throws NamingException; public void destroySubcontext(Name name) throws NamingException; ...}

Context接口中的每个命名方法都有一个名称作为参数,命名上下文实现通过解析这个名称获得目标命名上下文或对象,由这些命名方法定义的操作在目标命名上下文或对象上执行。如果名称为空(""),操作就直接在当前命名上下文上执行。

2.3.1.2.�初始上下文

JNDI API使用InitialContext类表示初始上下文。在JNDI中,没有绝对名称的概念,每个名称都相对于一个命名上下文。一般情况下,JNDI应用都是通过获取InitialContext类的一个实例开始使用JNDI。

public class InitialContext implements Context { public InitialContext()...; ...}

服务提供者在初始上下文中预先设置了若干绑定,以使JNDI应用可以通过初始上下文方便地取得常用的对象,这些对象通常是一些可能被频繁访问的命名上下文,如DNS命名空间中的根命名上下文。

2.3.1.3.�名称

名称是一个有序的组件序列。JNDI API使用Name接口表示名称。

public interface Name … { public Name add(int posn, String comp)

Page 22: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

10

throws InvalidNameException; public Object remove(int posn) throws InvalidNameException; public String get(int posn); public Enumeration<String> getAll(); …}

Context中每个带有名称参数的方法都有两种重载形式:一种重载形式使用Name类型的名称参数;另一种重载形式使用String类型的名称参数。使用Name参数的重载形式主要面向那些需要操作名称的应用,为诸如创建名称、比较名称成分之类的操作提供便利。使用String参数的重载形式主要面向一些简单应用,像那些只需要使用已经按照命名约定正确格式化的名称查找相应对象的应用。String类型的名称参数表示一个复合名称,而Name类型的名称参数可以表示复合名称或组合名称。

CompositeName类表示一个跨越多个命名空间的复合名称。复合名称中的组件是原子名称或组合名称。如果提供给Context中的方法的Name参数是CompositeName类的一个实例,那么这个Name就是一个复合名称。如果提供给Context中的方法的Name参数不是CompositName类的实例,那么这个Name就是一个组合名称。组合名称是单个命名空间中具有层级结构的名称,可以用CompoundName类或其他实现类来表示。

JNDI为操作字符串形式的复合名称预定义了规则,具体规则可以查阅JNDI API的类说明文档,这里仅给出两个字符串形式的复合名称示例。例如,假定字符串“comp1/comp21.comp22/comp3”表示一个复合名称,那么这个复合名称由三个组件构成,这三个组件依次是“comp1”、“comp21.comp22”和“comp3”。又如,假定字符串“comp1/comp21\/comp22/comp3”也表示一个复合名称,那么这个复合名称也由三个组件构成,三个组件依次是“comp1”、“comp21/comp22”和“comp3”。命名上下文的名称解析器(对应NameParser接口)可以依据与命名上下文关联的语法规则(定义了组件分隔符、组件排列方向等内容)来操作字符串形式的组合名称。

public interface Context { public NameParser getNameParser(Name name) throws NamingException; ...}

2.3.1.4.�绑定

Context.lookup()方法是最常使用的操作,命名上下文实现能够根据JNDI应用提供的名称参数返回JNDI应用需要的合适类型的对象。命名上下文实现完成Context.lookup()方法的基础则是绑定。JNDI API使用Binding类表示绑定。

Context有两个获取命名上下文中所有绑定信息的方法。Context.listBindings()方法返回一个Binding枚举。在构成上,每个Binding实例都是一个包含了被绑定对象的名称、被绑定对象的类名以及被绑定对象的三元组。Context.list()方法返回一个NameClassPair枚举,不同于Binding,每个NameClassPair实例只包含了被绑定对象的名称和被绑定对象的类名,而没有包含

Page 23: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

11

可能需要命名上下文实现花费可观的开销才能获取的被绑定对象。list()方法对于那些需要查找绑定在特定命名上下文中的对象的名称、类型信息但并不需要所有这些被绑定对象的应用,如JNDI的名称浏览器非常有用。

public class NameClassPair... { public String getName()...; public String getClassName()...; ...}public class Binding extends NameClassPair { public Object getObject()...; ...}

2.3.1.5.�引用

不同的命名上下文实现可以在本地使用不同的方式绑定对象。一个特别有用且应该在所有一般目的的命名上下文实现中提供支持的对象是Reference对象。Reference表示对存在于命名服务之外的对象的引用。引用使得JNDI应用看来似乎任意类型的对象都可以被绑定到命名服务中。例如,X.500对象,尽管Java编程语言不对其提供支持,但仍然可以通过引用被绑定到命名服务中。

如果Context.lookup()或Binding.getObject()等方法的结果是一个Reference对象,在返回结果给JNDI应用之前,JNDI会尝试将其转换成实际引用的对象。如果被绑定到一个命名系统的Reference对象实际引用了另一个命名系统的Context对象,这种引用在JNDI中尤其重要,这正是多个独立命名系统在JNDI复合命名空间中连接的方式。

能够被Reference对象引用的对象需要实现Referenceable接口,该接口中唯一的方法getReference()返回一个引用该对象的Reference对象。当将这样一个对象绑定到任何一个命名上下文时,如果对象本身不能在本地存储,命名上下文实现应该在底层系统中存储引用该对象的Reference对象。

每个Reference对象可能包含所引用对象的类名、所引用对象的类文件存在的位置(通常是URL)。另外,每个Reference对象还包含一个RefAddr类型的对象的序列,每个RefAddr又包含一个“类型”字符串和一些寻址数据(通常是一个字符串或者字节数组)。

Reference有一个子类LinkRef。LinkRef用来在JNDI命名空间中添加符号链接。LinkRef包含一个JNDI对象的名称。

2.3.1.6.�转介

一些命名服务支持“转介”的概念。在JNDI中,转介用来将一个来自JNDI应用的请求重定向到另一个位置。JNDI应用可以选择让命名上下文实现自动执行转介或忽略转介,也可以在程序中主动控制转介的执行。JNDI API使用Java抽象类ReferralException表示转介。

public abstract class ReferralException extends NamingException { public abstract Context getReferralContext()

Page 24: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

12

throws NamingException; public abstract Object getReferralInfo(); public abstract void retryReferral(); public abstract boolean skipReferral();}

转介发生后,如果没有事先指定让命名上下文实现自动执行转介或忽略转介,命名上下文实现将抛出一个ReferralException异常。getReferralInfo()方法以适合服务提供者的格式提供关于转介目标位置的信息。JNDI应用不需要检查这些信息,但是JNDI应用可能选择将这些信息呈现给用户以便用户决定是否执行转介。skipReferral()方法丢弃一个转介并继续下次可能存在的转介。若要继续操作,JNDI应用需要在抛出的ReferralException对象上调用getReferralContext()方法获得一个用于执行转介的命名上下文,然后在该命名上下文上重新调用同样的方法,并提供同样的参数。

2.3.2.�目录包(javax.naming.directory)

目录包(javax.naming.directory)扩展了命名包,包含访问目录服务额外需要用到的Java类和接口。

2.3.2.1.�目录对象

JNDI通过在Java接口DirContext中定义为对象关联属性以及检查、更新这些属性的操作支持目录对象。

public interface DirContext extends Context { Public void bind(Name name, Object obj, Attributes attrs) throws NamingException; public Attributes getAttributes(Name name) throws NamingException; public Attributes getAttributes(Name name, String[] attrIds) throws NamingException; public void modifyAttributes(Name name, int modOp, Attributes attrs) throws NamingException; public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException; ...}

DirContext扩展于Context,具有Context的所有特性。另外,DirContext不仅可以将对象绑定到名称,而且能够为对象关联属性。在DirContext中定义的bind()、rebind()和createSubContext()方法便是用来执行这种操作的。

在DirContext中定义的getAttributes()方法返回目录对象的部分或全部属性。目录对象的属性可以通过在DirContext中定义的两种形式的modifyAttributes()方法修改。两种形式的modifyAttributes()方法都要通过参数为每一次属性修改操作指定一种修改操作符,修改操作符可以是ADD_ATTRIBUTE、REPLACE_ATTRIBUTE和REMOVE_ATTRIBUTE其中之一。ADD_ATTRIBUTE指定为属性添加新值,前提是属性已经存在。REPLACE_ATTRIBUTE指定丢弃属性中任何预先存在的值。第一种形式的

Page 25: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

13

modifyAttributes()方法对属性集中的每个属性按指定的修改操作符执行修改操作。第二种形式的modifyAttributes()方法带有一个ModificationItem对象数组作为参数。每个ModificationItem对象指定了要修改的属性和修改操作符。

public class ModificationItem { public ModificationItem(int modOp, Attribute attr) ...; public Attribute getAttribute(); public int getModificationOp(); ...}

如果有可能,命名上下文实现应该将对modifyAttributes()方法的每次调用作为原子操作执行。

2.3.2.2.�属性

一个目录对象可以关联零到多个属性。每个属性拥有一个字符串标识和零到多个任意类型的值。JNDI API使用Attribute接口表示属性。

public interface Attribute ... { public String getID(); public Object get(int n) throws NamingException; public Boolean is Ordered() throws NamingException; ...}

属性的各个值可以是有序的或者无序的。如果值是无序的,重复值不允许出现。如果值是有序的,则允许出现重复值。

Attributes接口将与目录对象关联的各个属性组织到一起。

public interface Attributes ... { public Attribute get(String attrID); public NamingEnumeration getIDs(); public NamingEnumeration getAll(); public Attribute put(Attribute attr); public Attribute remove(String attrID); ...}

JNDI为Attribute和Attributes两个接口提供了基本的实现,分别是BasicAttribute和BasicAttributes。当然,服务提供者和JNDI应用也可以自由使用自己的实现。

需要注意的是,对Attributes和Attribute的更新,例如添加或删除一个属性或属性的值,并不影响与目录对象关联的相应属性。只有使用在DirContext中定义的modifyAttributes()方法才能更新与目录对象关联的属性。

2.3.2.3.�初始上下文

需要执行目录操作的JNDI应用应该使用InitialDirContext代替InitialContext创建初始上下文。

Page 26: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

14

public class InitialDirContext extends InitialContext implements DirContext { public InitialDirContext()...; ...}

然后,JNDI应用就可以在所创建的初始上下文上调用任何在Context或DirContext接口中定义的方法了。

2.3.2.4.�搜索

DirContext接口支持基于内容的搜索。最简单也是最常见的用法,是由JNDI应用指定一个属性集(可能还为部分属性指定了属性值)进行匹配。然后,JNDI应用在DirContext上调用search()方法,该方法返回匹配的目录对象和所请求的属性集。

public interface DirContext extends Context { public NamingEnumeration search(Name name, Attributes matchingAttributes) throws NamingException; public NamingEnumeration search(Name name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException; ...}

搜索结果通过一个NamingEnumeration返回,NamingEnumeration包含一个SearchResult类型对象的枚举。

public class SearchResult extends Binding { public Attributes getAttributes() ...;}

对于更加复杂的情况,还可以指定一个搜索过滤器并且提供控制信息,像搜索范围和结果数上限。搜索过滤器指定遵循Internet RFC 2254for LDAP的语法。SearchControls参数指定像搜索范围之类的内容。搜索范围可以是单个命名上下文,或是命名上下文中所有子命名上下文,或是从当前命名上下文开始的向下整个命名上下文层级结构。

public interface DirContext extends Context { public NamingEnumeration search(Name name, String filter, SearchControls ctls) throws NamingException; public NamingEnumeration search(Name name, String filter, Object[] filterArgs, SearchControls ctls) throws NamingException; ...}

Page 27: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

15

2.3.2.5.�模式

模式描述了定义命名空间的结构和命名空间中的对象所关联属性的规则。模式的粒度可以从同整个命名空间关联的单一模式变化到只描述单个属性的细粒度模式。

由于模式可以被表示成一棵信息树,在设计JNDI API时很自然就想到使用已经在JNDI中定义的命名和目录接口来访问模式。这使得JNDI应用能够以一种统一的方式来访问命名空间的模式部分和对象部分。

DirContext.getSchema()方法读取同目录对象关联的模式树的根。通过这个树根可以获取像ClassDefinition、AttributeDefinition和SyntaxDefinition这样特定于某一类定义的子模式。模式树的根及其子孙节点都是DirContext类型的对象。DirContext.getSchemaClassDefinition()方法返回一个包含了一个特定目录对象的类描述的DirContext。

public interface DirContext extends Context { public DirContext getSchema(Name name) throws NamingException; public DirContext getSchemaClassDefinition(Name name) throws NamingException; ...}

另外,同属性关联的模式可以通过Attribute.getAttributeDefinition()方法和Attribute.getAttributeSyntaxDefinition()方法来访问。

public interface Attribute ... { public DirContext getAttributeDefinition() throws NamingException; public DirContext getAttributeSyntaxDefinition() throws NamingException; ...}

图2.2展示了模式信息的关联、组织方式。

Page 28: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

16

图 2.2. 模式树示例

2.3.3.�事件包(javax.naming.event)

事件包(javax.naming.event)包含用于在命名服务中支持事件通告的Java类和接口。

2.3.3.1.�命名事件

命名事件表示一个由命名服务生成的事件。JNDI API使用Java类NamingEvent表示命名事件。

public class NamingEvent extends java.util.EventObject { public int getType(); public Binding getOldBinding(); public Binding getNewBinding(); ...}

NameEvent.getType()方法返回命名事件的类型。NamingEvent类定义了四种类型的命名事件,分别是:OBJECT_ADDED、OBJECT_REMOVED、OBJECT_RENAMED和OBJECT_CHANGED。这四种类型可以分成两个类别:影响命名空间的事件,如添加、删除、重命名一个对象;影响对象内容的事件。

除了事件的类型,命名事件还包含其他关于变更的信息,例如对象在变更前和变更后的信息。

Page 29: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

17

2.3.3.2.�命名监听者

命名监听者是一个注册命名事件的对象,在JNDI API中使用NamingListener接口表示。每种类型的命名事件都由NamingListener的一个相应子类型处理。NamespaceChangeListener接口表示一个关注命名空间变更事件的监听者,而ObjectChangeListener接口表示一个关注对象内容变更事件的监听者。一个监听者实现需要实现其中的一个或两个接口,这取决于所关注事件的类型。

2.3.3.3.�注册命名监听者

EventContext接口和EventDirContext接口分别扩展了Context接口和DirContext接口,并定义了注册命名监听者的方法。

public interface EventContext extends Context { public void addNamingListener(Name target, int scope, NamingListener l) throws NamingException; public void removeNamingListener(NamingListener l) throws NamingException; public boolean targetMustExist() throws NamingException;}

与Context接口中的方法一样,addNamingListener()方法也有一个接受String类型的名称参数的重载形式,这个名称参数即是方法声明中的“target”。“scope”参数指定要监听的范围:仅绑定到“target”的对象;绑定到“target”的对象的直接子节点;以绑定到“target”的对象为根的整棵子树。

在尚未绑定对象的名称上注册命名监听者是允许的,但前提是这种用法被服务提供者和底层协议或服务支持。JNDI应用可以使用targetMustExist()方法检查EventContext是否支持在尚未绑定对象的名称上注册命名监听者。

public interface EventDirContext extends EventContext, DirContext { public void addNamingListener(Name target, String filter, SearchControls ctls, NamingListener l) throws NamingException; public void addNamingListener(Name target, String filter, Object[] filterArgs, SearchControls ctls, NamingListener l) throws NamingException; ...}

EventDirContext接口扩展EventContext接口和DirContext接口,定义了注册命名监听者的方法,允许命名监听者监听由搜索过滤器(Internet RFC 2254)确定的对象。

与DirContext接口中的方法一样,addNamingListener()方法也有一个接受String类型的名称参数的重载形式。

如果在EventContext/EventDirContext实例上调用了addNamingListener()方法,该EventContext/EventDirContext实例就是那些(潜在)生成事件的事件源,

Page 30: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

18

如果注册的命名监听者在NamingEvent上调用getSourse()方法或getEventContext()方法,结果将是该EventContext/EventDirContext实例。

2.3.3.4.�异常处理

当一个命名监听者在一个命名上下文上注册的时候,该命名上下文可能需要做一些内部处理以收集生成事件所需的信息。例如,命名上下文可能需要向服务器发送请求,要求监听服务器上的某些变更。如果由于发生错误使得事件相关的信息不能被收集,命名监听者将永远不能得到事件通告。在这种情况下,命名上下文将抛出一个NamingExceptionEvent异常给命名监听者,并自动从已注册命名监听者列表中删除该命名监听者。

NamingListener接口定义了namingExceptionTrown()方法,命名上下文使用该方法将这类错误通知给命名监听者。

public interface NamingListener extends java.util.EventListener { public void namingExceptionThrown(NamingExceptionEvent evt);}

2.3.4.�LDAP包(javax.naming.ldap)

LDAP包(javax.naming.ldap)包含为LDAP v3扩展操作和控件提供支持的Java类和接口。

2.3.4.1.�扩展操作

在访问目录服务时,除了可以指定像搜索、修改这类在JNDI中明确定义的操作,LDAP v3协议(Internet RFC 2251)还提供了一种在LDAP客户端和服务器之间传送待定义操作命令的方式,这些操作被称为扩展操作。一个扩展操作可能由一个像IETF这样的标准组织定义,或者由产品供应商定义。

LdapContext接口定义了一个用于执行扩展操作的方法。

public interface LdapContext extends DirContext { public ExtendedResponse extendedOperation( ExtendedRequest request) throws NamingException; ...}

ExtendedRequest接口表示提供给扩展操作的参数,而ExtendedResponse接口表示执行扩展操作的结果。每个ExtendedRequest或ExtendedResponse包含一个扩展操作标识和一个使用ASN.1 BER编码格式编码的请求/响应内容的字节数组。

通常JNDI应用并不直接处理ExtendedRequest/ExtendedResponse接口,而是处理这两个接口的实现类。定义扩展操作的组织或产品供应商需要为JNDI应用提供这些接口的实现类。ExtendedRequest接口的实现类应该提供合适的构造方法接受那些JNDI应用易于构造且类型安全的参数。而ExtendedResponse接口的实现类应该提供合适的方法以适合JNDI应用使用且安全的类型返回响应数据。编码和解码BER内容的工作在实现类的内部完成。

Page 31: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

19

例如,假定一个LDAP服务器支持一个“get time”扩展操作,服务器需要提供像GetTimeRequest和GetTimeResponse这样的类,以使JNDI应用可以方便地使用这个扩展操作。JNDI应用按下面的方式使用这两个类。

GetTimeResponse resp = (GetTimeResponse)ctx.extendedOperation(new GetTimeRequest());long time = resp.getTime();

2.3.4.2.�控件

LDAP v3协议(Internet RFC 2251)允许在请求或响应中使用一些待定义的修饰符扩充请求或响应的含义,这些修饰符即是控件。包含在请求中的控件称为请求控件,包含在响应中的控件称为响应控件。一个控件可以由像IETF这样的标准组织定义,或者由产品供应商定义。请求控件和响应控件没有必要成对地定义,因此,如果一个请求使用了一个请求控件,不一定存在一个响应控件可以供该请求的响应使用,反之亦然。

存在两类请求控件:连接请求控件,影响LDAP客户端和服务器建立连接的方式;命名上下文请求控件,影响命名上下文方法。

连接请求控件在需要连接或重新连接LDAP服务器时使用。命名上下文请求控件在所有其他LDAP操作命令被发送给LDAP服务器时使用。之所以存在这种区分,是因为JNDI是一个高层次的API,不直接处理连接,连接管理是服务提供者的工作。因此,一个连接可能被多个命名上下文实例共享,而服务提供者可以自由使用自己的算法管理连接和网络使用。于是,当一个方法在命名上下文实例上被调用时,服务提供者不仅要执行相应的LDAP操作,可能还需要做一些连接管理相关的工作。对于连接管理,服务提供者使用连接请求控件,而对于常规的LDAP操作,服务提供者使用命名上下文请求控件。

LdapContext接口定义了处理控件的方法:

public interface LdapContext extends DirContext { public void reconnect(Control[] connCtls) throws NamingException; public Control[] getConnectControls() throws NamingException; public LdapContext newInstance(Control[] reqCtls) throws NamingException; public void setRequestControls(Control[] reqCtls) throws NamingException; public Control[] getRequestControls() throws NamingException; public Control[] getResponseControls() throws NamingException; ...}

JNDI API使用Java接口Control表示控件。控件有一个控件标识和一个由ASN.1BER编码格式编码的控件内容的字节数组。

连接请求控件通过初始上下文的构造器初始化,并被从一个命名上下文导出的其他命名上下文继承。reconnect()方法用来改变命名上下文的连接请求控件。一个命名上下文的连接请求控件可以通过getConnectControls()方法获得。

Page 32: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

20

命名上下文请求控件通过newInstance()方法初始化,并通过setRequestControls()方法改变。newInstance()方法是在多线程访问的情况下创建一个新的命名上下文实例的简便方法。例如,如果多个线程想要使用不同的命名上下文请求控件,每个线程都可以使用这个方法来获得这个命名上下文的拷贝,然后设置或获取命名上下文请求控件而不需要与其他线程同步。

与连接请求控件不同,命名上下文请求控件并不被从一个命名上下文导出的其他命名上下文实例继承。导出的命名上下文实例被初始化为不使用命名上下文请求控件,而必须使用setRequestControls()方法显式地设置导出的命名上下文实例的请求控件。一个命名上下文的命名上下文请求控件可以通过getRequestControls()方法获取。

2.3.4.3.�初始上下文

需要使用LDAP扩展操作或控件的JNDI应用可以使用InitialLdapContext代替InitialContext或InitialDirContext创建初始上下文。

public class InitialLdapContext extends InitialDirContext implements LdapContext { public InitialLdapContext() ...; public InitialLdapContext(Hashtable env, Control[] connCtls)...;}

然后,JNDI应用就可以在初始上下文上调用在Context、DirContext或LdapContext接口中定义的方法了。使用接受一个connCtls参数的构造方法,可以指定在建立到LDAP服务器的连接时使用的控件。

2.3.4.4.�主动通告

除了客户端和服务器之间那种常规的请求/响应交互方式,LDAP v3还提供主动通告——以异步方式从服务器发送到客户端、且不属于任何客户端请求的消息。

JNDI使用事件模型来支持主动通告。JNDI API为主动通告定义了事件类UnsolicitedNoitificationEvent和相应的监听者接口UnsolicitedNotificationListener。JNDI应用调用EventContext.addNamingListener()方法,并提供一个UnsolicitedNotificationListener参数,以注册接收UnsolicitedNotificationEvent。

2.4.�配置

JNDI应用除了可以使用由JNDI API提供的Java类和接口来执行各种命名操作,还可以使用配置向命名上下文提供一些首选项,从而改变命名上下文的某些行为。这些首选项以环境属性的形式提供给命名上下文,而所有这些环境属性构成了命名上下文的上下文环境。

2.4.1.�环境属性

环境属性有几个不同的类别:

Page 33: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

21

• 标准JNDI环境属性。这些属性由JNDI定义,并且在所有服务提供者中通用。这些属性使用相对通用的名称定义。例如,环境属性“java.naming.security.principal”指定用来在命名服务中认证的安全主体。各个服务提供者将这些环境属性映射到一个适合在内部使用的别名上。这些属性的名称拥有“java.naming”前缀。附录A包含了一个标准JNDI环境属性的列表。Context接口为这种类型的环境属性的名称定义了字符串常量。

• 服务特定的环境属性。这些属性在实现了特定服务或协议的一类服务提供者中通用。这些属性的名称拥有“java.naming.service”前缀,其中“sevice”是服务或协议的名称。例如,“java.naming.ldap”前缀被那些LDAP特定的环境属性使用。

• 特性特定的环境属性。这些属性在支持一项特定特性的一类服务提供者中通用。这些属性的名称拥有“java.naming.feature”前缀,其中“feature”是特性的名称。例如,“java.naming.security.sasl”被那些SASL特性特定的环境属性使用。

• 提供者特定的环境属性。这些属性仅仅在一个特定的服务提供者中适用。这些属性的名称应该拥有一个体现属性名称唯一性的前缀。一种常用的方法是使用服务提供者的包名作为前缀。例如,由于Sun公司的的LDAP提供者主要包含在com.sun.jndi.ldap包中,所以特定于Sun公司的LDAP提供者的属性的名称拥有“com.sun.jndi.ldap”前缀。

尽管JNDI对环境属性的支持非常广泛,但是需要注意,JNDI应用通常不需要处理这些属性,或者可能只需要设置一两个属性。大多数属性都有合理的缺省值,只有当JNDI应用有特殊需要的时候,才应该去调整这些值。

2.4.2.�上下文环境

2.4.2.1.�设定上下文环境

一个命名上下文的上下文环境由一个java.util.Hashtable或任何子类(例如java.util.Properties)表示。上下文环境通常最初作为InitialContext、InitialDirContext或InitialLdapContext构造方法的一个参数指定,并在随后使用从其他来源获取的环境属性扩充。当一个命名上下文方法从一个命名上下文转到另一个命名上下文时,后面命名上下文的上下文环境从前面命名上下文继承。下面的代码创建了一个包含两个安全相关的环境属性的上下文环境,并使用这个上下文环境创建了一个初始上下文。

Hashtable env = new Hashtable();env.put(Context.SEQURITY_PRINCIPAL, "jsmith");env.put(Context.SEQURITY_CREDENTIALS, "xxxxxxx");Context ctx = new InitialContext(env);

在通过查找获得的或从该初始上下文导出的其他命名上下文的上下文环境中将包含这两个属性。

并不是所有环境属性对所有命名上下文都有意义。那些没有意义的环境属性被命名上下文忽略,但是被那些从该命名上下文导出的命名上下文继承,因为这些属性可能对其他命名上下文有意义。

Page 34: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

22

可以使用Context.getEnvironment()方法获得命名上下文的上下文环境。

2.4.2.2.�扩充上下文环境

命名上下文的上下文环境中的环境属性除了可以在构造初始上下文时通过参数指定,还可能从资源文件、系统属性和Applet参数中获得。

当一个普通的Java应用被部署时,该Java应用的类路径中通常存在若干个代码库目录和JAR文件。同样,当一个Applet应用被部署时,该Applet应用也有一个可以从中找到Applet类的代码库和归档文件。JNDI在类路径中查找所有名为“jndi.properites”的资源文件。另外,如果“$JAVA_HOME/lib/jndi.properties”存在并且可读,JNDI也将这个文件作为一个附加的资源文件。(“$JAVA_HOME”是由“java.home”系统属性命名的目录。)所有在这些资源文件中指定的属性都被添加到初始上下文的上下文环境中。

对于那些在不止一个应用资源文件中指定的环境属性,JNDI使用第一个找到的值,在少数JNDI能够确定属性意义的情况下,所有值被串联。例如,如果环境属性“java.naming.factory.object”在三个“jndi.properties”资源文件中被指定,那么对象工厂列表是取自三个文件的属性值的串联。

某些标准JNDI环境属性可以在Java运行时的系统属性或Applet应用的参数列表中设定。这些环境属性是:

java.naming.factory.initialjava.naming.factory.objectjava.naming.factory.statejava.naming.factory.controljava.naming.factory.url.pkgsjava.naming.provider.urljava.naming.dns.url

为了使JNDI能够访问Applet应用的参数,在Applet应用的代码中必须设定环境属性“java.naming.applet”的值为Applet(java.applet.Applet)的一个实例。

这些被设置为系统属性或Applet应用参数的环境属性影响JNDI应用(包括普通Java应用和Applet应用)的所有命名上下文。

2.4.2.3.�修改上下文环境

一个命名上下文的上下文环境可以通过addToEnvironment()方法和removeFromEnvironment()方法修改。

public interface Context { public Object addToEnvironment(String propName, Object val) throws NamingException; public Object removeFromEnvironment(String propName) throws NamingException; ...}

Page 35: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

23

使用addToEnvironment()方法或removeFromEnvironment()方法对上下文环境的更改影响方法调用所在的命名上下文实例。例如,如果为命名上下文指定了一个新的证书,在该命名上下文上后续的方法调用如果需要与服务器通信,新的证书将被使用(在内部可能需要首先创建一个到服务器的新连接)。更新的环境属性被后续从该命名上下文导出的命名上下文实例继承,但是在更新之前已经存在的命名上下文实例的上下文环境不受影响。

在更改上下文环境时,并不要求这个更改操作被确认并且在addToEnvironment()方法或removeFromEnvironment()方法被调用的同时执行。唯一的要求是这个更改操作在下次一个使用这个属性的操作被调用时生效。

对于某些环境属性,JNDI定义了缺省值(见附录A)。其他属性的缺省值可能由一个或一组服务提供者决定。如果上下文环境中不存在一个特定的环境属性,命名上下文认为这个环境属性取缺省值。当一个环境属性从上下文环境中移除时,命名上下文也认为这个环境属性取缺省值。

一些环境属性有一个固定的可接受值的集合,也有一些环境属性的值必须遵循特定的语法。如果出现了一个不可接受的值,一个环境属性特定的异常就会被抛出,如ConfigurationException、IllegalArgumentException。

2.5.�安全考虑

在使用JNDI时,主要有两种配置:使用安全管理器和不使用安全管理器。

普通Java应用在运行时如果没有使用安全管理器,应用的代码是被信任的并且应用可以从本地类路径访问服务提供者。更进一步,服务提供者可以不受限制地访问本地文件或连接位于网络上任何位置的命名服务。

对于Applet应用或那些在运行时使用了安全管理器的普通Java应用,在该应用中同时存在着可信代码和不可信代码。这类应用在访问服务提供者,尤其是要求使用受限资源(像文件系统或网络连接)的服务提供者时,将受到严格限制。

2.5.1.�JNDI类

JNDI中的类不包含本地方法。在Java 2平台上,JNDI中的类在doPrivileged块中访问用作标准JNDI环境属性的系统属性。这使得所有JNDI中的类都能够在安全可控的环境中运行。

JNDI使用了若干系统属性,这使得JNDI应用不需要太多的编码就可以轻易地被配置。然而,一个JNDI应用可能由于所运行平台上安全管理器的限制,不能访问某些或全部系统属性。为此,JNDI还允许这些相同属性被指定为Applet应用的参数,或在资源文件中指定,或作为环境属性传递给命名上下文。

2.5.2.�共享命名上下文

命名上下文应该被视为受保护资源。一段可信代码如果获取了一个命名上下文的Java引用(可能是在通过了命名服务的认证后才获得的),应该避免这个Java引用被其他未经认证或不可信的代码获得。让不可信代码获得命名上下文的访问能

Page 36: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI API

24

力,在访问或更新命名空间中的信息或与命名上下文关联的安全敏感的环境属性时,可能会出现误用或滥用的情况。

2.5.3.�动态类加载

JNDI允许动态加载类文件。在很多JNDI应用场合,这能够给JNDI应用带来很多便利。例如,一个通过JNDI访问EJB的JNDI应用,完全不需要关心服务提供者如何加载EJB存根对象的类文件,只需要简单地使用Java编程语言中的强制类型转换操作符将从命名上下文查找到的对象转换成JNDI应用能够理解的接口类型,然后调用接口中的方法访问EJB。这个例子中,服务提供者就非常有可能通过动态加载的方式获取存根对象的类文件。

当JNDI运行在JDK 1.1.x平台上时,JNDI使用RMI类加载器。只有在安装了安全管理器并且安全管理器允许加载类的情况下,这样的类才能被加载。这样的类被加载后,就在由安全管理器指定的安全上下文中运行。

当JNDI运行在Java 2平台上时,JNDI将尝试使用java.net.URLClassLoader从代码库指定的位置加载这样的类。为了成功加载类,在部署JNDI应用时必须为应用和JNDI以及服务提供者的类授予访问代码库的URL的适当权限。例如,如果URL的方案标识是“http”或“ftp”,就必须授予“java.net.SocketPermission”权限;如果URL的方案标识符是“file”,则必须授予“java.io.FilePermission”权限。

Page 37: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

25

第�3�章�JNDI SPIJNDI SPI是JNDI为那些希望为使用JNDI API的Java应用提供服务的命

名服务提供的一套服务接入机制和编程接口。本章介绍JNDI SPI,需要在JNDI中接入命名服务的开发人员应该在熟悉JNDI API相关内容的基础上阅读本章内容。JNDI SPI主要涉及JNDI的服务接入机制以及创建命名服务提供者需要关注或使用的一些Java类和接口。

本章首先明确JNDI SPI的核心概念——命名服务提供者;第2节具体介绍创建一个命名服务提供者需要做的各项工作,包括必须做的工作和可选做的工作;第3节JNDI框架介绍JNDI接入命名服务的机制。

3.1.�服务提供者

典型的企业计算环境通常存在多个不同类型的命名服务,每个命名服务表示了一个复合命名空间的不同部分。例如,域名系统(DNS)可能被用作顶级命名服务,为一个企业的不同组织提供域名解析服务,而这些组织可能又各自在组织内部使用了一个命名服务,如LDAP、NDS或NIS。从用户的角度来看,这是一个包含了复合名称的命名空间。

这些命名服务可能基于不同的协议来实现,为了能够在Java应用中使用JNDIAPI按照统一的方式访问所有这些命名服务,JNDI需要在Java应用和各个命名服务之间执行协议转换逻辑。JNDI将统一的JNDI API调用请求转换成能够被某个命名服务接受的请求形式并发送给该命名服务,命名服务接受并处理请求,将处理结果封装到响应中发送给JNDI,JNDI再从响应中提取处理结果并包装成适合Java应用使用的Java类型返回给Java应用。

例如,Java应用可能在一个命名上下文上调用lookup()方法,并通过该方法的名称参数传递一个字符串类型的主机域名,希望从该方法返回一个封装了该主机域名对应的IP地址的java.net.InetAddress对象。

Context ctx =… // acquire the appropriate contextInetAddress address = (InetAddress)ctx.lookup("www.sina.com.cn");

为了实现这种使用方式,JNDI需要使用主机域名“www.sina.com.cn”构造一个用于查询IP地址的UDP报文并发往DNS服务器的53端口,DNS服务器收到查询UDP报文后,以递归或迭代的方式查询出对应主机域名“www.sina.com.cn”的IP地址,如“121.194.0.209”,并将查询结果封装到响应UDP报文中发送给JNDI,JNDI再从响应UDP报文中提取IP地址“121.194.0.209”并封装成java.net.InetAddress对象从lookup()方法返回给Java应用。

JNDI被设计成一种接入框架,本身不提供Java应用和大部分已有或新出现的命名服务之间的协议转换逻辑,而是为这类协议转换逻辑提供一种接入方式。想要在JNDI中接入命名服务的命名服务厂商或第三方需要为命名服务提供这种协议转换逻辑。这种协议转换逻辑就是本节关注的命名服务提供者。

另外注意,命名服务提供者完全可以不与其他任何命名服务交互,而在自身即包含命名服务的功能。这种自身即服务的实现方式与前面协议转换逻辑的实现方式在Java应用看来没有区别。

Page 38: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

26

为叙述方便,如不特别说明,本规范后续部分提到的“服务提供者”一律指代“命名服务提供者”。

3.2.�创建服务提供者

创建一个服务提供者最基本的要求也是必须做的工作是提供一个命名上下文实现,即实现Context接口或其任意子接口。而要使所创建的服务提供者在各种使用场合具有最大适用性,在实现Context接口或其任意子接口时,还应该遵循JNDI的一些建议或指南,这些算是可选的工作。

3.2.1.�实现命名上下文接口

实现命名上下文接口(Context或其子接口)是创建一个服务提供者的基本要求。在命名上下文接口中定义的方法较多,JNDI并不要求命名上下文实现必须支持所有这些方法,因此一个命名上下文实现可以只支持那些可能被使用的方法。如果命名上下文实现不支持在命名上下文接口中定义的某个方法,应该在方法实现中抛出OperationNotSupportedException异常。

3.2.2.�对象支持

从命名上下文查找得到的对象和被绑定到命名上下文的对象都是可以在Java应用中直接使用的Java对象。命名上下文实现在实现Context.lookup()方法和Context.bind()方法时最简单的方式是使用由Java应用提供的对象或对象的副本,这需要命名上下文实现在内存中维护所有绑定到命名上下文的对象,附录B的服务提供者示例便是采用这种方式。然而,这种实现方式的缺点也是显然的。可用内存的大小限制了能够绑定到命名上下文中的对象的数量,未持久保存的对象在命名服务重启后不可恢复。而且,一些对象不适合被直接绑定。例如,表示一个数据库连接的java.sql.Connection对象,若是被直接绑定,而在绑定之后的相当长一段时间内不被使用,将导致数据库引擎维护的连接信息长时间不被释放,影响数据库引擎处理数据库访问请求的性能。如果数据库引擎启用了连接超时机制,在超时后使用从命名上下文查找得到的java.sql.Connection对象将引发“连接被关闭”之类的异常。一种有效的绑定方式是在绑定前关闭连接,保存创建连接使用的URL字符串,在Java应用查找java.sql.Connection对象时,使用保存的URL字符串打开新连接并返回。

通常,被绑定到命名上下文的不是Java编程语言中的对象,而仅仅是用来定位或访问实际对象的引用信息,如用于打开java.sql.Connection对象的URL字符串。在绑定对象或查找对象时,命名上下文实现必须做数据类型转换,将由Java应用提供的Java对象转换成适合在命名服务中保存的数据类型,或者使用取自命名服务的对象引用信息创建适合Java应用使用的Java对象。

JNDI为命名上下文实现以独立数据格式的方式从命名服务读对象(查找对象)和向命名服务存对象(绑定对象)提供了工具类,这些工具类与执行实际转换工作的对象工厂或状态工厂交互。JNDI建议命名上下文实现使用这些工具类和工厂完成数据类型转换工作。当然,如果有必要,命名上下文实现也可以使用自己的方式。

Page 39: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

27

3.2.2.1.�读对象

为了使用取自命名服务的对象引用信息创建适合Java应用使用的Java对象,可以有不同的方法。或者命名上下文实现能够访问从命名服务返回的所有对象的实现类;或者命名上下文实现有一个特殊的类加载器用来加载对象的实现类。JNDI提供Reference类作为引用信息的标准表示方式,并鼓励命名上下文实现使用这个类而不是自己的机制来表示引用信息。

命名上下文实现应该使用由JNDI提供的下面两个方法把取自命名服务的对象引用信息转换成Java编程语言的对象。

Object NamingManager.getObjectInstance(Object refInfo, Name name, Context nameCtx, Hashtable env) throws Exception;Object DirectoryManager.getObjectInstance(Object refInfo, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws Exception;

“refInfo”是从底层服务读取的对象引用信息。“name”是对象的名称,“nameCtx”是解析“name”的命名上下文。相比“refInfo”,“name”/“nameCtx”对可以用来获得关于对象的更多信息。“env”是调用getObjectInstance()所在命名上下文的上下文环境。“attrs”是从目录服务读取的与对象关联的属性集合。

NamingManager类中的方法应该由实现了Context接口的命名上下文实现使用,而DirectoryManager类中的方法应该由实现了DirContext接口的命名上下文实现使用。

在以下方法(省略了字符串类型参数的重载形式)构造返回对象时,命名上下文实现应该调用getObjectInstance()方法。

javax.naming.Context.lookup(Name name)javax.naming.Context.lookupLink(Name name)javax.naming.Binding.getObject()javax.naming.directory.SearchResult.getObject()

对于Binding和SearchResult,命名上下文实现应该向这两个类的构造方法传递通过调用getObjectInstance()方法返回的对象,或者重载Binding和SearchResult的缺省实现,以使getObject()方法在返回前调用getObjectInstance()方法。

假设打印机对象由命名空间中的Reference表示。要将一个打印机Reference转换成一个能够在Java应用中直接使用的Printer对象,命名上下文实现应该使用NamingManager.getObjectInstance()方法。

Page 40: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

28

Object lookup(Name name) { ... Reference ref = <some printer reference looked up from naming service>; return NamingManager.getObjectInstance(ref, name, this, env);}

如果打印机以属性集的形式保存在目录服务中,要从这个属性集构造出一个能够在Java应用中直接使用的Printer对象,命名上下文实现应该使用DirectoryManager.getObjectInstance()方法。

Object lookup(Name name) { ... Attributes attrs = <read attributes from directory>; Reference ref = <construct reference from attributes>; return DirectoryManager.getObjectInstance(ref, name, this, env, attrs);}

3.2.2.2.�存对象

在将对象绑定到命名上下文时,命名上下文实现应该使用下面由JNDI提供的两个方法将待绑定的对象转换成适合在命名服务中存储的形式。

Object NamingManager.getStateToBind( Object obj, Name name, Context nameCtx, Hashtable env) throws NamingException;DirStateFactory.Result DirectoryManager.getStateToBind( Object obj, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws NamingException;

“obj”是要绑定的对象。“name”是对象的名称,“nameCtx”是解析这个名称所在的命名上下文,相对“obj”,“name”/“nameCtx”对可以用来获得关于对象的更多信息。“env”是调用getStateToBind()方法所在命名上下文的上下文环境。“attrs”是与对象关联的属性集合。DirStateFactory.Result包含了一个对象和一个属性集合。

NamingManager类中的方法应该由实现了Context接口的命名上下文实现使用,而DirectoryManager类中的方法应该由实现了DirContext接口的命名上下文实现使用。

在以下将对象绑定到命名上下文的方法(省略了字符串类型参数的重载形式)中,命名上下文实现应该调用getStateToBind()方法。

Page 41: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

29

javax.naming.Context.bind(Name name, Object o)javax.naming.Context.rebind(Name name, Object o)javax.naming.DirContext.bind(Name name, Object o, Attributes attrs)javax.naming.DirContext.rebind(Name name, Object o, Attributes attrs)

下面的代码片段展示了命名上下文实现使用getStateToBind()方法支持对象绑定的一种可能方式。

// First do transformationobj = NamingManager.getStateToBind(obj, name, ctx, env);// Check for Referenceableif (obj instanceof Referenceable) { obj = ((Referenceable)obj).getReference();}if (obj instanceof Reference) { // store as ref} else if (obj instanceof Serializable) { // serialize} else { ...}

如果是目录对象,则可能有下面的代码片段。

// First do transformationDirStateFactory.Result res = DirectoryManager. getStateToBind(obj, name, ctx, env, inAttrs);obj = res.getObject();Attributes outAttrs = res.getAttributes();// Check for Referenceableif (obj instanceof Referenceable) { obj = ((Referenceable)obj).getReference();}if (obj instanceof Reference) { // store as ref and add outAttrs} else if (obj instanceof Serializable) { // serialize and add outAttrs} else if (obj instanceof DirContext) { // grab attributes and merge with outAttrs} else { ...}

从上面的代码片段可见,一个命名上下文实现可能能够存储不同类型的对象(Reference,Serializable和DirContext)。如果命名上下文实现不能直接存储Referenceable对象,而getStateToBind()方法返回这类对象,命名上下文实现应该接着调用Referenceable.getReference()方法并存储返回的Reference对象。

Page 42: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

30

如果命名上下文实现能够存储多种不同类型的对象,尝试存储的顺序应该是:Reference、Serializable、DirContext。

3.2.3.�联合支持

当命名上下文获得一个字符串类型的名称参数时,这个名称可能是跨越了多个命名空间的复合名称,也可能是属于单个命名空间的组合名称(可能由一个或多个原子名称组成)。命名上下文实现必须决定名称的哪个部分应该在自己的命名上下文中被解析,而把名称剩下的部分传递给下一个命名上下文。

当命名上下文获得一个Name类型的名称参数时,如果这个名称是CompositeName类型的实例,这个名称将被作为复合名称对待,否则,这个名称将被作为组合名称对待,这个组合名称由CompoundName类或某些其他组合名称实现类实现。

这种跨越多个命名空间的名称解析,在JNDI中称为联合。为了支持联合,命名上下文实现需要处理三个主要的问题:贯通解析;接续操作;定位下一个命名系统。

3.2.3.1.�贯通解析

存在两类贯通解析:在同类型的Context中贯通解析;贯通解析到Context扩展类型。

在一个命名上下文实现中,Context.lookup()操作必须被支持,而命名上下文实现对其他方法的支持则是可选的。但是如果命名上下文要参与联合,那么隐含在所有操作中的名称解析都必须被支持。图3.1展示了一个在同类型的Context中贯通解析名称以执行Context.bind()操作的示例。

图 3.1. 在同类型的Context中贯通解析

如果一个命名上下文不支持Context.bind()操作,而作为这个操作的一个中间命名上下文,这个命名上下文必须执行这个操作的名称解析部分以使这个操作可以传递到下一个命名上下文,这个命名上下文应该只在被要求在自身创建绑定的情况下才抛出OperationNotSupportedException异常。

要调用一个DirContext方法,比如getAttributes(),JNDI应用首先获得一个DirContext类型的初始上下文,再在该初始上下文上执行操作。

Page 43: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

31

DirContext ctx = new InitialDirContext();Attributes attrs = ctx.getAttributes(someName);

从命名上下文实现的角度来看,为了获取属性,getAttributes()方法可能需要穿过多个命名系统,其中一些命名系统可能仅支持Context接口,而不支持DirContext接口。为了在联合中执行这种在Context扩展类型中定义的方法,其他类型的中间命名上下文需要为贯通解析名称到目标类型的命名上下文提供相应的支持。图3.2展示了一个贯通解析到Context扩展类型的示例。

图 3.2. 贯通解析到Context扩展类型

Resolver接口被JNDI框架用来在执行某些在特定Context扩展类型中定义的方法时贯通那些其他类型的中间命名上下文,这些其他类型的中间命名上下文的实现类需要实现Resolver接口。在Resolver接口中定义了两种重载形式的resolveToClass()方法,该方法只是部分地解析名称,在第一个与目标Context扩展类型相符的命名上下文上停止解析。

public interface Resolver { public ResolveResult resolveToClass(Name name, Class contextType) throws NamingException; public ResolveResult resolveToClass(String name, Class contextType) throws NamingException;}

3.2.3.2.�接续操作

在一个跨越了多个命名空间的名称上执行一个操作时,中间命名系统的命名上下文需要将操作传递给下一个命名系统,这种过程称为接续操作。为了接续操作,需要获取在下一个命名系统中作为解析起点的一个命名上下文。命名上下文实现可以使用自己的策略获取这个命名上下文,但JNDI推荐使用下面介绍的CannotProceedException。

中间命名上下文首先创建一个包含了精确描述执行进度的信息的CannotProceedException异常。这些进度描述信息包括解析到的对象、已解析的名称,名称的未解析部分以及异常的环境部分(如果是Context.rename()方法,还包括新名称的已处理部分)。接着,中间命名上下文将CannotProceedException异常传递给静态方法NamingManager.getContinuationContext(),从JNDI获得一个称为接续上下文的命名上下文。CannotProceedException异常中的信息被getContinuationContext()方法用来创建该接续上下文。

public class NamingManager {

Page 44: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

32

public static Context getContinuationContext( CannotProceedException e) throws NamingException; ...}

要为DirContext中的操作获取一个接续上下文,需要使用静态方法DirectoryManager.getContinueatioinDirContext()。

public class DirectoryManager { public static DirContext getContinuationDirContext( CannotProceedException e) throws NamingException; ...}

获得接续上下文后,应该在接续上下文上使用剩下的还未被解析的名称接续操作。例如,当试图接续一个bind()操作时,命名上下文实现中的代码应该是下面这样的。

public void bind(Name name, Object obj) throws NamingException { ... try { internal_bind(name, obj); ... } catch (CannotProceedException e) { Context cctx = NamingManager.getContinuationContext(e); cctx.bind(e.getRemainingName(), obj); }}

在这个例子中,bind()方法使用一个内部方法internal_bind()执行实际的绑定操作,并且在发现要跨越命名系统时抛出CannotProceedException异常。如果操作不能被接续,接续上下文将会抛出CannotProceedException给bind()方法的最初调用者。

3.2.3.3.�定位下一个命名系统

对于一个(多组件)复合名称,解析工作将从一个命名系统进行到下一个命名系统,通常每个组件将由所跨越命名系统的一个相应命名上下文实现解析。一个命名上下文实现把自己不负责的组件传递给下一个命名系统(的上下文实现)。

定位下一个命名系统的上下文实现可以有多种方法。可以显式地使用“连接”,在这种情况下,下一个命名系统的一个命名上下文(或引用命名上下文的Reference)被绑定到上一个命名系统中的一个名称。例如,对于复合名称“cn=fs,ou=eng/lib/xyz.zip”,LDAP名称“cn=fs,ou=eng”可能解析到一个文件系统的命名上下文,而“lib/xyz.zip”将会接着在该文件系统命名上下文中被解析。

也可以隐式地定位下一个命名系统。一种可能的做法是命名上下文实现基于已经解析的对象的特定于服务的知识来选择下一个命名系统。例如,对于复合名称“ldap.wiz.com/cn=fs,ou=eng”,DNS名称“ldap.wiz.com”可能命名了一个

Page 45: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

33

DNS条目,为了获得DNS之后下一个命名系统,DNS命名上下文实现可能使用在该条目中找到的SRV资源记录来创建一个LDAP命名上下文。

在一些联合配置中,在一个命名系统中解析的结果并没有指出下一个命名系统。命名上下文实现唯一可以给出的结论就是在当前命名系统中的解析已经结束,接下来需要到下一个命名系统中去解析了。

例如,假设复合名称“lib/xyz.zip/part1/abc”由两部分组成,其中“lib/xyz.zip”命名了一个ZIP格式的文件,而“part1/abc”是ZIP文件中一个条目的名称。尽管在解析完“lib/xyz.zip”后得到了一个文件对象,但是期望的解析结果是一个可以用来解析ZIP条目名称的命名上下文。类似地,另一个复合名称命名了一个tar格式文件中的一个条目,解析复合名称的文件名组件的期望结果是一个可以用来解析tar条目的命名上下文。

实际上,任何这种类型的命名上下文都可能在文件系统命名空间之下参与联合,解析特定格式的文件中的条目名称。编写文件系统命名上下文实现的开发者和那些编写ZIP文件的命名上下文实现、tar文件的命名上下文实现,或者其他尚待定义格式的文件的命名上下文实现的开发者,可以独立开展工作。

为了支持这种类型的联合,JNDI定义了一种特殊形式的Reference,称为nns引用(“nns”表示“next naming system”)。这个Reference有一个“nns”类型的地址。这个地址的内容是解析到的对象(在上面的例子中,就是ZIP文件)。继续考虑上面文件系统的例子,文件系统命名上下文实现可能按下面的方式创建nns引用。

RefAddr addr = new RefAddr("nns") { public Object getContent() { return theFile; }};Reference ref = new Reference("java.io.File", addr);

接下来,命名上下文实现构造一个CannotProceedException实例,在该实例中,解析到的对象为上面创建的nns引用,已解析的名称由已解析的文件名和一个空组件组成。这个空组件用作下一个命名系统的隐式指针,指出当前的解析工作已经进行到准备解析下一个命名系统的点上。命名上下文实现接下来将CannotProceedException实例传递给getContinuationContext()方法。

getContinuationContext()方法搜索一个接受nns引用的命名上下文实现。例如,ZIP文件的命名上下文实现可能接受一个nns引用和其他提供的信息,如该文件的名称(相对于给定命名上下文)。ZIP文件的命名上下文实现发现该文件是一个ZIP文件,便创建一个命名上下文用来解析该文件中的ZIP条目名称。

3.2.4.�转介支持

LDAP类型的目录服务支持转介,以重定向客户端请求到另一个服务器。转介与联合中的接续操作不同。转介可以呈现给JNDI应用,然后由JNDI应用决定是否执行转介,而CannotProceedException异常只有在不能被继续处理时才抛出给JNDI应用。

Page 46: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

34

一个支持转介的命名上下文实现定义了ReferralException的一个扩展类并且提供所有抽象方法的实现。getReferralContext()方法返回用来执行转介操作的命名上下文,该命名上下文被称作转介上下文。getReferralInfo()方法以一种适合命名上下文实现使用的格式返回关于转介目标的信息。

环境属性“java.naming.referral”指定了命名上下文实现应该如何处理转介。如果命名上下文实现被要求在遇到一个转介时抛出异常,或者在执行转介时遇到了问题,命名上下文实现将抛出一个ReferralException给JNDI应用。为了继续操作,JNDI应用使用为原来的方法调用提供的参数在转介上下文上重新调用方法。下面的代码展示了在JNDI应用中使用ReferralException执行转介的方式。

while (true) { try { bindings = ctx.listBindings(name); while (bindings.hasMore()) { b = (Binding) bindings.next(); ... } break; } catch (ReferralException e) { ctx = e.getReferralContext(); }}

使用原始参数重新调用方法是为了让JNDI应用使用起来可以更方便。然而,这却增加了ReferralException实现的负担,因为ReferralException实现需要提供足够多的信息给转介上下文实现以使方法可以继续执行。注意,这可能会传递一些多余的参数给重新调用的操作。转介上下文实现可以选择忽略任何多余或不需要的信息。

一个操作在抛出转介异常的同时也可以返回结果。例如,当搜索一个命名上下文时,服务器可能同时返回一些结果和若干转介,提示从转介上下文可以获得更多结果。如果转介要求用户交互(例如,不是自动执行转介的情况),命名上下文实现应该首先通过搜索枚举返回结果,在结果返回之后再抛出转介异常。这为向用户清楚地展示转介及其结果集之间的关系提供了一个简单的编程模型。

3.2.5.�模式支持

在Attribute接口中定义了获取属性定义以及属性的语法定义的方法。

public class Attribute { public DirContext getAttributeDefinition() throws NamingException; public DirContext getAttributeSyntaxDefinition() throws NamingException; ...}

工具类BasicAttribute提供了Attribute接口的基本实现,但并不为这些模式相关的方法提供有意义的实现。支持这种模式信息的命名上下文实现应该基于自己

Page 47: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

35

的模式机制为这些方法提供有意义的实现,这可能通过扩展BasicAttribute类并覆盖这些方法来实现。在返回Attribute实例时,命名上下文实现应该返回这些扩展类的实例。

在DirContext接口中也定义了模式相关的方法。

public class DirContext { ... public DirContext getSchema(Name name) throws NamingException; public DirContext getSchema(String name) throws NamingException; public DirContext getSchemaClassDefinition(Name name) throws NamingException; public DirContext getSchemaClassDefinition(String name) throws NamingException;}

getSchema()方法返回命名对象的模式树,而getSchemaClassDefinition()方法返回一个包含了命名对象的模式类定义的命名上下文。一些系统可能只有一个全局模式,不论名称参数的值是什么,都返回相同的模式树。另一些系统可能支持细粒度的模式定义,根据所检查的命名上下文,可能返回不同的模式树。

3.2.6.�事件支持

通过为EventContext/EventDirContext中的方法提供实现,命名上下文实现可以支持事件通告。这些接口的事件模型可以简单地通过使用一个多线程模型来支持。当JNDI应用使用addNamingListener()方法在命名上下文中注册一个监听者时,命名上下文记录这个请求并且触发收集生成事件所需信息的动作。命名上下文在收集到生成事件所需信息之后,便将事件分发给监听者。通常注册监听者的线程和运行监听者的线程不同。通常命名上下文实现使用由其创建并管理的一个线程来执行监听者的方法。如果一个事件被分发给多个监听者,命名上下文实现可能(并且是常常被鼓励)在不同的线程中执行监听者的方法。

addNamingListener()方法接受一个NamingListener的实例,这个实例可能实现了一个或多个NamingListener的子接口。如果监听者实现了不止一个NamingListener的子接口,命名上下文实现应该尝试保存注册所需资源。例如,一个实现可能提交单个请求给服务器,要求捕获各个子接口中的所有请求。

如果可能,在命名上下文不会发出更多事件的情况下,命名上下文实现应该将一个NamingExceptionEvent传递给一个监听者,并自动删除监听者。例如,如果在注册了监听者后,到服务器的连接中断,不能取得分发事件需要的信息,命名上下文应该分发一个NamingExceptionEvent给监听者。

3.2.7.�上下文环境支持

每个Context(或其扩展接口)的实例都应该同一个上下文环境关联,上下文环境中包含了JNDI应用指定的关于如何访问命名上下文提供的服务的各种首选项。例如,安全相关的信息,给出了用户的证书和要求的安全级别;服务器使用的配置信息等。

Page 48: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

36

上下文环境中包含服务提供者特定的环境属性,这些环境属性应该有一个反映提供者唯一性的前缀。一种常用的做法是使用服务提供者的包名作为前缀。例如,由于Sun的LDAP提供者主要包含在com.sun.jndi.ldap包中,于是特定于Sun的LDAP提供者的环境属性的前缀就是“com.sun.jndi.ldap”。

在创建一个初始上下文(使用InitialContext或其扩展类的构造器)时,JNDI应用可以通过一个类型为Hashtable或其扩展类(如Properties)的参数指定一个初始的上下文环境。JNDI将从其他来源(包括资源文件、系统属性和Applet应用的参数)获得的环境属性加到这个参数中,并将这个参数作为最终的上下文环境传递给命名上下文实现。

命名上下文实现接受的环境参数为调用者所有,命名上下文实现应该复制一份环境参数或者保证调用者对参数的改变不会影响命名上下文实现见到的参数,反之亦然。另外,如果环境参数是一个Properties实例,在这个参数上的枚举操作和Hashtable.get()方法仅仅检查最高层次的属性而不检查任何嵌套情况。

随着命名上下文方法的执行过程从一个命名上下文转到另一个命名上下文,后面的命名上下文继承前面的命名上下文的整个上下文环境。这种继承特性可以通过将前面命名上下文的上下文环境作为一个参数传递给Context构造方法或者NamingManager/DirectoryManager.getObjectInstance()方法以创建Context实例来实现。对于跨越多个命名系统的联合情况,可以将前面命名上下文的上下文环境作为NamingManager.getContinuationContext()方法或DirectoryManager.getContinuationDirContext() 方法的CannotProceedException参数的一部分来传递,这两个方法会从这个参数中读取这个上下文环境来创建接续上下文实例。

命名上下文的上下文环境可以通过Context接口中的addToEnvironment()方法和removeFromEnvironment()方法来更新。对上下文环境的更新影响当前命名上下文实例,并且更新后的上下文环境被后面从当前命名上下文实例导出的命名上下文实例继承,但是任何已经存在的命名上下文实例的上下文环境不受影响。

每个服务提供者都有一个可选的资源文件,这个文件包含了特定于该提供者的属性。该资源文件的名称是“[prefix/]jndiprovider.properties”。其中,“prefix”是将服务提供者的命名上下文实现的包名中的“.”替换成“/”后的字符串。例如,假定一个服务提供者定义了一个类名为“com.sun.jndi.ldap.LdapCtx”的命名上下文实现,那么提供者资源文件被命名为“com/sun/jndi/ldap/jndiprovider.properties”。

3.3.�JNDI框架

JNDI提供了一个框架,使得JNDI应用可以使用JNDI既有的类和接口访问各种不同的命名服务,也使得各个服务提供者能够共享JNDI的服务接入机制而不必额外做复杂的接入工作。创建服务提供者的开发人员,除了需要明确上一节介绍的各项工作,还应该通过阅读本节内容清楚地理解JNDI的命名服务接入机制。

JNDI框架主要由初始上下文和三类工厂(对象工厂、状态工厂和响应控件工厂)构成。初始上下文的实现类封装了JNDI应用连接底层服务提供者的缺省策略;三类工厂被服务提供者用来定制命名上下文实现。

Page 49: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

37

3.3.1.�初始上下文

JNDI应用几乎总是通过下面的方式获取一个初始上下文实例并通过该初始上下文实例访问命名服务。

Properties env = new Properties();// add some environment attributes in envContext ctx = new InitialContext(env);

3.3.1.1.�初始上下文工厂

尽管像解析URL名称之类的工作可能由初始上下文自身完成(在“URL支持”一节介绍),但多数情况下初始上下文需要在内部使用一个由服务提供者提供的命名上下文实现处理来自JNDI应用的命名上下文方法调用请求。而在一个JNDI应用中可能存在多个服务提供者,每个服务提供者有各自的命名上下文实现。在处理一个命名上下文方法调用请求时,初始上下文需要选择一个命名上下文实现。

初始上下文在内部调用静态方法NamingManager.getInitialContext()获取用于处理命名上下文请求的命名上下文实例,而NamingManager.getInitialContext()方法则使用初始上下文工厂创建该命名上下文实例。如果没有指定初始上下文工厂生成器(在“覆盖缺省策略”一节介绍),NamingManager.getInitialContext()方法将根据“java .naming.factory.initial”环境属性确定初始上下文工厂的实现类。该环境属性指定了一个初始上下文工厂的完全限定类名。初始上下文工厂类必须实现InitialContextFactory接口,并且有一个不带任何参数的公共构造方法。NamingManager.getInitialContext()方法加载并实例化初始上下文工厂类,然后调用工厂的getInitialContext()方法获取一个命名上下文实例。

public interface InitialContextFactory { public Context getInitialContext(Hashtable env) throws NamingException;}

如果环境属性“java.naming,factory.initial”被设定成一个非null值,但在创建初始上下文工厂或命名上下文时出错,初始上下文工厂将抛出一个异常通知该问题。如果环境属性“java.naming.factroy.initial”没有设定,将不会为初始上下文创建用于在内部处理命名上下文方法调用请求的命名上下文实例,该初始上下文仅可以用来解析部分名称,如URL名称。

3.3.1.2.�URL支持

如果一个URL字符串(在RFC 1738和相关RFC中定义)被传递给初始上下文,在没有安装初始上下文工厂生成器(在“覆盖缺省策略”一节介绍)的情况下,该URL字符串将被相应的URL上下文实现解析。这个特性由InitialContext类(及其扩展类)支持,并且不受“java.naming.factory.intial”环境属性影响。这个特性使得JNDI应用可以通过初始上下文到达任何URL上下文实现能够访问的命名空间。

一个URL字符串包含一个方案标识和一个自定义字符串,并使用冒号(“:”)分隔方案标识和自定义字符串。例如,“ldap://lserver/

Page 50: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

38

ou=eng,o=wiz,c=us”是一个LDAP URL字符串,具有方案标识“ldap”;“file://ftp.linkwan.com/pub”是一个文件URL字符串,具有方案标识“file”。

URL上下文实现是实现了Context接口(或者Context扩展接口)的类,接受使用某种或某几种方案标识的URL字符串作为名称参数。例如,LDAP URL上下文接受使用“ldap”方案标识的URL字符串。

当一个URL字符串名称被传递给一个URL上下文时,接受字符串名称的命名上下文方法将这个名称视为一个由URL方案标识的语法定义的URL。当一个Name对象被传递给一个URL上下文时,如果该Name对象的第一个组件是一个URL字符串名称,那么这第一个组件就被视为一个URL字符串,而剩下的部分将被用于联合(也就是说,第一个组件的解析结果指示了使用哪个命名系统来解析剩下的组件)。Name实例必须是一个复合名称,否则抛出一个InvalidNameException异常。不是URL字符串的字符串名称参数,以及使用不合适的方案标识的URL字符串应该通过抛出InvallidNameException异常拒绝。

URL上下文工厂被JNDI用来创建能够支持一种或多种方案标识的URL的URL上下文实例(URL上下文工厂实际上是一种特殊类型的对象工厂)。初始上下文接收到一个URL字符串名称参数时,使用下面的策略查找一个URL上下文工厂。

环境属性“java.naming.factory.url.pkgs”包含一个由冒号分隔的包前缀的列表,JNDI使用其中的每个包前缀以及附加的默认包前缀“com.sun.jndi.url”分别生成一个URL上下文工厂类名:包前缀 + “.” + 方案标识 + “.” + 方案标识+ “URLContextFactory”。初始上下文尝试用每个类名加载工厂类并实例化URL上下文工厂,直到得到一个URL上下文工厂实例。例如,如果URL是“ldap://somehost:389”,而环境属性“java.naming.factory.url.pkgs”的值是“com.widget:com.wiz.jndi”,初始上下文就会试图加载下面的工厂类直到有一个工厂类成功创建URL上下文实例。

com.widget.ldap.ldapURLContextFactorycom.wiz.jndi.ldap.ldapURLContextFactorycom.sun.jndi.url.ldap.ldapURLContextFactory

URL上下文工厂类实现了ObjectFactory接口,并且有一个不带任何参数的公共构造方法。初始上下文将方案标识作为被解析的对象传递给工厂的getObjectInstance()方法,该方法接着为这个URL方案标识创建一个URL上下文实例。创建的URL上下文实例在之后被用来在提供给初始上下文的URL上执行Context或DirContext操作。

通常服务提供者没有必要提供URL上下文工厂和URL上下文的实现,只有在需要让使用自定义方案标识的URL字符串名称被初始上下文接受时才有必要这样做。

3.3.1.3.�覆盖缺省策略

根据“java.namig.factory.initial”环境属性创建初始上下文工厂和提供URL支持的策略是在InitialContext类内部实现的。JNDI应用也可以覆盖这种缺省策略。

JNDI应用如果不希望URL字符串被初始上下文特殊处理,可以使用NamingManager.getInitialContext()方法,该方法使用由“java.naming.factory.initial”环境属性指定类名的工厂创建命名上下文实

Page 51: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

39

例。JNDI应用直接使用这个命名上下文实例,便可绕过初始上下文对URL字符串的特殊处理策略。

FooContext ctx = (FooContext) NamingManager. getInitialContext(env);Object obj = ctx.lookup("ldap://lserver/ou=eng,o=wiz,c=us");ctx.fooMethod1(...);

JNDI应用也可以通过安装一个初始上下文工厂生成器自定义定位和创建初始上下文工厂的策略。在安装了初始上下文工厂生成器之后,任何由JNDI使用的默认策略(“java.naming.factory.initial”环境属性或URL支持)都不再被使用。

初始上下文工厂生成器的实现类必须实现InitialContextFactoryBuilder接口,createInitialContextFactory()方法创建InitialContextFactory实例。JNDI应用可以通过静态方法NamingManager. setInitialContextFactoryBuilder()安装初始上下文工厂生成器。

在安装了初始上下文工厂生成器之后,JNDI应用可以通过使用InitialContext/InitialDirContext/InitialLdapContext的构造方法,或使用NamingManager.getInitialContext()方法获得初始上下文。如果是使用一种构造方法,被构造的初始上下文基本上是对NamingManager.getInitialContext()方法返回的底层命名上下文实例的包装。

3.3.1.4.�扩展初始上下文

如果需要让初始上下文支持一个扩展自Context、DirContext或LdapContext的接口,服务提供者需要提供一个InitialContext(或InitialDirContext/InitialLdapContext)的扩展类。

要使扩展的初始上下文能够以与InitialContext和InitialDirContext一样的方式提供对URL的支持,扩展的初始上下文可以使用InitialContext中的那些保护访问类型的方法。

例如,假设FooContext是DirContext的一个子接口,与FooContext相应的初始上下文实现可以定义getURLDefaultInitFooCtx()方法(接受Name和String名称参数的两种重载形式),该方法获取真正被使用的命名上下文。

public class InitialFooContext extends InitialDirContext { ... protected FooContext getURLOrDefaultInitFooCtx(Name name) throws NamingException { Context answer = getURLOrDefaultInitCtx(name); if (!(answer instanceof FooContext)) { throw new NoInitialContextException( "Not a FooContext"); } return (FooContext)answer; } // similar code for getURLOrDefaultInitFooCtx(String name)}

Page 52: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

40

在为FooContext接口中接受名称参数的新方法提供实现时,按下面的方式使用getURLOrDefaultInitFooCtx()方法。

public Object fooMethod1(Name name, ...) throws NamingException { return getURLOrDefaultInitFooCtx(name).fooMethod1(name, ...);}

而在为FooContext中不接受名称参数或不需要URL支持的新方法提供实现时,可以使用InitialContext.getDefaultInitCtx()方法。

protected FooContext getDefaultInitFooCtx() throws NamingException { Context answer = getDefaultInitCtx(); if (!(answer instanceof FooContext)) { throw new NoInitialContextException( "Not an FooContext"); } return (FooContext)answer;}public Object fooMethod2(Args args) throws NamingException { return getDefaultInitFooCtx().fooMethod2(args);}

新的初始上下文实现应该提供合适的构造方法。构造方法应该调用所继承的超类中合适的构造方法。如果需要在调用超类的构造方法前修改或检查环境,应该使用接受一个布尔类型的标记作为参数的保护访问类型的构造方法,以控制初始上下文的初始化过程,然后使用init()方法执行初始化过程。下面给出了相应的示例。

public InitialFooContext(Hashtable environment, Object otherArg) throws NamingException { super(true); // don't initialize yet // Clone environment and adjust Hashtable env = (environment == null) ? new Hashtable(11) : (Hashtable)environment.clone(); ... init(env);}

JNDI应用可以按下面的方式使用这个新的初始上下文。

FooContext ctx = new InitialFooContext(env);Object obj = ctx.lookup(name);ctx.fooMethod1(name, ...);

3.3.2.�工厂机制

服务提供者可以使用工厂定制命名上下文实现。在JNDI中存在三类用于这种目的的工厂:对象工厂,定制使用绑定信息创建命名对象的策略;状态工厂,定制从命名对象生成绑定信息的策略;响应控件工厂,在遵循LDAP v3协议的命名上下文实现中用来从特定于目录服务器的响应数据中提取有意义的信息。

Page 53: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

41

3.3.2.1.�使用对象工厂

JNDI为使用存储在命名空间中的信息创建对象(包括Context实例)提供了一种一般的方法。这些信息可以是一个Reference,或者一个URL,或者任何在创建对象时需要的数据类型(java.lang.Object)。对象工厂用来将这些存储在命名空间中的信息转换成对象。对象工厂实现了ObjectFactory(或DirObjectFactory)接口,并且提供一个不带任何参数的公共构造方法。

public interface ObjectFactory { public Object getObjectInstance(Object refObj, Name name, Context nameCtx, Hashtable env) throws Exception;}public interface DirObjectFactory extends ObjectFactory { public Object getObjectInstance(Object refObj, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws Exception;}

给定一个对象的引用信息和可选的对象名称、绑定到的位置、附加的环境信息(如一些关于创建对象的用户的身份或认证信息),对象工厂将试图创建一个由这些引用信息表示的对象。如果是DirContext实现使用的对象工厂,对象工厂还被提供给对象的一些属性。如果对象工厂需要更多的属性或信息,还可以使用“name”/“nameCtx”参数直接从命名服务中获取。对象工厂如果不能使用提供的参数创建对象,只有在没有其他对象工厂可以尝试的情况下才能抛出异常,否则应该返回null。

对象工厂既可以用来生成像打印机这样的命名对象,也可以用来生成像URL上下文这样的Context实例。对象工厂的主要使用场合有:在联合中生成接续上下文实例;在初始上下文中生成URL上下文实例;在命名上下文中生成一般的命名对象。

在JNDI中通过NamingManager/DirectoryManager.getObjectInstance()方法间接使用对象工厂。为了定位对象工厂,可以通过静态方法NamingManager.setObjectFactoryBuilder()为JNDI安装一个对象工厂生成器,也可以使用JNDI的缺省策略。

对象工厂生成器实现了ObjectFactoryBuilder接口,在接口中定义的createObjectFactory()方法返回对象工厂实例。在安装了对象工厂生成器之后,JNDI为定位对象工厂使用的任何缺省策略都不再被使用。

根据NamingManager/DirectoryManager.getObjectInstance() 方法使用缺省策略定位对象工厂的不同方式,提供给对象工厂用来生成对象实例的数据可以分成三种情况:结构化引用;URL引用;其他任意类型。

Page 54: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

42

3.3.2.1.1.�处理结构化引用

在Reference类中定义了返回对象工厂的类名和位置的方法。getClassName()方法返回要创建对象的类名。getFactoryClassName()方法返回实现了ObjectFactory接口的对象工厂类的类名。getFacotryClassLocation()方法返回对象工厂类所在代码库的位置,返回结果是一个由空格分隔的URL列表。

public class Reference { ... public String getClassName(); public String getFactoryClassName(); public String getFactoryClassLocation();}

如果提供给对象工厂用来生成对象实例的数据是Reference或Referenceable实例,并且可以通过在Reference实例(若是Referenceable实例,可以通过在Referenceable实例上调用getReference()方法获得Reference实例)上调用getFactoryClassName()方法获得对象工厂的类名,这类数据属于结构化引用的情况。

对于结构化引用,NamingManager/DirectoryManager.getObjectInstance()方法根据Reference中的信息创建ObjectFactory实例,然后使用Reference和提供的环境作为参数, 在ObjectFactory实例上调用getObjectInstance()方法创建对象。

考虑打印机的例子,假设Printer表示打印机接口,BSDPrinter是该接口的一个实现类。BSDPrinter实现了Referenceable接口并且使用Reference类存储关于如何创建BSDPrinter实例的信息以及在同打印服务器通信时需要的地址信息。Reference包含了对象的类名(Printer)、对象工厂的类名(PrinterFactory)和一个指明对象工厂类的加载位置的URL。使用工厂类名和加载工厂类的位置URL,JNDI首先加载工厂类PrinterFactory并创建一个工厂实例,然后使用Reference在工厂上调用getObjectInstance()方法以创建一个Printer实例。例如,Reference中可能有一个“bsd”类型的地址,该地址还包含了打印服务器的主机名“lobby-printserver”。PrinterFactory实例根据地址类型“bsd”决定创建一个BSDPrinter实例,并将地址内容“lobby-printserver”传递给BSDPrinter的构造方法。得到的BSDPrinter对象被返回给JNDI应用。

图 3.3. 在创建对象时处理结构化引用

Page 55: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

43

为了处理结构化引用,在创建服务提供者时需要完成下面的工作:

1.为对象定义一个实现了Referenceable接口或其扩展接口的类(如BSDPrinter);

2.定义Reference和对象的引用地址;

3.定义一个实现ObjectFactory接口的工厂类(如PrinterFactory),这个类的getObjectInstance()方法使用步骤2的Reference创建步骤1的类的一个实例。

3.3.2.1.2.�处理URL引用

如果提供给对象工厂用来生成对象实例的数据是Reference或Referenceable实例,并且没有通过Reference实例提供对象工厂的类名和类加载位置,但是在Reference实例中包含地址类型为“URL”的地址,这类数据属于URL引用的情况。

对于URL引用,NamingManager/DirectoryManager.getObjectInstance()方法使用一种特殊类型的对象工厂——URL上下文工厂创建对象。URL上下文工厂的定位策略在前面“初始上下文”一节介绍。然后将地址中的URL字符串传递给工厂的getObjectInstance()方法。获得URL上下文工厂后,通过将地址中的URL字符串传递给工厂的getObjectInstance()方法获取对象实例。

URL上下文工厂在实现ObjectFactory.getObjectInstance()方法时遵循下面的规则。

1.如果表示引用信息的对象为null,则创建一个命名上下文用来解析使用与此工厂关联的方案标识的URL,得到的命名上下文并不绑定到一个特定的URL。例如,在一个“ldap”URL上下文工厂上调用getObjectInstance(null, null,null, env),将返回一个能够解析LDAP URL(如“ldap://ldap.wiz.com/o=wiz,c=us”)的命名上下文。

2.如果表示引用信息的对象是一个URL字符串,则创建由此URL标识的对象。例如,在一个“ldap”URL上下文工厂上调用getObjectInstance(“ldap://ldap.wiz.com/o=wiz,c=us”, null, null, env),将返回LDAP服务器“ldap.wiz.com”上由“o=wiz,c=us”命名的对象。如果该URL恰好命名了一个命名上下文,那么返回的命名上下文就可以用来解析LDAP名称(如“JaneSmith”)。

3.如果表示引用信息的对象是一个URL字符串的数组,则假定这些URL所引用的命名上下文是等同的,URL在数组中的顺序并不重要。由 getObjectInstance() 方法返回对象的情况类似于引用信息是单个URL字符串的情况。

4.如果表示引用信息的对象是其他任何类型,getObjectInctance()方法的行为由工厂实现自行决定。

为了处理URL引用,在创建服务提供者时需要完成下面的工作:

1.定义对象的类(如BSDPrinter);

2.为对象定义URL方案标识;

3.定义一个实现ObjectFactory接口的URL上下文工厂类,给定使用步骤2的方案标识的URL,这个类的getObjectInstance()方法能够创建步骤1的类的一个实例。

Page 56: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

44

3.3.2.1.3.�处理例外情况

如果提供给对象工厂用来生成对象实例的数据既不是结构化引用,也不是URL引用,这类数据就归为第三类——例外情况。

对于例外情况,NamingManager/DirectoryManager.getObjectInstance()方法使用环境属性“java.naming.factory.object”定位对象工厂。环境属性“java.naming.factory.object”包含一个由冒号分隔的对象工厂的完全限定类名的列表。对于列表中的每一个类名,JNDI都尝试加载工厂类并实例化工厂实例,然后使用提供的对象和环境参数在工厂实例上调用getObjectInstance()方法。如果在尝试了一个类名后成功创建了对象实例,就将对象实例返回,否则尝试列表中的下一个类名直到列表中没有更多未被尝试的类名。

在打印机的例子中,如果不是使用一个Reference在命名空间中表示一个打印机,而是存储一些其他的信息。获得这些信息后,在“java.naming.factory.object”环境属性中指定的各个对象工厂依次尝试将这些信息转化成一个Printer实例。

图 3.4. 在创建对象时处理例外情况

为了处理例外情况,在创建服务提供者时需要完成下面的工作:

1.定义对象的类(如BSDPrinter);

2.为对象的引用信息定义类,这个类不需要是Reference,而可以是相应对象工厂能够理解的任意类型(例如,可以是包含了打印服务器名称的字符串“printertype=bsd; host=lobby-printserver”);

3.定义一个实现ObjectFactory的工厂类(如PrinterFactory),给定步骤2的类实例,这个类的getObjectInstance()方法能够创建步骤1的类的一个实例。

3.3.2.2.�使用状态工厂

JNDI为将对象转换成适合命名上下文实现存储的形式提供了相应的机制。转换后的形式可以是Reference、Serializable对象、属性集或其他命名上下文实现能够接受的任意数据。状态工厂被命名上下文实现用来将任意类型的对象转变成适合在命名空间存储的形式。状态工厂实现了StateFactory接口(或DirStateFactory扩展接口),并且有一个不带任何参数的公共构造方法。

public interface StateFactory {

Page 57: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

45

public Object getStateToBind(Object obj, Name name, Context nameCtx, Hashtable env) throws NamingException;}public interface DirStateFactory { public DirStateFactory.Result getStateToBind(Object obj, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws NamingException;}

给定一个对象和可选的对象名称、绑定位置信息、附加的环境信息(如访问命名空间的用户的一些身份或认证信息),状态工厂将试图创建一个适合用来绑定的对象。通常,状态工厂掌握了目标命名服务和命名上下文实现的很多信息,知道什么样的数据格式能够被命名上下文实现接受。如果是DirContext的状态工厂,一些要随对象一起存储的属性也被提供给状态工厂。如果状态工厂需要更多关于对象的信息,还可以使用“name”/“nameCtx”参数直接从命名服务中获取。如果状态工厂不能使用提供的参数返回任何数据,只有在没有其他状态工厂可以尝试的情况下才能抛出异常,否则应该返回null。

状态工厂的输出格式最终是由底层命名服务决定的。例如,一个CORBA对象服务的命名上下文实现只能在服务中存储CORBA对象的引用信息;一个LDAP目录服务的命名上下文实现只能存储属性。

通常,一个服务提供者为每一种可能的常用输入类型提供一个状态工厂,JNDI应用也可以增加自己的状态工厂。例如,一个CORBA对象服务的服务提供者可能拥有一个将Java RMI对象转换成CORBA对象引用的的状态工厂,JNDI应用也可以添加一个将Microsoft COM对象引用转换成CORBA对象引用的状态工厂。

在JNDI中通过NamingManager/DirectoryManager.getStateToBind()方法间接使用状态工厂。与定位对象工厂相比,NamingManager/DirectoryManager定位状态工厂的机制要简单得多,getStateToBind()方法总是根据环境属性“java.naming.factory.state”定位状态工厂。“java.naming.factory.state”环境属性包含了一个冒号分隔的状态工厂的完全限定类名的列表。对于列表中的每一个类名,JNDI都尝试加载工厂类并实例化工厂实例,然后使用提供的对象、名称、命名上下文、环境和属性在工厂实例上调用getStateToBind()方法。如果在尝试了一个类名后成功创建了一个非空的结果对象,就返回这个结果对象,否则尝试列表中的下一个类名直到列表中没有更多未被尝试的类名。

3.3.2.3.�使用响应控件工厂

LDAP v3协议允许在任何由服务器发送的响应中包含响应控件。控件由一个OID字符串标识和一个使用ASN.1 BER编码格式编码的字节序列。在没有任何外部信息或协助的情况下,JNDI应用调用LdapContext.getResponseControls()方法后,命

Page 58: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

46

名上下文实现只能返回简单的Control接口的实现,这种Control实现返回控件的OID标识和从响应中提取的控件的原始字节序列,而这种原始字节序列是不易直接在JNDI应用中使用的。响应控件工厂被命名上下文实现用来将只包含控件的OID标识和原始字节序列的控件转换成包含更多可以在JNDI应用中直接使用的有意义信息的控件。

当命名上下文实现接收到一个响应控件后,调用抽象类ControlFactory中的静态方法getControlInstance()查找一个控件工厂,查找到的控件工厂将原来的响应控件转换成一种提供了更多用户友好的访问方法的控件类型。例如,转换得到的控件能够解码ASN.1 BER字节序列,并提供以Java类型返回信息的访问方法。如果找不到这样的控件工厂,原始的响应控件被返回给JNDI应用。下面是一个转换得到的响应控件示例,该控件能够解码一天中的时间。

public class TimeResponseControl implements Control { long time; // Constructor used by ControlFactory public TimeResponseControl(String OID, byte[] berVal) throws NamingException { // check validity of OID time = // extract time from berVal } // Type-safe and User-friendly method public long getTime() { return time; } // Low-level methods public String getID() { return TIME_OID; } public byte[] getEncodedValue() { return // original berVal } ...}

一个控件工厂可以负责一个或多个控件。控件工厂为了确定一个控件是不是由自己负责,通常只需要在所支持的OID列表中匹配控件的OID。控件工厂如果不能将作为参数传入的原始控件转换成新的控件,只有在没有其他控件工厂可以尝试的情况下才抛出异常,否则返回null。

控件工厂扩展抽象类ControlFactory,实现抽象方法getControlInstance(),并提供一个不带任何参数的公共构造方法。下面是一个控件工厂的例子。

public class VendorXControlFactory extends ControlFactory { public VendorXControlFactory () { } public Control getControlInstance(Control orig) throws NamingException { if (isOneOfMyControls(orig.getID())) {

Page 59: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

JNDI SPI

47

... // determine which of ours // it is and call its constructor return new TimeResponseControl(orig.getID(), orig.getEncodedValue()); } return null; // not one of ours }}

在静态方法ControlFactory.getControlFactory()的实现中,JNDI使用环境属性“java.naming.factory.control”定位响应控件工厂。“java.naming.factory.control”环境属性包含了一个冒号分隔的响应控件工厂的完全限定类名的列表。对于列表中的每一个类名,JNDI都尝试加载工厂类并实例化工厂实例,然后使用原始的响应控件作为参数在工厂实例上调用由该工厂实例对应的工厂类实现的getControlInstance()方法。如果在尝试了一个类名后成功创建了一个新的控件实例,就返回这个控件实例,否则尝试列表中的下一个类名直到列表中没有更多未被尝试的类名。

Page 60: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

48

附录�A.�标准JNDI环境属性下表列出了所有标准JNDI环境属性。在Context接口和LdapContext接口中为所

有这些环境属性的名称定义了字符串常量,表中各属性的名称之后的括号内给出了对应常量的名称。

表 A.1. 标准JNDI环境属性

环境配置

java.naming.applet (Contex.APPLET) 一个java.applet.Applet实例,该实例的applet参数可以作为部分环境属性的来源。没有缺省值。

程序配置

java.naming.factory.initial(Context.INITIAL_CONTEXT_FACTORY)

要使用的初始上下文工厂的类名。没有缺省值。

java.naming.factory.object(Context.OBJECT_FACTORIES)

冒号分隔的要使用的对象工厂类的完全限定类名的列表。缺省值为空列表。

java.naming.factory.state(Context.STATE_FACTORIES)

冒号分隔的要使用的状态工厂类的完全限定类名的列表。缺省值为空列表。

java.naming.factory.control(Context.CONTROL_FACTORIES)

冒号分隔的要使用的响应控件工厂类的完全限定类名。缺省值为空列表。

java.naming.factory.url.pkgs(Context.URL_PKG_PREFIXES)

冒号分隔的在加载URL上下文工厂类时要使用的包前缀列表。缺省值为空列表。

访问配置

java.naming.provider.url(Context.PROVIDER_URL)

指定要使用的服务提供者的配置信息。缺省值由服务提供者自己决定。

java.naming.dns.url(Context.DNS_URL)

指定DNS主机和域名。没有缺省值。

服务相关

java.naming.authoritative(Context.AUHTORITATIVE)

指定所请求的服务是否需要是权威的。如果值为“true”,则指定使用最权威的源(例如,忽略任何缓存,或忽略一些系统中的副本)。否则,使用的源不需要(但可以)是权威的。缺省值为“false”。

java.naming.batchsize(Context.BATCHSIZE)

指定希望通过服务使用的协议批量返回数据的大小。该属性指示服务提供者以指定的大小批量返回操作结果,使得服务提供者能够优化性能和对资源的使用。该属性不影响返回数据的总大小。缺省值由服务提供者决定。

java.naming.referral(Context.REFERRAL)

指定服务提供者在遇到转介时的处理方式。如果值为“follow”,则自动执行转介。如果值为“ignore”,则忽略遇

Page 61: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

标准JNDI环境属性

49

到的转介。如果值为“throw”,则抛出ReferralException异常。缺省值由服务提供者决定。

安全

java.naming.security.protocol(Context.SECURITY_PROTOCOL)

服务使用的安全协议。缺省值由服务提供者决定。

java.naming.security.authentication(Context.SECURITY_AUTHENTICATION)

取值为“none”、“simple”、“strong”或一个服务提供者特定的字符串(如,“CRAM-MD5 DIGEST-MD5EXTERNAL”)。缺省值由服务提供者决定。

java.naming.security.principal(Context.SECURITY_PRINCIPAL)

在认证方案中使用的主体标识。缺省值由服务提供者决定。

java.naming.security.credentials(Context.SECURITY_CREDENTIALS)

在认证方案中使用的主体凭据。凭据的类型由所选择的认证方案决定。凭据的不同类型有密码、密钥和证书。缺省值由服务提供者决定。

国际化

java.naming.language(Context.LANGUAGE)

冒号分隔的希望在服务中使用的语言列表(如,“en-US”,“fr”,“fr-CH”,“ja-JP-kanji”)。使用在RFC1766中定义的标记指定语言。缺省值由服务提供者决定。

Page 62: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

50

附录�B.�服务提供者示例

B.1.�命名上下文实现

/** @(#)FlatCtx.java1.4 99/10/15** Copyright 1997, 1998, 1999 Sun Microsystems, Inc. All Rights* Reserved.** Sun grants you ("Licensee") a non-exclusive, royalty free,* license to use, modify and redistribute this software in source and* binary code form, provided that i) this copyright notice and license* appear on all copies of the software; and ii) Licensee does not* utilize the software in a manner which is disparaging to Sun.** This software is provided "AS IS," without a warranty of any* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE* HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE* FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING,* MODIFYING OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN* NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT* OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS* BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.** This software is not designed or intended for use in on-line* control of aircraft, air traffic, aircraft navigation or aircraft* communications; or in the design, construction, operation or* maintenance of any nuclear facility. Licensee represents and warrants* that it will not use or redistribute the Software for such purposes.*/package examples.spi.flat;import javax.naming.*;import java.util.*;/*** A sample service provider that implements a flat namespace in memory.*/class FlatCtx implements Context { Hashtable myEnv; private Hashtable bindings = new Hashtable(11); static NameParser myParser = new FlatNameParser(); FlatCtx(Hashtable environment) {

Page 63: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

服务提供者示例

51

myEnv = (environment != null) ? (Hashtable)(environment.clone()) : null; } public Object lookup(String name) throws NamingException { if (name.equals("")) { // Asking to look up this context itself. Create and return // a new instance with its own independent environment. return (new FlatCtx(myEnv)); } Object answer = bindings.get(name); if (answer == null) { throw new NameNotFoundException(name + " not found"); } return answer; } public Object lookup(Name name) throws NamingException { // Flat namespace; no federation; just call string version return lookup(name.toString()); } public void bind(String name, Object obj) throws NamingException { if (name.equals("")) { throw new InvalidNameException("Cannot bind empty name"); } if (bindings.get(name) != null) { throw new NameAlreadyBoundException( "Use rebind to override"); } bindings.put(name, obj); } public void bind(Name name, Object obj) throws NamingException { // Flat namespace; no federation; just call string version bind(name.toString(), obj); } public void rebind(String name, Object obj) throws NamingException { if (name.equals("")) { throw new InvalidNameException("Cannot bind empty name"); } bindings.put(name, obj); } public void rebind(Name name, Object obj) throws NamingException { // Flat namespace; no federation; just call string version rebind(name.toString(), obj); } public void unbind(String name) throws NamingException { if (name.equals("")) { throw new InvalidNameException("Cannot unbind empty name"); }

Page 64: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

服务提供者示例

52

bindings.remove(name); } public void unbind(Name name) throws NamingException { // Flat namespace; no federation; just call string version unbind(name.toString()); } public void rename(String oldname, String newname) throws NamingException { if (oldname.equals("") || newname.equals("")) { throw new InvalidNameException("Cannot rename empty name"); } // Check if new name exists if (bindings.get(newname) != null) { throw new NameAlreadyBoundException(newname + " is already bound"); } // Check if old name is bound Object oldBinding = bindings.remove(oldname); if (oldBinding == null) { throw new NameNotFoundException(oldname + " not bound"); } bindings.put(newname, oldBinding); } public void rename(Name oldname, Name newname) throws NamingException { // Flat namespace; no federation; just call string version rename(oldname.toString(), newname.toString()); } public NamingEnumeration list(String name) throws NamingException { if (name.equals("")) { // listing this context return new FlatNames(bindings.keys()); } // Perhaps 'name' names a context Object target = lookup(name); if (target instanceof Context) { return ((Context)target).list(""); } throw new NotContextException(name + " cannot be listed"); } public NamingEnumeration list(Name name) throws NamingException { // Flat namespace; no federation; just call string version return list(name.toString()); } public NamingEnumeration listBindings(String name) throws NamingException {

Page 65: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

服务提供者示例

53

if (name.equals("")) { // listing this context return new FlatBindings(bindings.keys()); } // Perhaps 'name' names a context Object target = lookup(name); if (target instanceof Context) { return ((Context)target).listBindings(""); } throw new NotContextException(name + " cannot be listed"); } public NamingEnumeration listBindings(Name name) throws NamingException { // Flat namespace; no federation; just call string version return listBindings(name.toString()); } public void destroySubcontext(String name) throws NamingException { throw new OperationNotSupportedException( "FlatCtx does not support subcontexts"); } public void destroySubcontext(Name name) throws NamingException { // Flat namespace; no federation; just call string version destroySubcontext(name.toString()); } public Context createSubcontext(String name) throws NamingException { throw new OperationNotSupportedException( "FlatCtx does not support subcontexts"); } public Context createSubcontext(Name name) throws NamingException { // Flat namespace; no federation; just call string version return createSubcontext(name.toString()); } public Object lookupLink(String name) throws NamingException { // This flat context does not treat links specially return lookup(name); } public Object lookupLink(Name name) throws NamingException { // Flat namespace; no federation; just call string version return lookupLink(name.toString()); } public NameParser getNameParser(String name) throws NamingException { return myParser; } public NameParser getNameParser(Name name) throws NamingException { // Flat namespace; no federation; just call string version return getNameParser(name.toString());

Page 66: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

服务提供者示例

54

} public String composeName(String name, String prefix) throws NamingException { Name result = composeName(new CompositeName(name), new CompositeName(prefix)); return result.toString(); } public Name composeName(Name name, Name prefix) throws NamingException { Name result = (Name)(prefix.clone()); result.addAll(name); return result; } public Object addToEnvironment(String propName, Object propVal) throws NamingException { if (myEnv == null) { myEnv = new Hashtable(5, 0.75f); } return myEnv.put(propName, propVal); } public Object removeFromEnvironment(String propName) throws NamingException { if (myEnv == null) return null; return myEnv.remove(propName); } public Hashtable getEnvironment() throws NamingException { if (myEnv == null) { // Must return non-null return new Hashtable(3, 0.75f); } else { return (Hashtable)myEnv.clone(); } } public String getNameInNamespace() throws NamingException { return ""; } public void close() throws NamingException { myEnv = null; bindings = null; } // Class for enumerating name/class pairs class FlatNames implements NamingEnumeration { Enumeration names; FlatNames (Enumeration names) { this.names = names; } public boolean hasMoreElements() {

Page 67: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

服务提供者示例

55

return names.hasMoreElements(); } public boolean hasMore() throws NamingException { return hasMoreElements(); } public Object nextElement() { String name = (String)names.nextElement(); String className = bindings.get(name).getClass().getName(); return new NameClassPair(name, className); } public Object next() throws NamingException { return nextElement(); } public void close() { } } // Class for enumerating bindings class FlatBindings implements NamingEnumeration { Enumeration names; FlatBindings (Enumeration names) { this.names = names; } public boolean hasMoreElements() { return names.hasMoreElements(); } public boolean hasMore() throws NamingException { return hasMoreElements(); } public Object nextElement() { String name = (String)names.nextElement(); return new Binding(name, bindings.get(name)); } public Object next() throws NamingException { return nextElement(); } public void close() { } }}

B.2.�名称解析器

/** @(#)FlatNameParser.java1.3 99/07/26** Copyright 1997, 1998, 1999 Sun Microsystems, Inc. All Rights* Reserved.*

Page 68: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

服务提供者示例

56

* Sun grants you ("Licensee") a non-exclusive, royalty free,* license to use, modify and redistribute this software in source and* binary code form, provided that i) this copyright notice and license* appear on all copies of the software; and ii) Licensee does not* utilize the software in a manner which is disparaging to Sun.** This software is provided "AS IS," without a warranty of any* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE* HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE* FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING,* MODIFYING OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN* NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT* OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS* BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.** This software is not designed or intended for use in on-line* control of aircraft, air traffic, aircraft navigation or aircraft* communications; or in the design, construction, operation or* maintenance of any nuclear facility. Licensee represents and warrants* that it will not use or redistribute the Software for such purposes.*/package examples.spi.flat;import javax.naming.NameParser;import javax.naming.Name;import javax.naming.CompoundName;import javax.naming.NamingException;import java.util.Properties;class FlatNameParser implements NameParser { static Properties syntax = new Properties(); static { syntax.put("jndi.syntax.direction", "flat"); syntax.put("jndi.syntax.ignorecase", "false"); } public Name parse(String name) throws NamingException { return new CompoundName(name, syntax); }}

B.3.�初始上下文工厂

/** @(#)FlatInitCtxFactory.java1.3 99/07/26*

Page 69: Java企业版命名服务规范 国产中间件标准体系hgj.apusic.com/pdf/spec/3.pdf · 构成上,为方便阅读和理解,本规范在jndi 1.2规范的基础上有较大调整,删减了

服务提供者示例

57

* Copyright 1997, 1998, 1999 Sun Microsystems, Inc. All Rights* Reserved.** Sun grants you ("Licensee") a non-exclusive, royalty free,* license to use, modify and redistribute this software in source and* binary code form, provided that i) this copyright notice and license* appear on all copies of the software; and ii) Licensee does not* utilize the software in a manner which is disparaging to Sun.** This software is provided "AS IS," without a warranty of any* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE* HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE* FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING,* MODIFYING OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN* NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT* OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS* BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.** This software is not designed or intended for use in on-line* control of aircraft, air traffic, aircraft navigation or aircraft* communications; or in the design, construction, operation or* maintenance of any nuclear facility. Licensee represents and warrants* that it will not use or redistribute the Software for such purposes.*/package examples.spi.flat;import java.util.Hashtable;import javax.naming.Context;import javax.naming.spi.InitialContextFactory;public class FlatInitCtxFactory implements InitialContextFactory { public Context getInitialContext(Hashtable env) { return new FlatCtx(env); }}