代码改变世界

J2EE学习笔记:第四天

2011-06-11 16:44  Aga.J  阅读(543)  评论(0编辑  收藏  举报

5 使用JNDI

使用JNDI,首先需要获取一个 添加或者查找名字 的 环境, 代表整个名称空间的环境叫做initial environment。 由一个名为javax.naming.InitalContext的类描述

Context ctx = new InitalContext();

Context对象描述的是一个可以用来查找对象添加对象的 环境。通过这个环境可以获取和这个环境相关的对象的列表。

这个初始化调用会抛出初始化失败等异常,编写代码过程中需要捕获并处理这些异常

 clip_image002

主要的异常包括:

(1) javax.naming.CommunicationException:Can not find SerialContextProvider

表示JNDI服务器没有启动或者 服务器的JNDI属性不正确

(2) javax,naming.NoInitialContextException:

当initialContext类没有默认的JNDI服务提供者属性或JNDI服务器属性配置不正确

(3) javax.naming.NoInitialContextException: Cannot instantiate class:XX

当为JNDI程序定义的类路径不包含JNDI服务器类时出现这个错误

(4) javax.naming.ServiceUnavialableException: Conection refused

程序的JNDI属性无法匹配当前使用的JNDI服务提供者

 

声明将要使用的JNDI服务:

使用JNDI服务器需要先配置程序以使用特定的命名服务器,通常需要使用下面的参数来声明命名服务器(具体的命名服务提供商可能还需要其他一些参数)

(1) JNDI服务类名

(2) 服务器的DNS主机名

(3) 套接字端口号

知道需要填写的参数后,我们可以使用下面任意一种方法来完成JNDI服务使用的声明:

(1) 向Java运行主目录的JNDI属性文件中添加属性

例如在jndi.properties文件中添加如下属性

Java.naming.factory.initial = com.sun.enterprise.naming.SerialInitContextFactory

Java.naming.provider.url=localhost:1099

Java.naming.factory.url.pkgs=com.sun.enterprise.naming

属性文件中每条记录都定义了一个名字/值对,InitialContext对象使用这些属性来决定JNDI服务提供者。

(2) 为程序提供应用程序资源文件

(3) 指定传递到应用程序的命令行参数

(4) 指定传递到applet的参数

<applet code=”MyApplet.class” width = ”640” height =”480”   >

<param name=”java.naming.factory.initial”

Value=”com.sun.enterprise.naming.SerialInitContextFactory”>

<param name=”java.naming.provider.url”

“value=localhost:1099”>

</applet>

为本地站点定制applet时,通过applet HTML文件使用参数会使JNDI属性更加明显,而JAR文件中的jndi.properties文件则很容易忽视

(5) 硬编码

Hashtable env=new Hashtable();

Env.put ( Context.INITIAL_CONTEXT_FACTORY,

“com.sun.enterprise.naming.SerialInitContextFactory”);

Env.put(Context.PROVIDER_URL,”localhost:1099”);

Context ctx= new InitialContext(env);

 

6 JNDI对象绑定

获取初始环境后,我们就可以查找现有对象,例如我们使用EJB的时候,主要的JNDI行为就是查找现有的被绑定的对象。

当然我们也可以向环境绑定新的对象,如前面一篇文章介绍了使用Context对象的bind函数。

(注意:被绑定的对象必须实现Serializable接口,这样名字服务器才可以存储对象副本,而且不同的服务提供者采用不同的命名规则,要根据规则来完成bind)

一个绑定的对象在解开绑定之前都会保留在名称空间中,如果绑定的名字在服务器重启后仍然保留,那么我们就称这个名字是持久的,商业服务器例如DNS,活动目录,LDAP等都是持久的名字服务器,他们将绑定的名字和ancilliary信息存储在磁盘(一般是数据库)上。

J2EE默认使用的命名服务是一种临时服务每次启动命名服务时都从SDK主目录的配置文件中重新绑定对象,这种命名服务在服务器重新启动后不会保留Context.bind方法绑定的对象

 

7 定位RMI-IIOP对象

在查找时只有一个例外,那就是当处理基于IIOPRMI对象时。

J2EE实现需要使用RMI-IIOP来实现EJB组件的远程接口,所以当查找EJB名字时,我们不能转换返回的对象,而是必须对其进行定位。这是因为RMI-IIOP使用的是一个便携式的远程对象封装真实远程对象的信息。这个便携式的远程对象含有可移植格式的真实绑定对象信息,容纳接受者通过查询这些信息来查找真正的远程对象,而从便携式远程对象上获取真实远程对象的过程就叫做 定位

InitialContext ic = new InitialContext();

Object lookup= ic.lookup(“java:comp/env/ejb/agency”);

AgencyHome home=(AgencyHome) PortableRemoteObject.narrow( lookup, AgencyHome.class);

后文介绍EJB的时候也会说到EJB远程接口与客户端服务器通信是基于IIOP的RMI的

 

8 其他JNDI接口使用

Context ctx= new InitialContext();

NamingEnumeration list= ctx.list(“sams”);

While( list.hasMore() )

{

NameClassPair item = (NameClassPair) list.next();

String cl = item.getClassName();

String name= item.getName();

System.out.println( cl+”-”+name);

}

Context ic = new InitialContext();

Ic.createSubContext(“dd”);

Context ic= new InitialContext();

Ic.destroySubcontext(“dd”);

 

9 JNDI名字

JNDI必须尽可能地以透明的方式来支持不同服务提供者的命名规则。

JNDI尽可能不解析指定为String对象的名字,同时使用/右斜线作为名字分割符来划分名字层次。

 clip_image004

 

10 属性

属性是目录服务的一个特点,使用简单的命名服务器无法使用属性,它可以存储额外的信息

目录服务将属性存储为 键/值 对, 通常支持名字的搜索,然而也支持查找具有某些属性值的对象的名字。

 

11 LDAP目录服务器

LDAP使用逗号分隔的名字列表,名字顺着树的方向从较低的条目到较高的条目依次指定。名字由键/值对构成。

C(country name) --- us uk

O ( organization name)

Ou( organizationUnitName) 组织内的一个分支

L ( localityName)

Cn ( commonName) 公共名字

Dc ( domainComponent)

Uid ( userid)

LDAP DN的实例如下:

Cn=Martin bond, ou=Authors, O=SAMS, c=us

 

12 目录服务器的使用

只有目录服务支持属性,所以无法通过普通的Context对象来访问属性,但是可以使用javax,naming.directory,DirContext来代替,它是环境的一个子类

例如 读取属性

Public static void main( String[] args )

{

Try

{

  DirContext ctx = new InitialDirContext();

  Attributes attrs = ctx.getAttributes( args[0] ) ;

  NamingEnumeration ae= attrs.getAll() ;

  While( ae.hasMore() )

  {

   Attribute attr = ( Attribute) ae.next() ;

   System.out.println(attr.getID());

   NamingEnumeration e = attr.getAll();

   While( e.hasMore())

   System.out.println( e.next());

}

}

}

 

搜索对象:

属性的一个强大且实用的功能就是允许我们搜索一个有明确属性名或者有具体属性值的名字。

Import javax.naming.*;

Import javax.naming.directory.*;

Public class JNDISearch

{

Private final static String JNDI = “ou=Customers, o=Agency, c=us”;

Public static void main ( String[] args )

{

Try

{

DirContext ctx= new InitialDirContext();

Attributes match = new BasicAttributes(true);

//match表示要查找的属性列表

Match.put( new BasicAttributr(“sn”));

Match.put( new BasicAttribute(“description”.”president”));

NamingEnumeration enum = ctx.search( JNDI, match) ;

While( enum.hasMore() )

{

SearchResult res= (SearchResult) enum.next();

System.out.println( res.getName()+”,”+JNDI );

}

}

Catch(NamingException ex)

{

System.out.println(ex);

System.exit(1);

}

}

}

 

操作属性:

DirContextModifyAttributes()支持名字属性的添加,修改,删除。

Import javax.naming.*;

Import javax.naming.directory.*;

Public class JNDIModify

{

Private final static String JNDI=”cn=Abraham, ou=Customers,0=Agency,c=us”;

Public static void main( String[] args)

{

Try

{

DirContext ctx= new InitialDirContext();

SearchControls sc= new SearchControls();

ModificatioinItem[] mods = new ModificationItem[2];

Modes[0] = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, new BasicAttributr(“description”,”Assasinated President”));

Modes[1] =new ModificationItem( DirContext.ADD_ATTRIBUTR, new BasicAtrribute(“seeAlso”,”cn=George, ou=Customers, o=Agency, c=us”));

Ctx.modifyAttributes(JNDI,mods);

//第二个参数mods定义了操作和要操作的Attribute对象列表

}

}

}

 

12 一个被JNDI绑定的对象必须实现Serializable接口并且JNDI服务器必须可以使用这个对象的类文件(可以通过设置服务器的class path来包含jar文件,也可以动态加载,从HTTP服务器中动态获取必要的类文件)

Public class Book implements Serializable

{

String title;

Public Book(String title)

This.title=title;

Publit String toString()

Return title;

}

要绑定和搜寻Book对象,Web服务器必须使得LDAP服务器提供者可以下载book.class文件。然后就可以使用Book对象和参数绑定,接着就可以通过lookup来查找到对象。

但有时候在目录服务中存储序列化的对象拷贝并不合适,可能对象太大,也可能会由于对象的构建随客户的不同而不同,所以必须对它进行动态实例化。JNDI引用提供了一种机制,它通过引用而不是值来存储对象,只有底层JNDI服务提供者支持Referenceable对象,这种机制才能正常工作,而对象引用需要使用一个工厂类从引用存储的信息中构造对象。

例如下面的引用类

Public class BookRef implements Referenceable          //使用这个新的BOOK类

{

String title;

Public BookRef(String title)

{ this.title= title;}

Public String toString(){return title;}

Public Reference getReference() throw NamingException

{

Return new Reference( BookRef.class.getName(), new StringRefAddr(“book”,title),  BookFactory.class.getName(),null);

//在这里可能调用了BookFactory的getObjectInstance方法

}

}

clip_image006

Public class BookFactory implements ObjectFactory

{

Public Object getObjectInstance(object obj, Name name, Context ctx, Hashtable env) throws Exception

{

If ( obj instanceof Reference)

{

  Reference ref=(Reference)obj;

  If ( ref.getClassName.equals(BookRef.class.getName())

  {

    RefAddr addr = ref.get( “book” );

    If ( addr !=null)

    Return new BookRef(String addr.getContent());

  }

}

Return null;

}

}

//工厂类的getObjectInstance方法检测是否有一个Reference对象传递过来,然后检查引用Reference对象的类是否为BookRef,如果两个条件都是真,那么工厂则使用地址类型book查找对象的值,并使用这个值构建一个新的BookRef对象。

clip_image008clip_image010

14 JNDI小结

JNDI为底层命名或者目录服务提供了统一的api. 命名服务提供了一种方法,用来存储简单的名字信息,这样可以用名字作为主键来检索信息,目录服务则还可以存储属性和相应的值,目录服务通过属性对名字进行分类,这样可以支持对目录树结构的强大搜索功能。

只要服务提供者实现可用,JNDI可以支持任何命名服务,JNDI支持的标准服务有

(1) LDAP

(2) NDS

(3) CORBA

Java中的JNDI使用比较简单,只需要创建一个环境然后再环境中寻找名字既可以。Context类支持命名服务,DirContext类支持目录服务。

定义一个环境后,使用lookup方法通过名字检索存储的对象,使用binf方法和rebind方法添加或者修改绑定对象,使用unbind方法删除一个绑定对象

在j2ee中,使用jndi发布下述组件:

(1) EJB

(2) 数据库

(3) JMS消息队列和主题

clip_image012