一、概念:
1、JNDI(JavaNaming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,客户端通过统一接口的API,来使用不同服务,这些不同的服务分别实现了JNDI服务供应接口(SPI),使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是命名服务的一种自然扩展。两者之间的关键差别是目录服务中对象不但可以有名称还可以有属性(例如,用户有email地址),而命名服务中对象没有属性。
JNDI中的命名(Naming),就是将Java对象以某个名称的形式绑定(binding)到一个容器环境(Context)中,以后调用容器环境(Context)的查找(lookup)方法又可以查找出某个名称所绑定的Java对象。
JNDI可访问的现有的目录及服务有:DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。
JNDI中的目录(Directory)是指将一个对象的所有属性信息保存到一个容器环境中。JNDI的目录(Directory)原理与JNDI的命名(Naming)原理非常相似,主要的区别在于目录容器环境中保存的是对象的属性信息,而不是对象本身,所以,目录提供的是对属性的各种操作。
2、JNDI树结构示意图:
3、JNDI环境属性:
4、 JNDI的基本使用方法:
一种是在代码里指定上面的参数:
Hashtable
env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
env.put(Context.PROVIDER_URL, "dns:" + dnsServer);
DirContext ctx = new InitialDirContext(env);
另一种是在jndi.properties文件中指定,文件内容可以如下:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=localhost
jndi.properties文件为所有的InitialContexts设置默认的属性,jndi.properties文件的搜索次序:·CLASSPATH,·$JAVA_HOME/lib;
二、Demo:
从JDK 1.4开始的版本又集成了用于DNS查询的JNDI服务程序,所以,如果我们使用JDK 1.4及更高的JDK版本来开发DNS信息查询程序时,不需要下载和安装JNDI API和用于DNS查询的JNDI服务程序。
下面是一个使用JNDI API获取DNS信息的程序,运行这个程序时,需要指定一个或两个参数,第一个参数是必须的,为要查询的域名,第二个参数是可选的,为查询时所使用的DNS服务器的IP地址,如果没有指定第二个参数,DNS的JNDI服务程序将使用底层操作系统上设置的DNS服务器。
import java.util.Hashtable; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; public class DNSQuery { public static void main(String[] args) throws NamingException { /*第一个参数指定要查询的域或主机名,第二个参数指定查询的DNS服务器, 为了程序的简单易读性,省略了严格的参数错误检查*/ String domain = args[0]; String dnsServer = args.length<2 ? "" : ("//" + args[1]); //通过环境属性来指定Context的工厂类 Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory"); env.put(Context.PROVIDER_URL, "dns:" + dnsServer); DirContext ctx = new InitialDirContext(env); //分别获取包含所有属性和只包含Mx属性的Attributes对象 Attributes attrsAll = ctx.getAttributes(domain); Attributes attrsMx = ctx.getAttributes(domain, new String[]{"MX"}); /*上面的整段程序代码也可以用下面这段程序代码来替代,下面这段程序 代码通过查询URL中的Schema信息来自动选择Context的工厂类*/ /* DirContext ctx = new InitialDirContext(); Attributes attrsAll = ctx.getAttributes("dns:" + dnsServer + "/" + domain); Attributes attrsMx = ctx.getAttributes( "dns:" + dnsServer + "/" + domain, new String[]{"MX"}); */ System.out.println("打印出域" + domain + "的Attributes对象中的信息:"); System.out.println(attrsAll); System.out.println("--------------------------"); System.out.println("打印只检索域" + domain + "的MX记录的Attributes对象:"); System.out.println(attrsMx); System.out.println("--------------------------"); System.out.println("逐一打印出Attributes对象中的各个属性:"); NamingEnumeration attributes = attrsAll.getAll(); while(attributes.hasMore()) { System.out.println(attributes.next()); } System.out.println("--------------------------"); //直接调用get方法从attrsMx集合检索MX属性 System.out.println("直接检索Attributes对象中的MX属性:"); Attribute attrMx = attrsAll.get("MX"); System.out.println(attrMx); System.out.println("--------------------------"); //获取Mx属性中的第一个值: System.out.println("获取Mx属性中的第一个值:"); String recordMx = (String)attrMx.get(); System.out.println(recordMx); //从Mx属性的第一个值中提取邮件服务器地址 System.out.println("从MX属性值中提取的邮件服务器地址:"); String smtpServer = recordMx.substring( recordMx.indexOf(" ") + 1); System.out.println(smtpServer); }
三、源码解析:
1、Java命名系统的接口是Context,JNDI API中提供了一个InitialContext类来创建用作JNDI命名操作的入口Context对象。
2、Java目录系统的接口是DirContext,DirContext是Context的子类,显然它除了能完成目录相关的操作外,也能完成所有的命名(Naming)操作。DirContext是对Context的扩展,它在Context的基础上增加了对目录属性的操作功能,可以在其中绑定对象的属性信息和查找对象的属性信息。JNDI API中提供了一个InitialDirContext类来创建用作JNDI命名与目录属性操作的入口DirContext对象。
3、InitialContext类中的myprops成员变量是一个Hashtable类型,存储属性名称和属性值的键值对,用于初始化Context时的相关参数,defaultInitCtx成员变量存储真正实现服务的Context对象,象Context的API接口(比如bind, lookup, rebind等)都是调用这个defaultInitCtx来实现的;
4、InitialContext对象的启动过程:
构造函数如果传入了一个hashtable对象,则克隆该hashtable对象(包括克隆原hashtable对象里面的数据),如果没传,则创建一个新的hashtable对象:
可以看到构造函数都会调用init函数,在init函数中又会调用ResourceManager.getInitialEnvironment函数,下面是getInitialEnvironment函数的实现:
可以看到getInitialEnvironment函数中会判断构造函数传过来的hashtable参数如果没传,则创建一个初始只有11个元素的hashtable对象;
在这个函数中,会读取VersionHelper.PROPS的所有属性,其定义如下:
在读取这些属性值时,首先优先从构造函数传递过来的hashtable中去读取,如果读取不到,再到java.naming.applet中去读取对应的属性值,如果还是读取不到,则从系统参数中去读取;如果这三个里面只要有任何一个独到属性值了,则将属性名和属性值的键值对存储到env变量中并返回这个env对象;
再来看看init函数:
可以看到init函数首先调用上面的getInitialEnvironment返回一个存储有服务对象实例化时需要的属性的hashtable对象,如果这个hashtable里面有java.naming.factory.initial属性时,则调用getDefaultInitCtx来构造服务对象并存储在defaultInitCtx变量里面;
可以看到getDefaultInitCtx调用NamingManager.getInitialContext来实例化服务对象,而在getInitialContext中根据java.naming.factory.initial属性值指定的类名实例化服务对象,然后调用factory.getInitialContext来初始化其他属性并返回实例化的Context对象;
5、InitialContext对象相关接口的实现:
可以看到InitialContext对象相关接口的实现都是调用了getURLOrDefaultInitCtx函数返回的服务示例的接口;
可以看到这个函数会根据传递的name参数查找到对应的子容器对象Context返回,如果查找不到则返回默认的Context对象defaultInitCtx;
6、InitialDirContext对象的启动过程:
可以看到InitialDirContext的构造函数什么也没做,只是调用父类InitialContext的构造函数;
7、InitialDirContext对象相关接口的实现:
象bind/lookup/rebind等接口以及新增加的 getAttributes/modifyAttributes/getSchema等接口的实现是依赖于getURLOrDefaultInitDirCtx函数返回的DirContext对象,而getURLOrDefaultInitDirCtx函数又是调用父类的getURLOrDefaultInitCtx函数实现的;