Java 中的 jndi

来源:互联网 发布:流星网络电视apk安卓 编辑:程序博客网 时间:2024/05/29 03:21

JNDI是 Java 命名与目录接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之一,不少专家认为,没有透彻理解JNDI的意义和作用,就没有真正掌握J2EE特别是EJB的知识。
那么,JNDI到底起什么作用?

要了解JNDI的作用,我们可以从“如果不用JNDI我们怎样做?用了JNDI后我们又将怎样做?”这个问题来探讨。

没有JNDI的做法:
程序员开发时,知道要开发访问MySQL数据库的应用,于是将一个对 MySQL JDBC 驱动程序类的引用进行了编码,并通过使用适当的 JDBC URL 连接到数据库。
就像以下代码这样:
Connection conn=null;try {  Class.forName("com.mysql.jdbc.Driver",                true, Thread.currentThread().getContextClassLoader());  conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue");  /* 使用conn并进行SQL操作 */  ......  conn.close();} catch(Exception e) {  e.printStackTrace();} finally {  if(conn!=null) {    try {      conn.close();    } catch(SQLException e) {}  }}

这是传统的做法,也是以前非Java程序员(如Delphi、VB等)常见的做法。这种做法一般在小规模的开发过程中不会产生问题,只要程序员熟悉Java语言、了解JDBC技术和MySQL,可以很快开发出相应的应用程序。

没有JNDI的做法存在的问题:
1、数据库服务器名称MyDBServer 、用户名和口令都可能需要改变,由此引发JDBC URL需要修改;
2、数据库可能改用别的产品,如改用DB2或者Oracle,引发JDBC驱动程序包和类名需要修改;
3、随着实际使用终端的增加,原配置的连接池参数可能需要调整;
4、......

解决办法:
程序员应该不需要关心“具体的数据库后台是什么?JDBC驱动程序是什么?JDBC URL格式是什么?访问数据库的用户名和口令是什么?”等等这些问题,程序员编写的程序应该没有对 JDBC 驱动程序的引用,没有服务器名称,没有用户名称或口令 —— 甚至没有数据库池或连接管理。而是把这些问题交给J2EE容器来配置和管理,程序员只需要对这些配置和管理进行引用即可。

由此,就有了JNDI。

用了JNDI之后的做法:
首先,在在J2EE容器中配置JNDI参数,定义一个数据源,也就是JDBC引用参数,给这个数据源设置一个名称;然后,在程序中,通过数据源名称引用数据源从而访问后台数据库。
具体操作如下(以JBoss为例):
1、配置数据源
在JBoss的 D:/jboss420GA/docs/examples/jca 文件夹下面,有很多不同数据库引用的数据源定义模板。将其中的 mysql-ds.xml 文件Copy到你使用的服务器下,如 D:/jboss420GA/server/default/deploy。
修改 mysql-ds.xml 文件的内容,使之能通过JDBC正确访问你的MySQL数据库,如下:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
    <jndi-name>
MySqlDS</jndi-name>
    <connection-url>
jdbc:mysql://localhost:3306/lw</connection-url>
    <driver-class>
com.mysql.jdbc.Driver</driver-class>
    <user-name>
root</user-name>
    <password>
rootpassword</password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
    <metadata>
       <type-mapping>
mySQL</type-mapping>
    </metadata>
</local-tx-datasource>
</datasources>


这里,定义了一个名为MySqlDS的数据源,其参数包括JDBC的URL,驱动类名,用户名及密码等。

2、在程序中引用数据源:
Connection conn=null;try {  Context ctx=new InitialContext();  Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用数据源  DataSource ds=(Datasource)datasourceRef;  conn=ds.getConnection();  /* 使用conn进行数据库SQL操作 */  ......  c.close();} catch(Exception e) {  e.printStackTrace();} finally {  if(conn!=null) {    try {      conn.close();    } catch(SQLException e) { }  }}
直接使用JDBC或者通过JNDI引用数据源的编程代码量相差无几,但是现在的程序可以不用关心具体JDBC参数了。
在系统部署后,如果数据库的相关参数变更,只需要重新配置 mysql-ds.xml 修改其中的JDBC参数,只要保证数据源的名称不变,那么程序源代码就无需修改。

由此可见,JNDI避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。

JNDI的扩展:
JNDI在满足了数据源配置的要求的基础上,还进一步扩充了作用:所有与系统外部的资源的引用,都可以通过JNDI定义和引用。

所以,在J2EE规范中,J2EE 中的资源并不局限于 JDBC 数据源。引用的类型有很多,其中包括资源引用(已经讨论过)、环境实体和 EJB 引用。特别是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一项关键角色:查找其他应用程序组件。

EJB 的 JNDI 引用非常类似于 JDBC 资源的引用。在服务趋于转换的环境中,这是一种很有效的方法。可以对应用程序架构中所得到的所有组件进行这类配置管理,从 EJB 组件到 JMS 队列和主题,再到简单配置字符串或其他对象,这可以降低随时间的推移服务变更所产生的维护成本,同时还可以简化部署,减少集成工作。 外部资源”。 


总结:
J2EE 规范要求所有 J2EE 容器都要提供 JNDI 规范的实现。JNDI 在 J2EE 中的角色就是“交换机” —— J2EE 组件在运行时间接地查找其他组件、资源或服务的通用机制。在多数情况下,提供 JNDI 供应者的容器可以充当有限的数据存储,这样管理员就可以设置应用程序的执行属性,并让其他应用程序引用这些属性(Java 管理扩展(Java Management Extensions,JMX)也可以用作这个目的)。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。

在 J2EE 中,JNDI 是把 J2EE 应用程序合在一起的粘合剂,JNDI 提供的间接寻址允许跨企业交付可伸缩的、功能强大且很灵活的应用程序。这是 J2EE 的承诺,而且经过一些计划和预先考虑,这个承诺是完全可以实现



关键字:JNDI,J2EE,Java,命名和目录接口,Java Naming and Directory Interface

摘要:

本文详细介绍了JNDI的架构与实现,JNDI的工作原理,并给出了具体代码,帮助读者更理解J2EE主要常用技术---JNDI.本文为系列文章的第一篇,其它相关文章会在近期推出。
 
名词解释
    jndi是Java 命名和目录接口(Java Naming and Directory Interface,JNDI)的简称.从一开始就一直是 Java 2 平台企业版(JEE)的核心技术之一。在JMS,JMail,JDBC,EJB等技术中,就大量应用的这种技术。
    
为什么会有jndi
    jndi诞生的理由似乎很简单。随着分布式应用的发展,远程访问对象访问成为常用的方法。虽然说通过 Socket等编程手段仍然可实现远程通信,但按照模式的理论来说,仍是有其局限性的。RMI技术,RMI-IIOP技术的产生,使远程对象的查找成为了技术焦点。JNDI技术就应运而生。JNDI技术产生后,就可方便的查找远程或是本地对象。
 
JNDI的架构与实现
    
JNDI的架构与JDBC的架构非常类似.JNDI架构提供了一组标准命名系统的API,这些API在JDK1.3之前是作为一个单独的扩展包jndi.jar(
通过这个地址下载),这个基础API构建在与SPI之上。这个API提供如下五个包
  • javax.naming
  • javax.naming.directory
  • javax.naming.event
  • javax.naming.ldap
  • javax.naming.spi
在应用程序中,我们实际上只使到用以上几个包的中类.具体调用类及通信过程对用户来说是透明的.
JNDI API提供了访问不同JNDI服务的一个标准的统一的实现,其具体实现可由不同的 Service Provider来完成。前面讲的为第一层JNDI API层.
 
最下层为JNDI SPI API及其具体实现。
 

图中所列的一些SPI可从http://java.sun.com/products/jndi/downloads/index.html下载.

 


包括了几个增强和下面的命名/目录服务提供者:
  • LDAP(Lightweight Directory Access Protocol)服务提供者
  • CORBA COS(Common Object Request Broker Architecture Common Object Services)命名服务提供者 
  • RMI(Java Remote Method Invocation)注册服务提供者
  • DNS(Domain Name System)服务提供者.
  • FSSP(File System Service Provider)文件系统服务提供者
  • 其它服务提供者
 
中间层为命名管理层。其功能应该由JNDI SPI来完成。上层为JNDI API,这个API包在Java 2 SDK 1.3及以上的版本中已经包括。
 
前面讲解的只是作为应用程序客户端的架构实现,其服务端是由SPI对应的公司/厂商来实现的,我们只需将服务端的相关参数传给JNDI API就可以了,具体调用过程由SPI来完成.
 
 
JNDI工作原理
 

下面通过一个示例程序来说明JNDI工作原理(代码为自解释).

/*    * Created on 2005-3-4    *    * To change the template for this generated file go to    * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments    */    package com.sily.jndi;    import java.io.FileInputStream;    import java.util.Properties;    import javax.naming.Context;    import javax.naming.InitialContext;    /**    * @author shizy    *    * To change the template for this generated type comment go to    * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments    */    public class TestJbossJNDI {    /**    *    */    public TestJbossJNDI() {    super();    // TODO Auto-generated constructor stub    }    public static void main(String[] args) { try {    Properties env = new Properties();    //载入jboss的SPI相关参数,包括初始上下文工厂,服务URL,等等    env.load(new FileInputStream("jbossJndi.properties"));    env.list(System.out);    //通过JNDI api 初始化上下文    InitialContext ctx = new javax.naming.InitialContext(env);    System.out.println("Got context");    //create a subContext    ctx.createSubcontext("/sylilzy");    ctx.createSubcontext("sylilzy/sily");    //rebind a object    ctx.rebind("sylilzy/sily/a", "I am sily a!");    ctx.rebind("sylilzy/sily/b", "I am sily b!");    //lookup context    Context ctx1=(Context)ctx.lookup("sylilzy");    Context ctx2=(Context)ctx1.lookup("/sylilzy/sily");    ctx2.bind("/sylilzy/g", "this is g");    //lookup binded object    Object o;    o=ctx1.lookup("sily/a");    System.out.println("get object from jndi:"+o);    //rename the object    ctx2.rename("/sylilzy/g", "g1");    o=ctx2.lookup("g1");    System.out.println("get object from jndi:"+o);    } catch (Exception e) {    e.printStackTrace();    }    }    }结果输出如下:
-- listing properties --
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=jnp://localhost:1099
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
Got context
get object from jndi:I am sily a!
get object from jndi:this is g
 
程序中jbossJndi.properties文件的内容为:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=jnp://localhost:1099
 
注意:要正确运行示例程序,请启动jboss,并将jboss的jbossall-client.jar文件放入classpath中。
 
上述示例程序在jboss服务器的jndi树上建立了几个上下文,并bind了几对象,大家可通过附录中的代码或其它工具查看
查看结果为:
-----------------------------
/sylilzy/sily
-----------------------------
/sylilzy/sily/b:I am sily b!
/sylilzy/sily/a:I am sily a!
/sylilzy/sily/g1:this is g
-----------------------------
-----------------------------
 
上述程序中,我们的代码只涉及到了jndi API,其它细节如初始化jboss jndi的初始上下文,建立网络连接,与服务器通信,对我们来说都是透明的,另外,我们将jboss jndi的spi包中的类名作为参数传入了程序中,要访问一个远程对象,我们所做的就这么多。
 
下面,再提供一个例子,与上例不同,我们不需要 jboss,我们使用sun的FSSP(File System Service Provider)文件系统服务提供者.注意在这个例子中要使用到前面所说的File System Service Provider for the Java Naming and Directory InterfaceTM (JNDI)相关类(下载)。
/*    * Created on 2005-3-1    *    * To change the template for this generated file go to    * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments    */    package com.sily.jndi;    import java.io.FileInputStream;    import java.util.Properties;    import javax.naming.*;    import javax.naming.Context;    import javax.naming.InitialContext;    /**    * @author shizy    *    * To change the template for this generated type comment go to    * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments    */    public class JndiTest1 {    /**    *    */    public JndiTest1() {      super();      // TODO Auto-generated constructor stub     }     public static void main(String[] args) {      try {       Properties env = new Properties();       env.load(new FileInputStream("fileSystemService.properties"));       env.put(Context.PROVIDER_URL, "file:///c:/");       Context ctx = new InitialContext(env);       ctx.createSubcontext("sylilzy");        NamingEnumeration list = ctx.list("/");       while (list.hasMore()) {        NameClassPair nc = (NameClassPair) list.next();        System.out.println(nc);       }        }      catch (Exception e) {       e.printStackTrace();      }     }    }    
 
上例中fileSystemService.properties文件的内容为:java.naming.factory.initial=com.sun.jndi.fscontext.RefFSContextFactory
 
这个例子较简单,运行后,它会列出C:\下所有的文件和目录,另外你会发现有一个新目录被创建了.本例不同于上例,它并不需要服务端,因为它访问的是文件系统.有关帮助可查阅包内的相关文档。
 
通过对比这两个例子,应该JNDI的工作原理有了一个大致的了解。
总结:
jndi技术体现了分布式应用的优点,同进它的 产生也为分布式对象提供了统一的访问接口。由于篇幅所限,对目录的操作本文未作介绍,其它内容将在接下来的系列中讨论。要对JNDI技术作全面的了解,请 参阅参考资料.要对于JNDI技术深入学习,仍有许多地方值得进一步了解,例如EJB容器所使用的JNDI所提供的对象就有 Local和Remote之分,对于Local Object,对于不同的JVM是不可访问的;对于远程对象的访问,还涉及到Java安全机制。
 
附录:
查看jboss jndi内容的代码:
//----------------------------------------
/*    * Created on 2005-3-4    *    * To change the template for this generated file go to    * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments    */    package com.sily.jndi;    import java.io.FileInputStream;    import java.util.Properties;    import javax.naming.*;    import javax.naming.Context;    import javax.naming.InitialContext;    /**    * @author shizy    *    * To change the template for this generated type comment go to    * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments    */    public class ListJbossJndi {    /**    *    */    public ListJbossJndi() {      super();      // TODO Auto-generated constructor stub     }     public static void main(String[] args) {      try {       Properties env = new Properties();       env.load(new FileInputStream("jbossJndi.properties"));       //env.list(System.out);       Context ctx = new InitialContext(env);       listCtx(ctx.lookup("sylilzy"));      }      catch (Exception e) {       e.printStackTrace();      }     }     static void listCtx(Object o){      if(!(o instanceof Context))log(":"+o);      else {       log("\n-----------------------------");      try {       Context ctx=(Context)o;       //log(ctx.getNameInNamespace()+"/:");       NamingEnumeration list=ctx.listBindings("");       while(list.hasMore()){        Binding bind=(Binding)list.next();        log("\n/"+ctx.getNameInNamespace()+"/"+bind.getName());        listCtx(bind.getObject());       }       log("\n-----------------------------");      }      catch (NamingException e) {       // TODO Auto-generated catch block       e.printStackTrace();      }     }     }     static void log(Object o){      System.out.print(o);     }    }

关键字:JNDI,J2EE,Java,命名和目录接口,Java Naming and Directory Interface

摘要:本文详细介绍了JNDI的目录相关内容,并以DNS Service Provider为例进行了示例代码的演示.本文为系列文章的第二篇,JNDI的基础内容请见本系列的第一篇

总述:

目录(Directory)可看作是对命名(Naming)的一个扩充,一个目录对象不仅像命名一样,而且还提供的对属性(Attributes)的操作.由API文档可知,javax.naming.directory.DirContext 类扩展自Context接口,同样,javax.naming.directory.InitialDirContext也扩展自 javax.naming.InitialContext,由此也可看出目录操作完全支持命名操作。下面给出一个DNS Service Provider例子以演示有关目录的一些操作:

 

<!-- Code creation by HtmlSave Eclipse Plug-in (C) 2005 Morten Moeller / eclipse.moelleryoung.com --> * Created on 2005-11-17 package com.sily.jndi; import java.util.Properties; /**  * Description:  *   * @author shizy  * @version 1.0 date:2005-11-17  */ public class TestDNSJndi {     public static void main(String[] args) throws Exception {         Properties env = new Properties();         env.put(Context.INITIAL_CONTEXT_FACTORY,                 "com.sun.jndi.dns.DnsContextFactory");         //此IP一定要为要访问的DNS服务器的IP,可通过网络设置查看         env.put(Context.PROVIDER_URL"dns://10.17.45.239");         DirContext ctx = new InitialDirContext(env);         System.out.println("a:" + ctx);         DirContext ctx1 = (DirContext) ctx.lookup("www.sina.com");         System.out.println("b:" + ctx1);         printAttributes("c:", ctx1.getAttributes(""));         //从ctx.getAttributes("www.sina.com")与ctx1.getAttributes("")结果一样         printAttributes("d:", ctx.getAttributes("www.sina.com"));         Attributes attrs1 = ctx.getAttributes("www.sina.com",                 new String[] { "a" });         Attributes attrs2 = ctx.getAttributes("www.163.com",                 new String[] { "a" });         Attributes attrs3 = ctx1.getAttributes(""new String[] { "a" });         Attributes attrs4 = ctx.getAttributes("www.baidu.com",                 new String[] { "a" });         printAttributes("e:", attrs1);         printAttributes("f:", attrs2);         printAttributes("g:", attrs3);         printAttributes("attrs4:", attrs4);                  System.out.println("nameParse:"+ctx1.getNameInNamespace());         //list,此方法会导致程序lock         //listEnumation("list:",ctx.list(""));         //----------------------search         Attributes matchAttrs = new BasicAttributes(true);         matchAttrs.put(new BasicAttribute("a""61.172.201.13"));         NamingEnumeration answer = ctx1.search("www.sina.com", matchAttrs);         printNamingEnumeration("search :", answer);     }     public static void printAttributes(String tag, Attributes attres)             throws Exception {         for (NamingEnumeration ae = attres.getAll(); ae.hasMore();) {             Attribute attr = (Attribute) ae.next();             System.out                     .println(tag                             + "-----------------------------------------------\nattribute: "                             + attr.getID());             /* Print each value */             for (NamingEnumeration e = attr.getAll(); e.hasMore(); System.out                     .println("value: " + e.next()))                 ;         }     }     public static void listEnumation(String tag, NamingEnumeration name)             throws Exception {         for (; name.hasMore();) {             NameClassPair nameClass = (NameClassPair) name.next();             System.out                     .println(tag                             + "-----------------------------------------------\nattribute: "                             + nameClass.getName() + ":"                             + nameClass.getClassName());         }     }     public static void printNamingEnumeration(String tag, NamingEnumeration e)             throws Exception {         for (; e.hasMore();) {             Attribute attr = (Attribute) e.next();             System.out                     .println(tag                             + "-----------------------------------------------\nattribute: "                             + attr.getID());             /* Print each value */             for (NamingEnumeration ve = attr.getAll(); ve.hasMore(); System.out                     .println("value: " + ve.next()))                 ;         }     } } 

 

上例中,在jdk1.4中可运行通过。对于DNS Service Provider更详细的文档,大家可通过此URL下载:http://java.sun.com/products/jndi/downloads/index.html

上例一个可能运行结果如下:

a:javax.naming.directory.InitialDirContext@1bf216ab:com.sun.jndi.dns.DnsContext@3a6727c:-----------------------------------------------attribute: CNAMEvalue: us.sina.com.cn.d:-----------------------------------------------attribute: CNAMEvalue: us.sina.com.cn.e:-----------------------------------------------attribute: Avalue: 218.30.66.67value: 218.30.66.68value: 218.30.66.69value: 218.30.66.70value: 218.30.66.71value: 218.30.66.56value: 218.30.66.57value: 218.30.66.58value: 218.30.66.59value: 218.30.66.60value: 218.30.66.61value: 218.30.66.62value: 218.30.66.63value: 218.30.66.64value: 218.30.66.65value: 218.30.66.66f:-----------------------------------------------attribute: Avalue: 220.181.28.42g:-----------------------------------------------attribute: Avalue: 218.30.66.68value: 218.30.66.69value: 218.30.66.70value: 218.30.66.71value: 218.30.66.56value: 218.30.66.57value: 218.30.66.58value: 218.30.66.59value: 218.30.66.60value: 218.30.66.61value: 218.30.66.62value: 218.30.66.63value: 218.30.66.64value: 218.30.66.65value: 218.30.66.66value: 218.30.66.67attrs4:-----------------------------------------------attribute: Avalue: 220.181.27.5nameParse:www.sina.com.Exception in thread "main" javax.naming.OperationNotSupportedExceptionat com.sun.jndi.dns.DnsContext.c_search(Unknown Source)at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(Unknown Source)at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(Unknown Source)at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(Unknown Source)at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(Unknown Source)at com.sily.jndi.TestDNSJndi.main(TestDNSJndi.java:57)示例分析:通过分析代码,我们可以看出我们从DNS服务器获取了指定域名的IP地址,而且可以看出www.sina.com有多个IP.另外,可以看出从ctx.getAttributes("www.sina.com")得到的结果与ctx1.getAttributes("")结果一样,这便是目录操作的两种模式,这两种模式取得的结果是一样的,这点可以参考API文档(http://java.sun.com/j2se/1.5.0/docs/api/javax/naming/directory/DirContext.html):There are two basic models of what attributes should be associated with. First, attributes may be directly associated with a DirContext object. In this model, an attribute operation on the named object is roughly...

另外,还有一点需要注意,从ctx.getAttributes()方法返回的Attributes中包含多个Attribute,每个Attribute包含多个values,其它详细内容请参考API文档最后,代码NamingEnumeration answer = ctx1.search("www.sina.com", matchAttrs);试图对ctx1进行属性查找,但是抛出了异常,查看 DNS Service Provider 的文档可知,DNS Service Provider 没有提供对search方法的支持,大家可用其它的SP来测试此方法,如LDAP SP

总结:

此例只是简单地演示的JNDI的目录操作,对于目录操作的其它高级主题如Search,Search Scope,Count Limit,Composite Names 等没有详细介绍,请参考其它相关文档.