Java中的命名与目录接口JNDI基本操作方法概览
对jndi总体的理解:
jndi(javanaminganddirectoryInterface)它提供了一套使用命名和目录服务的接口。用户可以通过它来使用命名和目录服务。就像jdbc一样。jndi包括命名服务和目录服务两部分,其中目录服务包含目录对象directoryobject,它包含若干属性对象。提供了对属性的很多操作。
命名和目录服务:
命名和目录服务我们一直在使用,如操作系统的文件系统,它给我们提供对文件的操作,查询,添加删除等功能。DNS服务将url同ip地址绑定在了一起。命名和目录系统的最主要的功能是将name和对象绑定。在它的基础之上还提供更多的功能如lookup,search.而且存储的对象是有一定层次结构的。使用这样的服务我们可以对对象更加有效的管理和操作。
命名服务将一个名称映射到一个对象。RMIRegistry和CORBANamingService都是命名服务。
目录服务也存放对象,但目录服务识别这些对象的相关属性。可以用项目属性来搜索目录。
在20世纪90年代早期,轻量级的目录访问协议(LightWeightDiretoryAccessProtocol,LDAP)被作为一种标准的目录协议被开发出来,JNDI能够访问LDAP。
j2se为jndi提供了5个扩展包:
- javax.naming;为访问命名服务提供的api
- javax.naming.directory;为访问目录服务提供了基本的接口
- javax.naming.event;支持命名和目录服务中的事件通知
- javax.naming.ldap;支持ldap的包,
- javax.naming.spi;提供了不同命名和目录服务可以挂接他们的实现的方法。
context:context是一套name-to-object的绑定(bindings),可以理解为层次或目录,它还可已包括下一层subContext。在使用命名和目录服务时获得initialcontext是对整个名字空间操作的入口。在目录服务中是DirContext.
JNDI(JavaNamingandDirectoryInterface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。
JNDI可访问的现有的目录及服务有:
DNS、XNam、Novell目录服务、LDAP(LightweightDirectoryAccessProtocol轻型目录访问协议)、CORBA对象服务、文件系统、WindowsXP/2000/NT/Me/9x的注册表、RMI、DSMLv1&v2、NIS。
JNDI优点:
包含了大量的命名和目录服务,使用通用接口来访问不同种类的服务;可以同时连接到多个命名或目录服务上;建立起逻辑关联,允许把名称同Java对象或资源关联起来,而不必指导对象或资源的物理ID。
JNDI程序包:
- javax.naming:命名操作;
- javax.naming.directory:目录操作;
- javax.naming.event:在命名目录服务器中请求事件通知;
- javax.naming.ldap:提供LDAP支持;
- javax.naming.spi:允许动态插入不同实现。
- 利用JNDI的命名与服务功能来满足企业级APIs对命名与服务的访问,诸如EJBs、JMS、JDBC2.0以及IIOP上的RMI通过JNDI来使用CORBA的命名服务。
JNDI与JDBC:
JNDI提供了一种统一的方式,可以用在网络上查找和访问服务。通过指定一个资源名称,该名称对应于数据库或命名服务中的一个纪录,同时返回数据库连接建立所必须的信息。
代码示例:
try{ Contextcntxt=newInitialContext(); DataSourceds=(DataSource)cntxt.lookup("jdbc/dpt"); } catch(NamingExceptionne){ ... }
JNDI与JMS:
消息通信是软件组件或应用程序用来通信的一种方法。JMS就是一种允许应用程序创建、发送、接收、和读取消息的JAVA技术。
代码示例:
try{ Propertiesenv=newProperties(); InitialContextinictxt=newInitialContext(env); TopicConnectionFactoryconnFactory=(TopicConnectionFactory)inictxt.lookup("TTopicConnectionFactory"); ... } catch(NamingExceptionne){ ... }
访问特定目录:举个例子,人是个对象,他有好几个属性,诸如这个人的姓名、电话号码、电子邮件地址、邮政编码等属性。通过getAttributes()方法
Attributeattr=directory.getAttributes(personName).get("email"); Stringemail=(String)attr.get();
通过使用JNDI让客户使用对象的名称或属性来查找对象:
foxes=directory.search("o=Wiz,c=US","sn=Fox",controls);
通过使用JNDI来查找诸如打印机、数据库这样的对象,查找打印机的例子:
Printerprinter=(Printer)namespace.lookup(printerName); printer.print(document);
浏览命名空间:
NamingEnumerationlist=namespace.list("o=Widget,c=US"); while(list.hasMore()){ NameClassPairentry=(NameClassPair)list.next(); display(entry.getName(),entry.getClassName()); }
- 常用的JNDI操作:
- voidbind(StringsName,Objectobject);――绑定:把名称同对象关联的过程
- voidrebind(StringsName,Objectobject);――重新绑定:用来把对象同一个已经存在的名称重新绑定
- voidunbind(StringsName);――释放:用来把对象从目录中释放出来
- voidlookup(StringsName,Objectobject);――查找:返回目录总的一个对象
- voidrename(StringsOldName,StringsNewName);――重命名:用来修改对象名称绑定的名称
- NamingEnumerationlistBinding(StringsName);――清单:返回绑定在特定上下文中对象的清单列表
- NamingEnumerationlist(StringsName);
代码示例:重新得到了名称、类名和绑定对象。
NamingEnumerationnamEnumList=ctxt.listBinding("cntxtName"); ... while(namEnumList.hasMore()){ Bindingbnd=(Binding)namEnumList.next(); StringsObjName=bnd.getName(); StringsClassName=bnd.getClassName(); SomeObjectobjLocal=(SomeObject)bnd.getObject(); }
了解名字服务和目录服务的相关概念,有助于更好的使用JNDI。Namingservice 名字服务定义了如何将名字与对象关联,并通过名字如何找到对象的方法。典型的例子如:DNS将域名与IP关联,文件系统将文件名与文件相关联。在名字服务中,主要的概念:
- 名字(Names),在名字系统中实际对象的代号,如文件名,域名等,它会被用来查找关联的对象。不同的系统中会有不同的命名规范,如文件系统采用“\”来表示层级,而DNS则使用“.”。
- 绑定(Bindings),名字和实际对象的关联。
- 引用和地址(ReferencesandAddresses),当对象不能直接被存储在名字系统时,就必须使用引用,通过引用找到实际的对象。在系统中,保存的引用的内容被称为地址。引用还有另一个用处:在名字系统中,缺少象关系数据库中外键的概念。通过使用引用,可以作为外键的一个取代办法。
- 上下文(Context),它是一个名字-对象集合,提供了与名字系统交互的主要操作,如查找、绑定、去绑定。子上下文(subcontext)与它的关系类似文件系统中目录和子目录的关系,子上下文被包含在一个上下文中,通过父上下文中的一个名字与子上下文关联。
- 名字系统和名字空间(NamingSystemsandNamespaces),名字系统是相同类型的上下文的集合,它提供名字服务;名字空间,是名字系统中的名字集合,如文件系统的文件名和目录。
Directoryservice 目录服务是名字服务的扩展,它除了关联名字和对象,还允许对象包含属性。目录系统通常以层次结构组织数据。在目录服务中的主要概念:
- 属性(Attributes),它属于目录对象,它是(名字,值)对,属性可以有多个值。
- 目录和目录服务(DirectoriesandDirectoryServices),目录是目录对象的集合;目录服务则提供与目录相关的服务,创建、删除和修改存放在目录中的对象的属性。
- 查找和查找过滤器(SearchesandSearchFilters),获取目录对象的操作就是查找;过滤器是类似查找条件的对象。
基本使用
² 注册JNDI提供者
在使用JNDI之前,需要先获取JNDI的提供者,并在系统注册它。与JNDI相关的系统属性在javax.naming.Context中定义,常用的属性:
- java.naming.factory.initial,服务提供者用来创建InitialContext的类名。
- java.naming.provider.url,用来配置InitialContext的初始url
- java.naming.factory.object,用来创建name-to-object映射的类,用于NameClassPair和References。
- java.naming.factory.state,用来创建jndistate的类
对于目录服务,由于一般需要安全设置,还通常使用:
- java.naming.security.authentication,安全类型,三个值:none,simple或strong。
- java.naming.security.principal,认证信息。
- java.naming.security.credentials,证书信息。
- java.naming.security.protocol,安全协议名。
使用System.setProperty注册,如果程序不显示说明,那么java会在classpath内查找jdni.properties文件来完成注册。jdni.properties例子:
java.naming.factory.initial=com.codeline.db.MockInitialContextFactory
连接服务
注册之后,就可以实施服务连接了。对于名字服务由InitialContext开始,目录服务则使用InitialDirContext。它们分别实现了Context和DirContext,这两个接口分别对应名字服务和目录服务的接口,也是JNDI中最重要的两个接口。
连接名字服务:
System.setProperty(Context.INITIAL_CONTEXT_FACTORY," com.sun.jndi.fscontext.FSContextFactory"); InitialContextctx=newInitialContext();
连接目录服务:
Hashtableenv=newHashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL,"ldap://myserver.com/"); env.put(Context.SECURITY_AUTHENTICATION,"simple"); //登录ldapserver需要的用户名 env.put(Context.SECURITY_PRINCIPAL,"ldapuser"); //登录ldapserver需要的密码 env.put(Context.SECURITY_CREDENTIALS,"mypassword"); InitialDirContextctx=newInitialDirContext(env);
多服务提供者:如果应用包含多个服务提供者,在连接时略有不同。以名字服务为例
Hashtableenv=newHashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); env.put(Context.PROVIDER_URL,"rmi://myserver.com:1099"); //使用不同的构造函数 InitialContextctx=newInitialContext(env);
查找对象
不论名字服务还是目录服务,都是使用lookup来查找对象的。除了可以使用String作为参数之外,lookup还可使用Name接口作为参数。
Greetergreeter=(Greeter)ctx.lookup("SayHello");
如果想要获得上下文中所有的对象名字,就使用lis返回NameClassPair列表。NameClassPair包含对象名字和对象类名。如果想要获得实际的对象实例列表,就使用listBindings,它返回Binding列表。Binding是NameClassPair的子类,它包含对象的实例。
- list
NamingEnumerationlist=ctx.list("awt"); while(list.hasMore()){ NameClassPairnc=(NameClassPair)list.next(); System.out.println(nc); }
- listBindings
NamingEnumerationbindings=ctx.listBindings("awt"); while(bindings.hasMore()){ Bindingbd=(Binding)bindings.next(); System.out.println(bd.getName()+":"+bd.getObject()); }
对象绑定
-使用bind添加绑定
Fruitfruit=newFruit("orange"); ctx.bind("favorite",fruit);
-使用rebind修改绑定
Fruitfruit=newFruit("lemon"); ctx.rebind("favorite",fruit);
-使用unbind去除绑定。
ctx.unbind("favorite");
对象改名
使用rename可以给一个在上下文中的对象改名
ctx.rename("report.txt","old_report.txt");
-获取属性
属性相关的接口是Attribute和Attributes,它们都在javax.naming.directory包内。通过DirContext的getAttributes方法就可以获得对象的属性集合,然后使用Attributes的get方法获得对应的属性,最后通过Attribute的get方法就可以获得属性值。
Stringdn="uid=me,dc=mycompany,dc=com,ou=customer,o=ExampleApp"; Contextuser=(Context)ctx.lookup(dn); //获得所有属性 Attributesattrs=user.getAttributes(""); Attributetest=attrs.get("test"); ObjecttestValue=test.get();
上例中获得的是user的所有属性,在实际使用过程中,考虑网络带宽的影响,可以设置获取要获取的属性列表:
Stringreqd_attrs=newString[]{"surname","initials","title","rfc822mailalias"}; Attributesattrs=user.getAttributes("",reqd_attrs);
查找和过滤
使用search方法完成。
publicDirContext[]findUser(Stringinitials,Stringsurname,Stringcountry,Stringphone){ //构造条件 BasicAttributessearch_attrs=newBasicAttributes(); search_attrs.put("initials",initials); search_attrs.put("sn",surname); search_attrs.put("c",country); if(phone!=null) search_attrs.put("phonenumber",phone); NamingEnumerationresults=initial_ctx.search("ou=Customer,o=ExampleApp",search_attrs); LinkedListfound=newLinkedList(); while(results.hasMore()){ SearchResultssr=(SearchResults)results.next(); Stringname=sr.getName(); Objectctx=sr.getObject(); if((ctx==null)||!(ctxinstanceofDirContext)) found.add(initial_ctx.lookup(name)); else found.add(ctx); } DirContext[]ret_val=newDirContext[found.size()]; found.toArray(ret_val); returnret_val; }
DirContext接口主要过滤方式:
1.使用过滤字符串
Stringreqd_attrs=newString[]{"cn","uid","rfc822mailalias"}; NamingEnumerationresults=initial_ctx.search("ou=Customer,o=ExampleApp",search_attrs,reqd_attrs);
2.使用SearchControls,获得更多的控制
SearchControlsctrls=newSearchControls(); ctrls.setCountLimit(20); ctrls.setTimeLimit(5000); ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE); NamingEnumerationresults=initial_ctx.search("cat=books,ou=Products, o=ExampleApp","title=*Java*",ctrls);
修改属性
使用DirContext和InitialDirContext的modifyAttributes方法完成。所谓的修改过程,实际就是先构造要修改的属性列表,然后使用上述方法提交。对于属性包含多个值时,需要把属性的不修改的值也要包含,否则服务器会认为那些值不再需要而删除它们。
publicvoidupdateAddress(Stringdn,Stringaddress,Stringcountry,Stringphone){ BasicAttributesmod_attrs=newBasicAttributes(); if(address!=null) mod_attrs.put("address",address); if(country!=null) mod_attrs.put("c",country); if(phone!=null) mod_attrs.put("phonenumber",phone); if(mod_attrs.size()!=0) initial_ctx.modifyAttributes(dn,DirContext.REPLACE_ATTRIBUTE,mod_attrs); }
使用ModificationItem,也可一次进行多个不同的修改操作:
ModificationItem[]mod_items=newModificationItems[2]; Attributeemail=newBasicAttribute("rfc822mailalias",new_email); ModificationItememail_mod=newModificationItem(DirContext.ADD_ATTRIBUTE,email); Attributeaddr=newBasicAttribute("address",address); ModificationItemaddr_mod=newModificationItem(DirContext.REPLACE_ATTRIBUTE,addr); mod_items[0]=email_mod; mod_items[1]=addr_mod; initial_ctx.modifyAttributes(dn,mod_items);
创建上下文
使用createSubcontext方法完成。
BasicAttributesattrs=newBasicAttributes(); attrs.put("initials",initials); attrs.put("sn",surname); attrs.put("rfc822mailalias",email); if(address!=null) attrs.put("address",address); if(country!=null) attrs.put("c",country); if(phone!=null) attrs.put("phonenumber",phone); initial_ctx.createSubcontext(dn,attrs);
删除上下文
使用destroySubcontext方法完成。
initial_ctx.destroySubcontext(dn);
实例
以下再举一个实例。
在tomcat的conf/server.xml中配置:
<Contextpath="/jndi"> <Resourcename="bean/MyBeanFactory"auth="Container" type="com.huawei.jndi.bean.MyBean" factory="org.apache.naming.factory.BeanFactory" bar="23"/> </Context>
上面就在tomcat中声明了一个组件,接下来在代码中可以获取这个组件:
try { ContextinitContext=newInitialContext(); ContextenvCtx=(Context)initContext.lookup("java:comp/env"); MyBeanbean=(MyBean)envCtx.lookup("bean/MyBeanFactory"); System.out.println(bean.getBar()); } catch(Exceptione) { e.printStackTrace(); }
总结:在tomcat中配置jndi组件,然后在代码中获取已配好的组件。
各WEB容器的JNDI实现类是不同的,比如在JBOSS中,JNDI提供类是org.jnp.interfaces.NamingContextFactory,与tomcat是不同的。
这样看来,JNDI的作用和spring的依赖注入倒是差不多。但是通过JNDI,可以实现跨应用,甚至跨域获取组件。在服务器A上配置的组件,在另一台服务器B上,可以通过JNDI获取到。
spring也提供了对jndi的封装,使用起来更加方便,以下是一个例子。
<!--JNDI模板--> <beanid="jndiTemplate"class="org.springframework.jndi.JndiTemplate"> <propertyname="environment"> <props> <propkey="java.naming.factory.initial">org.jnp.interfaces.NamingContextFactory</prop> <propkey="java.naming.provider.url">10.137.96.212:18199</prop> <propkey="java.naming.factory.url.pkgs">org.jnp.interfaces:org.jboss.naming</prop> </props> </property> </bean> <!--创建连接工厂--> <beanid="jmsConnectionFactory"class="org.springframework.jndi.JndiObjectFactoryBean"> <propertyname="jndiTemplate"ref="jndiTemplate"/> <propertyname="jndiName"value="TopicConnectionFactory"/> </bean>
先声明JndiTemplate,配置好目标地址、JNDI服务提供类。然后通过JndiObjectFactoryBean,就可以很方便地获取JNDI组件,并进行类型转换。