欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java中的命名与目录接口JNDI基本操作方法概览

程序员文章站 2024-03-11 12:20:01
对jndi总体的理解: jndi(java naming and directory interface)它提供了一套使用命名和目录服务的接口。用户可以通过它来使用命名和...

对jndi总体的理解:

jndi(java naming and directory interface)它提供了一套使用命名和目录服务的接口。用户可以通过它来使用命名和目录服务。就像jdbc一样。jndi包括命名服务和目录服务两部分,其中目录服务包含目录对象directory object,它包含若干属性对象。提供了对属性的很多操作。
命名和目录服务:

命名和目录服务我们一直在使用,如操作系统的文件系统,它给我们提供对文件的操作,查询,添加删除等功能。dns服务将url同ip地址绑定在了一起。命名和目录系统的最主要的功能是将name和对象绑定。在它的基础之上还提供更多的功能如lookup,search.而且存储的对象是有一定层次结构的。使用这样的服务我们可以对对象更加有效的管理和操作。
命名服务将一个名称映射到一个对象。rmi registry和corba naming service都是命名服务。
目录服务也存放对象,但目录服务识别这些对象的相关属性。可以用项目属性来搜索目录。
在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。在使用命名和目录服务时获得initial context是对整个名字空间操作的入口。在目录服务中是dircontext.
jndi(java naming and directory interface)是一个应用程序设计的api,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似jdbc都是构建在抽象层上。
jndi可访问的现有的目录及服务有:
dns、xnam 、novell目录服务、ldap(lightweight directory access protocol 轻型目录访问协议)、 corba对象服务、文件系统、windows xp/2000/nt/me/9x的注册表、rmi、dsml v1&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、jdbc 2.0以及iiop上的rmi通过jndi来使用corba的命名服务。

jndi与jdbc:
jndi提供了一种统一的方式,可以用在网络上查找和访问服务。通过指定一个资源名称,该名称对应于数据库或命名服务中的一个纪录,同时返回数据库连接建立所必须的信息。
代码示例:

try{
context cntxt = new initialcontext();
datasource ds = (datasource) cntxt.lookup("jdbc/dpt");
}
catch(namingexception ne){
...
}

jndi与jms:
消息通信是软件组件或应用程序用来通信的一种方法。jms就是一种允许应用程序创建、发送、接收、和读取消息的java技术。
代码示例:

try{
properties env = new properties();
initialcontext inictxt = new initialcontext(env);
topicconnectionfactory connfactory = (topicconnectionfactory) inictxt.lookup("ttopicconnectionfactory");
...
}
catch(namingexception ne){
...
}

访问特定目录:举个例子,人是个对象,他有好几个属性,诸如这个人的姓名、电话号码、电子邮件地址、邮政编码等属性。通过getattributes()方法

attribute attr = directory.getattributes(personname).get("email");
string email = (string)attr.get();

通过使用jndi让客户使用对象的名称或属性来查找对象:

foxes = directory.search("o=wiz,c=us", "sn=fox", controls);

通过使用jndi来查找诸如打印机、数据库这样的对象,查找打印机的例子:

printer printer = (printer)namespace.lookup(printername);
printer.print(document);

浏览命名空间:

namingenumeration list = namespace.list("o=widget, c=us");
while (list.hasmore()) {
nameclasspair entry = (nameclasspair)list.next();
display(entry.getname(), entry.getclassname());
}
  • 常用的jndi操作:
  • void bind(string sname,object object);――绑定:把名称同对象关联的过程
  • void rebind(string sname,object object);――重新绑定:用来把对象同一个已经存在的名称重新绑定
  • void unbind(string sname);――释放:用来把对象从目录中释放出来
  • void lookup(string sname,object object);――查找:返回目录总的一个对象
  • void rename(string soldname,string snewname);――重命名:用来修改对象名称绑定的名称
  • namingenumeration listbinding(string sname);――清单:返回绑定在特定上下文中对象的清单列表
  • namingenumeration list(string sname);

代码示例:重新得到了名称、类名和绑定对象。

namingenumeration namenumlist = ctxt.listbinding("cntxtname");
...
while ( namenumlist.hasmore() ) {
binding bnd = (binding) namenumlist.next();
string sobjname = bnd.getname();
string sclassname = bnd.getclassname();
someobject objlocal = (someobject) bnd.getobject();
}

了解名字服务和目录服务的相关概念,有助于更好的使用jndi。 naming service       名字服务定义了如何将名字与对象关联,并通过名字如何找到对象的方法。典型的例子如:dns将域名与ip关联,文件系统将文件名与文件相关联。在名字服务中,主要的概念:
-          名字(names),在名字系统中实际对象的代号,如文件名,域名等,它会被用来查找关联的对象。不同的系统中会有不同的命名规范,如文件系统采用“\”来表示层级,而dns则使用“.”。
-          绑定(bindings),名字和实际对象的关联。
-          引用和地址(references and addresses),当对象不能直接被存储在名字系统时,就必须使用引用,通过引用找到实际的对象。在系统中,保存的引用的内容被称为地址。引用还有另一个用处:在名字系统中,缺少象关系数据库中外键的概念。通过使用引用,可以作为外键的一个取代办法。
-          上下文(context),它是一个名字-对象集合,提供了与名字系统交互的主要操作,如查找、绑定、去绑定。子上下文(subcontext)与它的关系类似文件系统中目录和子目录的关系,子上下文被包含在一个上下文中,通过父上下文中的一个名字与子上下文关联。
-          名字系统和名字空间(naming systems and namespaces),名字系统是相同类型的上下文的集合,它提供名字服务;名字空间,是名字系统中的名字集合,如文件系统的文件名和目录。
directory service       目录服务是名字服务的扩展,它除了关联名字和对象,还允许对象包含属性。目录系统通常以层次结构组织数据。在目录服务中的主要概念:
-          属性(attributes),它属于目录对象,它是(名字,值)对,属性可以有多个值。
-          目录和目录服务(directories and directory services),目录是目录对象的集合;目录服务则提供与目录相关的服务,创建、删除和修改存放在目录中的对象的属性。
-          查找和查找过滤器(searches and search filters),获取目录对象的操作就是查找;过滤器是类似查找条件的对象。
基本使用
 
²        注册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,用来创建jndi state的类
对于目录服务,由于一般需要安全设置,还通常使用:
-          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"); 
initialcontext ctx = new initialcontext();

连接目录服务:

 hashtable env = new hashtable(); 
 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"); 
 //登录ldap server需要的用户名 
 env.put(context.security_principal, "ldapuser"); 
 //登录ldap server需要的密码 
 env.put(context.security_credentials, "mypassword"); 
initialdircontext ctx = new initialdircontext(env);

 
多服务提供者:如果应用包含多个服务提供者,在连接时略有不同。以名字服务为例

hashtable env = new hashtable(); 
 env.put(context.initial_context_factory, 
"com.sun.jndi.rmi.registry.registrycontextfactory"); 
 env.put(context.provider_url, "rmi://myserver.com:1099"); 
 //使用不同的构造函数 
initialcontext ctx = new initialcontext(env); 

 
查找对象
不论名字服务还是目录服务,都是使用lookup来查找对象的。除了可以使用string作为参数之外,lookup还可使用name接口作为参数。

greeter greeter = (greeter)ctx.lookup("sayhello"); 

如果想要获得上下文中所有的对象名字,就使用lis返回nameclasspair列表。nameclasspair包含对象名字和对象类名。如果想要获得实际的对象实例列表,就使用listbindings,它返回binding列表。binding是nameclasspair的子类,它包含对象的实例。
-  list

namingenumeration list = ctx.list("awt"); 
while (list.hasmore()) { 
 nameclasspair nc = (nameclasspair)list.next(); 
 system.out.println(nc); 
}

-  listbindings

namingenumeration bindings = ctx.listbindings("awt"); 
while (bindings.hasmore()) { 
 binding bd = (binding)bindings.next(); 
 system.out.println(bd.getname() + ": " + bd.getobject()); 
} 

 
对象绑定
- 使用bind添加绑定

fruit fruit = new fruit("orange");
ctx.bind("favorite", fruit);

- 使用rebind修改绑定

fruit fruit = new fruit("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方法就可以获得属性值。

string dn = "uid=me, dc=mycompany, dc=com, ou=customer, o=exampleapp"; 
context user = (context)ctx.lookup(dn); 
//获得所有属性 
attributes attrs = user.getattributes(""); 
attribute test= attrs .get("test"); 
object testvalue= test.get(); 

上例中获得的是user的所有属性,在实际使用过程中,考虑网络带宽的影响,可以设置获取要获取的属性列表:

string reqd_attrs = new string[] { "surname", "initials","title", "rfc822mailalias"}; 
attributes attrs = user.getattributes("", reqd_attrs); 

 
查找和过滤
使用search方法完成。

 public dircontext[] finduser(string initials,string surname,string country,string phone) { 
  //构造条件 
   basicattributes search_attrs = new basicattributes(); 
  search_attrs.put("initials", initials); 
  search_attrs.put("sn", surname); 
  search_attrs.put("c", country); 
  if(phone != null) 
   search_attrs.put("phonenumber", phone); 
  namingenumeration results = initial_ctx.search("ou=customer,o=exampleapp", search_attrs); 
  linkedlist found = new linkedlist(); 
  while(results.hasmore()) { 
   searchresults sr = (searchresults)results.next(); 
   string name = sr.getname(); 
   object ctx = sr.getobject(); 
   if((ctx == null) || !(ctx instanceof dircontext)) 
    found.add(initial_ctx.lookup(name)); 
   else 
    found.add(ctx); 
  } 
  dircontext[] ret_val = new dircontext[found.size()]; 
  found.toarray(ret_val); 
  return ret_val; 
 } 

 
dircontext接口主要过滤方式:

1.使用过滤字符串 

string reqd_attrs = new string[] { "cn", "uid","rfc822mailalias" }; 
namingenumeration results = initial_ctx.search("ou=customer, o=exampleapp",search_attrs,reqd_attrs); 

 
2.使用searchcontrols,获得更多的控制

searchcontrols ctrls = new searchcontrols(); 
ctrls.setcountlimit(20); 
ctrls.settimelimit(5000); 
ctrls.setsearchscope(searchcontrols.subtree_scope); 
namingenumeration results = initial_ctx.search("cat=books,ou=products, 
o=exampleapp","title=*java*",ctrls); 

修改属性
使用dircontext和initialdircontext的modifyattributes方法完成。所谓的修改过程,实际就是先构造要修改的属性列表,然后使用上述方法提交。对于属性包含多个值时,需要把属性的不修改的值也要包含,否则服务器会认为那些值不再需要而删除它们。

 public void updateaddress(string dn,string address, string country, string phone) { 
  basicattributes mod_attrs = new basicattributes(); 
  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 = new modificationitems[2]; 
attribute email = new basicattribute("rfc822mailalias", new_email); 
modificationitem email_mod = new modificationitem(dircontext.add_attribute, email); 
attribute addr = new basicattribute("address", address); 
modificationitem addr_mod = new modificationitem(dircontext.replace_attribute, addr); 
mod_items[0] = email_mod; 
mod_items[1] = addr_mod; 
initial_ctx.modifyattributes(dn, mod_items);

创建上下文
使用createsubcontext方法完成。

 basicattributes attrs = new basicattributes(); 
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中配置:

<context path="/jndi"> 
 
 <resource name="bean/mybeanfactory" auth="container" 
   type="com.huawei.jndi.bean.mybean" 
   factory="org.apache.naming.factory.beanfactory" 
   bar="23"/> 
 
</context> 

上面就在tomcat中声明了一个组件,接下来在代码中可以获取这个组件:

try 
{ 
 context initcontext = new initialcontext(); 
 context envctx = (context) initcontext.lookup("java:comp/env"); 
 mybean bean = (mybean) envctx.lookup("bean/mybeanfactory"); 
 system.out.println(bean.getbar()); 
} 
catch (exception e) 
{ 
 e.printstacktrace(); 
} 

总结:在tomcat中配置jndi组件,然后在代码中获取已配好的组件。

各web容器的jndi实现类是不同的,比如在jboss中,jndi提供类是org.jnp.interfaces.namingcontextfactory,与tomcat是不同的。

这样看来,jndi的作用和spring的依赖注入倒是差不多。但是通过jndi,可以实现跨应用,甚至跨域获取组件。在服务器a上配置的组件,在另一台服务器b上,可以通过jndi获取到。

spring也提供了对jndi的封装,使用起来更加方便,以下是一个例子。

<!-- jndi模板 --> 
<bean id="jnditemplate" class="org.springframework.jndi.jnditemplate"> 
 <property name="environment"> 
  <props> 
   <prop key="java.naming.factory.initial">org.jnp.interfaces.namingcontextfactory</prop> 
   <prop key="java.naming.provider.url">10.137.96.212:18199</prop> 
   <prop key="java.naming.factory.url.pkgs">org.jnp.interfaces:org.jboss.naming</prop> 
  </props> 
 </property> 
</bean> 
 
<!-- 创建连接工厂 --> 
<bean id="jmsconnectionfactory" class="org.springframework.jndi.jndiobjectfactorybean"> 
 <property name="jnditemplate" ref="jnditemplate" /> 
 <property name="jndiname" value="topicconnectionfactory" /> 
</bean> 

先声明jnditemplate,配置好目标地址、jndi服务提供类。然后通过jndiobjectfactorybean,就可以很方便地获取jndi组件,并进行类型转换。