现在的位置: 首页 > 综合 > 正文

基于JNDI的应用开发

2014年03月22日 ⁄ 综合 ⁄ 共 18411字 ⁄ 字号 评论关闭

 作者:kehua     翻译:高科华

 

JNDI(The Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API。命名服务将名称和对象联系起来,使得我们可以用名称访问对象。目录服务是一种命名服务,在这种服务里,对象不但有名称,还有属性。
命名或目录服务使你可以集中存储共有信息,这一点在网络应用中是重要的,因为这使得这样的应用更协调、更容易管理。例如,可以将打印机设置存储在目录服务中,以便被与打印机有关的应用使用。
本文用代码示例的方式给出了一个快速教程,使你可以开始使用JNDI。它:
l  提供了JNDI概述
l  描述了JNDI的特点
l  体验了一下用JNDI开发应用
l  表明了如何利用JNDI访问LDAP服务器,例如,Sun ONE 目录服务器
l  表明了如何利用JNDI访问J2EE服务
l  提供了示例代码,你可以将其改编为自己的应用

JNDI概述
我们大家每天都不知不觉地使用了命名服务。例如,当你在web浏览器输入URL,http://java.sun.com时,DNS(Domain Name System,域名系统)将这个符号URL名转换成通讯标识(IP地址)。命名系统中的对象可以是DNS记录中的名称、应用服务器中的EJB组件(Enterprise JavaBeans Component)、LDAP(Lightweight Directory Access Protocol)中的用户Profile。
目录服务是命名服务的自然扩展。两者之间的关键差别是目录服务中对象可以有属性(例如,用户有email地址),而命名服务中对象没有属性。因此,在目录服务中,你可以根据属性搜索对象。JNDI允许你访问文件系统中的文件,定位远程RMI注册的对象,访问象LDAP这样的目录服务,定位网络上的EJB组件。
对于象LDAP 客户端、应用launcher、类浏览器、网络管理实用程序,甚至地址薄这样的应用来说,JNDI是一个很好的选择。
JNDI架构
JNDI架构提供了一组标准的独立于命名系统的API,这些API构建在与命名系统有关的驱动之上。这一层有助于将应用与实际数据源分离,因此不管应用
访问的是LDAP、RMI、DNS、还是其他的目录服务。换句话说,JNDI独立于目录服务的具体实现,只要你有目录的服务提供接口(或驱动),你就可以使用目录。如图1所示。
 
图1:JNDI架构
关于JNDI要注意的重要一点是,它提供了应用编程接口(application programming interface,API)和服务提供者接口(service provider interface,SPI)。这一点的真正含义是,要让你的应用与命名服务或目录服务交互,必须有这个服务的JNDI服务提供者,这正是JNDI SPI发挥作用的地方。服务提供者基本上是一组类,这些类为各种具体的命名和目录服务实现了JNDI接口—很象JDBC驱动为各种具体的数据库系统实现了JDBC接口一样。作为一个应用开发者,你不必操心JNDI SPI。你只需要确认你要使用的每一个命名或目录服务都有服务提供者。
J2SE和JNDI
Java 2 SDK 1.3及以上的版本包含了JNDI。对于JDK 1.1和1.2也有一个标准的扩展。Java 2 SDK 1.4.x的最新版本包括了几个增强和下面的命名/目录服务提供者:
l  LDAP(Lightweight Directory Access Protocol)服务提供者
l  CORBA COS(Common Object Request Broker Architecture Common Object Services)命名服务提供者
l  RMI(Java Remote Method Invocation)注册服务提供者
l  DNS(Domain Name System)服务提供者
更多的服务提供者
可以在如下网址找到可以下载的服务提供者列表:http://java.sun.com/products/jndi/serviceproviders.html
特别有意思的或许是如下网址提供的Windows 注册表JNDI服务提供者:http://cogentlogic.com/cocoon/CogentLogicCorporation/JNDI.xml
这个服务提供者使你可以访问Windows XP/2000/NT/Me/9x的windows注册表。
也可以在如下网址下载JNDI/LDAP Booster Pack:http://java.sun.com/products/jndi/
这个Booster Pack包含了对流行的LDAP控制的支持和扩展。它代替了与LDAP 1.2.1服务提供者捆绑在一起的booster pack。关于控制和扩展的更多信息可以在如下网站看到:
http://java.sun.com/products/jndi/tutorial/ldap/ext/index.html
另一个有趣的服务提供者是Sun的支持DSML v2.0(Directory Service Markup Language,目录服务标记语言)的服务提供者。DSML的目的是在目录服务和XML之间架起一座桥梁。
JNDI API
JNDI API由5个包组成:
l  Javax.naming:包含了访问命名服务的类和接口。例如,它定义了Context接口,这是命名服务执行查询的入口。
l  Javax.naming.directory:对命名包的扩充,提供了访问目录服务的类和接口。例如,它为属性增加了新的类,提供了表示目录上下文的DirContext接口,定义了检查和更新目录对象的属性的方法。
l  Javax.naming.event:提供了对访问命名和目录服务时的时间通知的支持。例如,定义了NamingEvent类,这个类用来表示命名/目录服务产生的事件,定义了侦听NamingEvents的NamingListener接口。
l  Javax.naming.ldap:这个包提供了对LDAP 版本3扩充的操作和控制的支持,通用包javax.naming.directory没有包含这些操作和控制。
l  Javax.naming.spi:这个包提供了一个方法,通过javax.naming和有关包动态增加对访问命名和目录服务的支持。这个包是为有兴趣创建服务提供者的开发者提供的。
JNDI 上下文
正如在前面提到的,命名服务将名称和对象联系起来。这种联系称之为绑定(binding)。一组这样的绑定称之为上下文(context),上下文提供了解析(即返回对象的查找操作)。其他操作包括:名称的绑定和取消绑定,列出绑定的名称。注意到一个上下文对象的名称可以绑定到有同样的命名约定的另一个上下文对象。这称之为子上下文。例如,如果UNIX中目录/home是一个上下文,那么相对于这个目录的子目录就是子上下文—例如,/home/guests中guests就是home的子上下文。
在JNDI中,上下文用接口javax.naming.Context表示,这个接口是与命名服务交互的关键接口。在Context(或稍后讨论的DirContext)接口中的每一个命名方法都有两种重载形式:
l  Lookup(String name):接受串名
l  Lookup(javax.naming.Name):接受结构名,例如,CompositeName(跨越了多个命名系统的名称)或CompondName(单个命名系统中的名称);它们都实现了Name接口。Compound name的一个例子是:cn=mydir,cn=Q Mahmoud,ou=People,composite name的一个例子是:cn=mydir,cn=Q Mahmoud,ou=People/myfiles/max.txt(这里,myfiles/max.txt是表示第二部分的文件名)
Javax.naming.InitialContext是实现了Context接口的类。用这个类作为命名服务的入口。为了创建InitialContext对象,构造器以java.util.Hashtable或者是其子类(例如,Properties)的形式设置一组属性。下面给出了一个例子:

Hashtable env = new Hashtable();
// select a service provider factory
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContext");
// create the initial context
Context contxt = new InitialContext(env);

INITIAL_CONTEXT_FACTORY指定了JNDI服务提供者中工厂类(factory class)的名称。Factory负责为其服务创建适当的InitialContext对象。在上面的代码片断中,为文件系统服务提供者指定了工厂类。表1给出了所支持的服务提供者的工厂类。要注意的是文件系统服务提供者的工厂类需要从Sun公司单独下载,J2SE 1.4.x没有包含这些类。

表1:上下文INITIAL_CONTEXT_FACTORY的值
Name  Service Provider Factory
File System  com.sun.jndi.fscontext.RefFSContextFactory
LDAP  com.sun.jndi.ldap.LdapCtxFactory
RMI  com.sun.jndi.rmi.registry.RegistryContextFactory
CORBA  com.sun.jndi.cosnaming.CNCtxFactory
DNS  com.sun.jndi.dns.DnsContextFactory

为了用名称从命名服务或目录中取得或解析对象,使用Context的lookup方法:Object obj=contxt.lookup(name)。Lookup方法返回一个对象,这个对象表示的是你想要找的上下文的儿子。

命名服务的例子
现在,我们看一个使用了命名服务的例子。在这个例子中,我们编写了一个简单的程序查找对象,对象的名称作为命令行参数传入。这里,我们使用了文件系统的服务提供者,因此,我们提供作为参数的名称必须是文件名。示例代码1给出了代码。

示例代码1:Resolve.java
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;

public class Resolve {
   public static void main(String argv[]) {
      // The user should provide a file to lookup
      if (argv.length != 1) {
         System.err.println("Usage: java Resolve ");
         System.exit(-1);
      }

      String name = argv[0];

      // Here we use the file system service provider
      Hashtable env = new Hashtable();
      env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");

      try {
         // Create the initial context
         Context ctx = new InitialContext(env);

         // Look up an object
         Object obj = ctx.lookup(name);

         // Print it out
         System.out.println(name + " is bound to: " + obj);

         // Close the context
         ctx.close();
      } catch (NamingException e) {
         System.err.println("Problem looking up " + name + ": " + e);
      }
   }
}

这里,我们假定你使用的是Java 2 SDK 1.4.x,这个版本附带了几个服务提供者(上面已列出)。这个应用使用了文件系统服务提供者,文件系统服务提供者缺省时没有安装。因此,你需要下载并安装它。另一方面,如果你运行这个程序并且没有安装这个服务提供者,你就会得到NoInitialContextException,这表明不能找到服务提供者工厂类,因此不能对它实例化。你需要将fscontext.jar和providerutil.jar放在类路径classpath上—或者你也可以象我所做的这样,把这两个文件拷贝到JAVA_HOME/jre/lib/ext,这里JAVA_HOME是你安装的Java 2 SDK的根目录。
用这个应用试验的步骤:
1.  确认下载并安装了文件系统服务提供者(正如上面提到的),因为J2SE 1.4.x没有包含这个服务提供者。
2.  把代码拷贝并复制到文件Resolve.java。
3.  用javac编译文件Resolve.java。
4.  用解释器java运行这个应用。
下面是一个运行实例:
prompt> java Resolve /classes
/classes is bound to: com.sun.jndi.fscontext.FSContext@f62373
如果你提供的是文件名,你看到的就会是这样:
prompt> java Resolve /classes/Resolve.java
/classes/Resolve.java is bound to: C:/classes/Resolve.java
列出目录的内容
现在,我们来看一看如何使用其他的JNDI API列出一个文件目录的内容。我们假定你想让用户用file:///这样的URL指定命令行参数。在这种情况下,你要设置一个新属性PROVIDER_URL,如示例代码2所示。Context的listBindings方法返回NamingEnumeration,这可以用while循环迭代,如示例代码2所示。

示例代码2:Resolve2.java
import javax.naming.Binding;
import javax.naming.NamingEnumeration;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;

public class Resolve2 {
   public static void main(String argv[]) {
      // The user should provide a file to lookup
      if (argv.length != 1) {
         System.err.println("Usage: java Resolve2 ");
         System.exit(-1);
      }

      // Here we use the file system service provider
      Hashtable env = new Hashtable();
      env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.FSContextFactory");
      env.put(Context.PROVIDER_URL, argv[0]);

      try {
         // Create the initial context
         Context ctx = new InitialContext(env);
         
         NamingEnumeration ne = ctx.listBindings("");
         while(ne.hasMore()) {
           Binding b = (Binding) ne.next();
           System.out.println(b.getName() + " " + b.getObject());
         }
         
         // close the context
         ctx.close();
      } catch (NamingException e) {
         System.err.println("Problem looking up " + argv[0] + ": " + e);
      }
   }
}
用这个应用试验的步骤与上一个例子相同。下面是一个运行实例:
prompt>: java Resolve2 file:///uddi
fig1.gif C:/uddi/fig1.gif
fig2.gif C:/uddi/fig2.gif
fig3.gif C:/uddi/fig3.gif
fig4.gif C:/uddi/fig4.gif
fig5.gif C:/uddi/fig5.gif
impl.txt C:/uddi/impl.txt
目录服务
如前所述,目录服务只不过是对象既有名称又有属性的命名服务。具有属性和名称的对象称为目录条目。应用可以使用目录服务存储和提取目录对象的属性。它甚至可以用作对象仓库。
LDAP
LDAP(Lightweight Directory Access Protocol)—其来源可以追溯到密歇根大学开发的X.500协议—是一个访问和管理目录服务的协议。它定义了客户端应该如何访问存储在服务器上的数据,但是没有规定如何存储数据。一个LDAP目录由具有描述性信息的条目组成,这些描述性信息描述了人(例如,人名、电话号码、email地址等)或网络资源(例如,打印机、传真机等)。用条目的属性存储这样的描述性信息,每个属性描述了一种具体的信息。下面是一个描述人的属性的例子:
cn: Qusay H. Mahmoud
mail: qmahmoud@javacourses.com
telephoneNumber: 123-4567
LDAP目录服务可以用于根据属性查找某人的电话号码或email地址。表2给出了一些共用LDAP属性:
表2:一些共用LDAP属性
Attribute  Meaning
o  Organization
cn  Common Name
sn  Surname
uid  Userid
mail  Email address
c  Country

LDAP名称是一系列的(name,value)对,例如,名称、组织、国家:
cn=Qusay Mahmoud, o=javacourses.com, c=Canada
javax.naming.directory.DirContext扩充了javax.naming.Context,是JNDI目录服务接口。它提供的方法如下:
l  search:在目录中搜索相配的目录条目,把目录条目与一组属性相比较。
l  bind和createSubcontext:增加新的目录条目。
l  modifyAttributes:修改目录条目的指定属性。可以用Rename方法修改条目名称本身。
l  unbind和destroySubcontext:删除指定的目录条目。
l  close:结束与LDAP服务器会话
用JNDI对LDAP编程
为了操作LDAP服务器上的对象(例如,Sun ONE目录服务器),必须首先连接到服务器。连接服务器时或许还需要通过身份鉴别。为了连接到服务器,你从DirContext接口内获得了一个对象引用。用InitialDirContext类做到这一点,传递给这个类的是一个哈希表(HashTable)。
下面的代码片断用来鉴别用户,将用户连接到LDAP服务器。要注意的是这里用的是简单鉴别。简单鉴别通过向LDAP服务器发送用户的完全DN和用户明文口令实现。为了避免暴露明文口令,如果你的LDAP服务器支持的话,就用加密隧道(SSL)使用这个机制。关于鉴别模式的更多信息,请看如下网址上的JNDI教程:http://java.sun.com/products/jndi/tutorial/index.html。

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// specify where the ldap server is running
env.put(Context.PROVIDER_URL, "ldap://GH308C-N-MAHMOUD.humber.org:61596");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
env.put(Context.SECURITY_CREDENTIALS, "password");
// Create the initial directory context
DirContext ctx = new InitialDirContext(env);

一旦连接到了LDAP服务器,你就可以向LDAP服务器增加新的条目,修改条目,删除条目或者搜索条目。下面的代码片断说明了如何增加或存储一个新条目。注意:为了存储一个对象,必须用Java Schema装载它,目录服务器没有预置Java Schema。关于这一点,请看JNDI教程中的Java Objects And the Directory一节。
SomeObject Obj = new SomeObjct("param1", "param2", "param3");
ctx.bind("cn=myobject", obj);
可以用lookup方法查找对象,如下所示:
SomeObject obj = (SomeObject) ctx.lookup("cn=myobject");
示例代码3给出了一个如何提取名称对象的属性的例子。正如你所看到的,选择工厂类的代码与前面的一样。我们用InitialDirContext类创建目录上下文,DirContext,用getAttributes方法返回对象的属性,最后,用get方法找到姓氏并打印出来。简单吧?

示例代码 3: GetAttrib.java
import javax.naming.Context;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.DirContext;
import javax.naming.directory.Attributes;
import javax.naming.NamingException;
import java.util.Hashtable;

class GetAttrib {
   public static void main(String[] argv) {
      Hashtable env = new Hashtable();
      env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
      // specify where the ldap server is running
      env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=javacourses.com,c=Canada");
      // use simple authenticate to authenticate the user
      env.put(Context.SECURITY_AUTHENTICATION, "simple");
      env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
      env.put(Context.SECURITY_CREDENTIALS, "password");

      try {
         // Create the initial directory context
         DirContext ctx = new InitialDirContext(env);

         // Ask for all attributes of the object
         Attributes attrs = ctx.getAttributes("cn=Qusay Mahmoud");

         // Find the surname ("sn") attribute of this object and print it
         System.out.println("Last Name: " + attrs.get("sn").get());

         // Close the context
         ctx.close();
      } catch (NamingException e) {
         System.err.println("Problem getting attribute: " + e);
      }
   }
}

JNDI为基本的和高级的(用过滤器filter)搜索提供了API。例如,可以用条目必须具有的一组属性和在其中进行搜索的目标上下文的名称执行简单搜索。下面的代码片断说明了如何对一个子树搜索具有uid=qmahmoud属性的条目。使用过滤器的高级搜索超出了本文的范围。

// ignore attribute name case
Attributes matchattribs = new BasicAttributes(true);
matchattribs.put(new BasicAttribute("uid", "qmahmoud"));
// search for objects with those matching attributes
NamingEnumeration answer = ctx.search("ou=People,o=javacourses.com", matchattribs);
while (answer.hasMore()) {
  SearchResult sr = (SearchResult)answer.next();
  // print the results you need
}

关于使用JNDI编写LDAP客户端的更多信息,请看如下网址:http://java.sun.com/products/jndi/tutorial/ldap/index.html
JNDI的CORBA COS命名服务提供者
CORBA COS名称服务器用于存储COBRA 对象引用。通过使用COS 命名包(org.omg.CORBA.CosNaming)从CORBA应用中访问这个服务器。
JNDI COS命名服务提供者在COS命名包的基础上实现了javax.naming.Context接口,使得CORBA应用能够利用JNDI访问COS名称服务器。因此,使用了JNDI的CORBA应用有一个接口访问所有的命名和目录服务。这使得CORBA应用可以利用象LDAP这样的分布式企业级服务存储对象引用。
要选择COS命名服务提供者,使用:
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory");
要转换CORBA应用,使其使用JNDI,考虑AddServer.java和AddClient.java,这两个类在另一篇文章(http://java.sun.com/developer/technicalArticles/releases/corba/)中有更详细的讨论。
1.  使用javax.naming:在客户端和服务器上,用如下的代码:
import javax.naming.*;
代替下面的代码:
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
2.  在客户端和服务器上,用InitialContext代替NameService,用如下的代码:
Hashtable env = new Hashtable();
env.put("java.naming.corba.orb", orb);
Context ctx = new InitialContext(env);
代替下面的代码:
org.omg.CORBA.Object objRef =
        orb.resolve_initial_references("NameService");
NamingContextExt ncRef =
        NamingContextExtHelper.narrow(objRef);
3.  用lookup代替resolve,用如下的代码:
Add href = AddHelper.narrow((org.omg.CORBA.Object)ctx.lookup("Add"));
代替下面的代码:
String name = "Add";
Add href = AddHelper.narrow(ncRef.resolve_str(name));
JNDI的RMI注册服务提供者
RMI注册服务提供者允许JNDI应用访问用RMI注册的远程对象。一旦知道了注册的位置,服务提供者就通过绑定为被注册的对象创建命名上下文。这个上下文或许又被绑定到另一个JNDI可以访问的名称空间(例如LDAP)。这个新功能包含了java.rmi.Naming类提供的功能。
像这样使用RMI的最大好处是客户端不再需要知道运行RMI注册的主机和端口,它是独立于位置的。
下面的代码片断说明了如何通过RMI使用JNDI:

// select the registry service provider as the initial context
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");

// specify where the registry is running
env.put(Context.PROVIDER_URL, "rmi://server:1099");

// create an initial context that accesses the registry
Context ctx = new InitialContext(env);

// now, the names stored in registry can be listed
NamingEnumeration enum = ctx.list("");

// bind the registry context into LDAP directory
Context ldapctx = (Context)ctx.lookup("ldap://server:port/o=comp,c=ca");
ldapctx.bind("cn=rmi", ctx);
JNDI的DNS 服务提供者
DNS服务提供者使得基于JNDI的应用可以访问存储在DNS中的信息。DNS服务提供者将DNS名称空间表示为JNDI目录上下文树,将DNS资源记录表示为JNDI属性。
示例代码4说明了如何使用DNS服务提供者提取环境和IP地址信息(A记录)。

示例代码4:TestDNS.java
import javax.naming.*;
import com.sun.jndi.dns.*;
import java.util.Hashtable;

public class TestDNS {
   public static void main(String[] argv) {
      Name cn = null;
      String name = argv[0];
      Hashtable env = new Hashtable();
      env.put(Context.INITIAL_CONTEXT_FACTORY,
       "com.sun.jndi.dns.DnsContextFactory");
      env.put(Context.PROVIDER_URL, "dns://IP for DNS Server/");

      try {
         // Create the initial context
         Context ctx = new InitialContext(env);
         // print the fully qualified name (.)
         System.out.println("Name in namespace: "+ctx.getNameInNamespace());
         // retrieve the parser associated with the named context
         NameParser np = ctx.getNameParser(ctx.getNameInNamespace());
  if (argv.length != 1) {
            System.out.println("Usage: java TestDNS ");
            System.exit(-1);
         }
         // parse the name into its components and print them
         cn = np.parse(name);
         System.out.println("Name is: "+cn.toString());
         System.out.println("The parsed name has "+cn.size()+" components:");
         for (int i=0; i<cn.size(); i++){
            System.out.println(cn.get(i));
         }
         System.out.print("Trying to lookup ");
         // get the prefix (domain) and suffix (hostname)
         Name domain = cn.getPrefix(cn.size()-1);
         Name host = cn.getSuffix(cn.size()-1);
         System.out.println("DNS Host: "+host+" Domain: "+domain);
         // retrieve the named object
         Object obj = ctx.lookup(domain);
         System.out.println(domain.toString()+" is bound to: "+obj);
         // retrieve and print the environment in effect
         System.out.println("Domain properties: "+ ((Context)obj).getEnvironment());
         // retrieve and print the IP address (the DNS A records)
         System.out.println("IP for: "+cn+ " is: "+
                            ((DnsContext)obj).getAttributes(host, new String[]{"A"}));

         // we're done so close the context
         ctx.close();
      } catch (NamingException e) {
         System.err.println("Problem looking up " + cn + ": " + e);
      }
   }
}
运行这个应用前,确认为DNS服务器指定了IP地址。
下面是一个运行示例:
prompt> java TestDNS prep.ai.mit.edu

Name in namespace: .
Name is: prep.ai.mit.edu
The parsed name has 4 components:
edu
mit
ai
prep
Trying to lookup DNS Host: prep Domain: ai.mit.edu
ai.mit.edu is bound to: com.sun.jndi.dns.DnsContext@b89838
Domain properties: {java.naming.provider.url=dns://IP for DNS Server/, java.namin
g.factory.initial=com.sun.jndi.dns.DnsContextFactory}
IP for: prep.ai.mit.edu is: {a=A: 199.232.41.9}
JNDI 和 J2EE
JNDI是J2EE平台上的标准服务API之一。它向应用组件提供了引用资源和其他应用组件的标准API。J2EE还定义了标准的命名策略(逻辑名称与实名),使用这个命名策略和JNDI使得我们可以用独立于部署环境的方式编写应用。你可以通过根据逻辑名称在目录中查找它们来引用J2EE服务。为此,每个J2EE兼容的系统都提供了一个称之为环境(environment)的JNDI服务,环境包括如下内容:
l  环境变量
l  EJB引用
l  资源工厂引用
注意:本文中我们只讨论环境变量。关于EJB引用和资源工厂引用的信息,请看http://java.sun.com/developer/technicalArticles/xml/WebAppDev4/
环境变量
应用组件的命名环境允许你定制应用组件,不必访问或改变应用组件的源代码。每一个应用组件都定义了自己的一组环境变量。同一个容器内的一个应用组件的所有实例共享同样的环境变量。要注意的是不允许应用组件实例在运行时修改环境变量。
声明环境变量
应用组件提供者必须声明所有的从应用组件访问的环境变量。这些环境变量用部署描述符文件(例如,Tomcat中的web.xml文件)中的<env-entry>标记来声明。标记<env-entry>的元素有:
l  <description>:环境变量的可选描述。
l  <env-entry-name>:环境变量名称。
l  <env-entry-type>:期望的环境变量类型。可以是下面的Java类型之一: Boolean, Byte, Double, Character, Float, Integer, Long, Short, String。
l  <env-entry-value>:环境变量的值,必需与<env-entry-type>中提供的类型相配。以后还可以改变这个值,但是如果没有设置这个值,就必须在部署时指定一个值。
示例代码5中的例子给出了对两个环境变量的声明。为了声明一个新的环境变量,只需要将它增加到web应用描述符文件中(web.xml)。

示例代码5: 声明环境变量
<env-entry>
<description>welcome message</description>
<env-entry-name>greetings</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>Welcome to the Inventory Control
                           System</env-entry-value>
</env-entry>

<env-entry>
<description>maximum number of products</descriptor>
<env-entry-name>inventory/max</env-entry-name>
<env-entry-type>java.lang.Integer</env-entry-type>
<env-entry-value>27</env-entry-value>
</env-entry>
一个 <env-entry> 标记描述了一个环境变量。因此,在这个例子中定义了两个环境变量。第一个环境变量是greetings,它的类型是String,缺省初值是:Welcome to the Inventory Control System。第二个环境变量是inventory/max,它的类型是Integer,缺省初值是27。
现在,应用组件实例就可以用JNDI定位环境变量。在无参构造器内创建一个javax.naming.InitialContext对象。然后通过InitialContext,用以java:comp/env开始的JNDI URL查找命名环境。示例代码6说明了应用组件如何访问环境变量。

示例代码 6: 访问环境变量
// obtain the application component's environment
// naming context
javax.naming.Context ctx =
new javax.naming.InitialContext();
javax.naming.Context env =
ctx.lookup("java:comp/env");

// obtain the greetings message
//configured by the deployer
String str = (String) env.lookup("greetings");

// use the greetings message
System.out.println(greetings);

// obtain the maximum number of products
//configured by the deployer
Integer maximum = (Integer) env.lookup(
"inventory/max");
//use the entry to customize business logic

要注意的是应用组件也可以用路径全名查找环境变量,如下所示:
javax.naming.Context ctx =
new javax.naming.InitialContext();
String str = (String) ctx.lookup(
"java:comp/env/greetings");
这个代码片断可以用在JSP 文件中,如示例代码7所示:
示例代码7:从JSP文件中访问环境变量
<HTML>
<HEAD>
<TITLE>JSP Example</TITLE>
</HEAD>
<BODY BGCOLOR="#ffffcc">
<CENTER>
<H2>Inventory System</H2>
<%
javax.naming.Context ctx =
new javax.naming.InitialContext();
javax.naming.Context myenv =
   (javax.naming.Context) t.lookup("java:comp/env");
java.lang.String s = (java.lang.String)
    myenv.lookup("greetings");
out.println("The value is: "+greetings);
%>
</CENTER>

</BODY>
</HTML>

结论
JNDI是一组API,它用命名/目录服务增强了网络应用。本文中的示例说明了用JNDI开发基于目录的应用是多么容易。它也说明了如何用同样的API访问不同的命名/目录服务。开发者不必学习不同的API。在某些情况下,例如,在RMI和CORBA应用中,JNDI允许你部署时才选择命名服务。
JNDI未来将增加的功能包括:与标准的Java SASL API(JSR-28,http://jcp.org/aboutJava/communityprocess/review/jsr028/) 的集成、支持国际化域名、支持安全DNS。
为了开始使用JNDI和LDAP,下载Sun ONE目录服务器试用版(http://wwws.sun.com/software/products/directory_srvr/home_directory.html),这个服务器支持多种语言、多种平台。

更多的信息
LDAP v3
JNDI
The JNDI Tutorial
JNDI Service Providers
Sun ONE Directory Server
JNDI-INTEREST Mailing List (for discussing JNDI)  

 

转载地址:http://www.delphibbs.com/keylife/iblog_show.asp?xid=10279

 

另外,在IBM的developerworks上有一篇很好的JNDI的入门文章,链接地址是:http://www.ibm.com/developerworks/cn/java/j-jndi/

抱歉!评论已关闭.