1. 程式人生 > >JNDI

JNDI

號碼 throw gree true 所有組 false oracl ado 姓名

鏈接:http://javacrazyer.iteye.com/blog/759485

在DataSource中事先建立多個數據庫連接,保存在數據庫連接池中。當程序訪問數據庫時,只用從連接池中取空閑狀態的數據庫連接即可,

訪問結束,銷毀資源,數據庫連接重新回到連接池,這與每次去直接訪問數據庫相比,會節省大量時間和資源。

JNDI( Java Naming and Directory Interface ),是Java平臺的一個標準擴展,提供了一組接口、類和關於命名空間的概念。如同其它很多Java技術一樣,JDNI是provider-based的技術,暴露了一個 API和一個服務供應接口(SPI)。這意味著任何基於名字的技術都能通過JNDI而提供服務,只要JNDI支持這項技術。JNDI目前所支持的技術包括 LDAP、CORBA Common Object Service(COS)名字服務、RMI、NDS、DNS、Windows註冊表等等。很多J2EE技術,包括EJB都依靠JNDI來組織和定位實體。可以把它理解為一種將對象和名字捆綁的技術,對象工廠負責生產出對象,這些對象都和唯一的名字綁在一起,外部資源可以通過名字獲得某對象的引用。

在javax.naming的包包中提供Context接口,提供了兩個很好用的方法:
<1> void bind( String name , Object object )
將名稱綁定到對象。所有中間上下文和目標上下文(由該名稱最終原子組件以外的其他所有組件指定)都必須已經存在。
<2>Object lookup( String name )
檢索指定的對象。如果 name為空,則返回此上下文的一個新實例(該實例表示與此上下文相同的命名上下文,但其環境可以獨立地進行修改,而且可以並發訪問)。

運行機制:
1、 首先程序代碼獲取初始化的 JNDI 環境並且調用 Context.lookup() 方法從 JNDI 服務提供者那裏獲一個 DataSource 對象

2、 中間層 JNDI 服務提供者返回一個 DataSource 對象給當前的 Java 應用程序這個 DataSource 對象代表了中間層服務上現存的緩沖數據源

3、 應用程序調用 DataSource 對象的 getConnection() 方法

4、 當 DataSource 對象的 getConnection() 方法被調用時,中間層服務器將查詢數據庫 連接緩沖池中有沒有 PooledConnection 接口的實例對象。這個 PooledConnection 對象將被用於與數據庫建立物理上的數據庫連接

5、 如果在緩沖池中命中了一個 PooledCoonection 對象那麽連接緩沖池將簡單地更 新內部的緩沖連接隊列並將該 PooledConnection 對象返回。如果在緩沖池內沒 有找到現成的 PooledConnection 對象,那麽 ConnectionPoolDataSource 接口將會被 用來產生一個新的 PooledConnection 對象並將它返回以便應用程序使用

6。 中間層服務器調用 PooledConnection 對象的 getConnection() 方法以便返還一個 java.sql.Connection 對象給當前的 Java 應用程序

7、 當中間層服務器調用 PooledConnection 對象的 getConnection() 方法時, JDBC 數據 庫驅動程序將會創建一個 Connection 對象並且把它返回中間層服務器

8、 中間層服務器將 Connection 對象返回給應用程序 Java 應用程序,可以認為這個 Connection 對象是一個普通的 JDBC Connection 對象使用它可以和數據庫建立。事 實上的連接與數據庫引擎產生交互操作 。

9、 當應用程序不需要使用 Connection 對象時,可以調用 Connection 接口的 close() 方 法。請註意這種情況下 close() 方法並沒有關閉事實上的數據庫連接,僅僅是釋 放了被應用程序占用的數據庫連接,並將它還給數據庫連接緩沖池,數據庫連接 緩沖池會自動將這個數據庫連接交給請求隊列中下一個的應用程序使用。

現在,數據庫的連接沒有用到連接池幾乎很少很少,每個項目組都可能有自己的數據庫連接池組件,各容器提供商也提供了各自的數據庫連接池,下面介紹一下tomcat的數據庫連接管理。
tomcat6 數據源配置(server.xml方式和context.xml方式)

在server.xml下配置你必需重啟服務器才能生效,而context.xml配置保存後tomcat會自動加載無需重啟
在JNDI配配置數據源中需註意:項目下需要引入數據庫驅動包,並且TOMCAT下也需要引入,不然會報錯的
1.context.xml方式
Tomcat-6.0.26\conf\context.xml文件當添加以下的配置信息:
<Resource name="jdbc/mysql" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="60" wait_timeout="18800" timeBetweenEvictionRunsMillis="300000" minEvictableIdleTimeMillis="600000"
username="root" password="jdzxdb" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/sxtele?comautoReconnect=true&amp;failOverReadOnly=false" removeAbandoned="true" removeAbandonedTimeout="60" logAbandoned="true"/>
<Resource name="jdbc/db2" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="60" wait_timeout="18800" timeBetweenEvictionRunsMillis="300000" minEvictableIdleTimeMillis="600000"
username="lcgluser" password="lcgluser" driverClassName="com.ibm.db2.jcc.DB2Driver"
url="jdbc:db2://133.64.46.65:50000/STEDWDB" removeAbandoned="true" removeAbandonedTimeout="60" logAbandoned="true"/>
其中:
name 表示指定的jndi名稱
auth 表示認證方式,一般為Container
type 表示數據源床型,使用標準的javax.sql.DataSource
maxActive 表示連接池當中最大的數據庫連接
maxIdle 表示最大的空閑連接數
maxWait 當池的數據庫連接已經被占用的時候,最大等待時間
logAbandoned 表示被丟棄的數據庫連接是否做記錄,以便跟蹤
username 表示數據庫用戶名
password 表示數據庫用戶的密碼
driverClassName 表示JDBC DRIVER
url 表示數據庫URL地址

註意,這裏你配置的name值要和程序中使用的是一樣的,比如按照這個例子,程序就應該是這樣的

Java代碼
String gENV = "java:comp/env/";
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx .lookup(gENV+"jdbc/mysql");
Connection conn = ds.getConnection();

String gENV = "java:comp/env/";
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup(gENV+"jdbc/db2");
Connection conn = ds.getConnection();

關於獲取數據源的語法,大體有(javax.sql.DataSource) ctx.lookup ("java:comp/env/XXX")和(javax.sql.DataSource) ctx.lookup("XXX")兩種寫法,好多人以為這兩種寫法是相同的,以為都是通過JNDI來獲取數據源。其實java:comp/env和 JNDI是不同的,java:comp/env 是環境命名上下文(environment naming context(ENC)),是在EJB規範1.1以後引入的,引入這個是為了解決原來JNDI查找所引起的沖突問題,也是為了提高EJB或者J2EE應用的移植性。ENC是一個引用,引用是用於定位企業應用程序的外部資源的邏輯名。引用是在應用程序部署描述符文件中定義的。在部署時,引用被綁定到目標可操作環境中資源的物理位置(JNDI名)。使用ENC是把對其它資源的JNDI查找的硬編碼解脫出來,通過配置這個引用可以在不修改代碼的情況下,將引用指向不同的EJB(JNDI)。 在J2EE中的引用常用的有:
---------JDBC 數據源引用在java:comp/env/jdbc 子上下文中聲明
---------JMS 連接工廠在java:comp/env/jms 子上下文中聲明
---------JavaMail 連接工廠在java:comp/env/mail 子上下文中聲明
---------URL 連接工廠在 java:comp/env/url子上下文中聲明


2.在server.xml文件中配置數據源
Tomcat-6.0.26\conf\context.xml中的配置:在<Host></Host>中添加如下配置
<Context path="/sljx" reloadable="true" docBase="sljx" crossContext="true"
debug="0">
<Resource
name="jdbc/oracle"
type="javax.sql.DataSource"
auth="Container"
username="sljx"
password="sljx"
driverClassName="oracle.jdbc.driver.OracleDriver"
maxIdle="3000"
maxWait="5000"
url="jdbc:oracle:thin:@localhost:1521:jadetrus"
autoReconnect="true"
maxActive="1000"/>
</Context>
具體屬性解釋和JAVA獲取數據源跟上面一樣

3.在hibernate中配置JNDI也很簡單在hibernate.cfg.xml中
<property name="connection.datasource">java:/comp/env/jdbc/mysql</property>
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>


常用獲取示例
package com.cn.util.db;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DBCache {
private InitialContext ic;

private Map<String,DataSource> cache;

private static DBCache instance = new DBCache();

private DBCache() {
try {
ic = new InitialContext();
} catch (NamingException e) {
e.printStackTrace();
}
cache = Collections.synchronizedMap(new HashMap<String,DataSource>());
}

public static DBCache getInstance() {
return instance;
}


public Connection getDBConn(String dataSourceName) throws SQLException,
NamingException {
Connection conn = null;
conn=getDataSource(dataSourceName).getConnection();
return conn;
}

public DataSource getDataSource(String dataSourceName)
throws NamingException {

if (cache.containsKey(dataSourceName)) {
return cache.get(dataSourceName);
} else {

DataSource newDataSource = (DataSource) ic.lookup(dataSourceName);
cache.put(dataSourceName, newDataSource);
return newDataSource;
}

}
public void removeDateSouceOffCache(String dataSourceName) {
try {
DataSource ds = cache.get(dataSourceName);
ds.getConnection().close();
cache.remove(dataSourceName);
} catch (Exception ex) {
}
}


public static void freeConn(Connection conn) {
freeConn(conn, null, null);
}

public static void freeConn(Connection connection, Statement statement) {
freeConn(connection, statement, null);
}


public static void freeConn(Connection conn, Statement ps, ResultSet rs) {
try {
try {
if (rs != null)
rs.close();
} finally {
try {
if (ps != null)
ps.close();
} finally {
if (conn != null)
conn.close();
}
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}

如果獲取連接調用getDBConn傳入下面兩值中任意一個即可
public static final String DATA_SOURCE = "java:comp/env/jdbc/localhostMysql";
public static final String DATA_SOURCE_DB2 = "java:comp/env/jdbc/db2";
private static final String DATA_SOURCE="java:comp/env/jdbc/oracle";

關於JNDI的更多用法
基於JNDI的應用開發
JNDI(The Java Naming and Directory Interface,Java命名和目錄接口)是一組在Java應用中訪問命名和目錄服務的API.命名服務將名稱和對象聯系起來,使得我們可以用名稱訪問對象。目錄服務是一種命名服務,在這種服務裏,對象不但有名稱,還有屬性。

命名或目錄服務使你可以集中存儲共有信息,這一點在網絡應用中是重要的,因為這使得這樣的應用更協調、更容易管理。例如,可以將打印機設置存儲在目錄服務中,以便被與打印機有關的應用使用。


JNDI概述
我們大家每天都不知不覺地使用了命名服務。例如,當你在web瀏覽器輸入URL,http://java.sun.com時,DNS(Domain Name System,域名系統)將這個符號URL名轉換成通訊標識(IP地址)。命名系統中的對象可以是DNS記錄中的名稱、應用服務器中的EJB組件(Enterprise JavaBeans Component)、LDAP(Lightweight Directory Access Protocol)中的用戶Profile.

目錄服務是命名服務的自然擴展。兩者之間的關鍵差別是目錄服務中對象可以有屬性(例如,用戶有email地址),而命名服務中對象沒有屬性。因此,在目錄服務中,你可以根據屬性搜索對象。JNDI允許你訪問文件系統中的文件,定位遠程RMI註冊的對象,訪問象LDAP這樣的目錄服務,定位網絡上的EJB組件。

對於象LDAP 客戶端、應用launcher、類瀏覽器、網絡管理實用程序,甚至地址薄這樣的應用來說,JNDI是一個很好的選擇。

JNDI架構

JNDI架構提供了一組標準的獨立於命名系統的API,這些API構建在與命名系統有關的驅動之上。這一層有助於將應用與實際數據源分離,因此不管應用訪問的是LDAP、RMI、DNS、還是其他的目錄服務。換句話說,JNDI獨立於目錄服務的具體實現,只要你有目錄的服務提供接口(或驅動),你就可以使用目錄。如圖1所示。 圖1:JNDI架構

關於JNDI要註意的重要一點是,它提供了應用編程接口(application programming interface,API)和服務提供者接口(service provider interface,SPI)。這一點的真正含義是,要讓你的應用與命名服務或目錄服務交互,必須有這個服務的JNDI服務提供者,這正是JNDI SPI發揮作用的地方。服務提供者基本上是一組類,這些類為各種具體的命名和目錄服務實現了JNDI接口?很象JDBC驅動為各種具體的數據庫系統實現了JDBC接口一樣。作為一個應用開發者,你不必操心JNDI SPI.你只需要確認你要使用的每一個命名或目錄服務都有服務提供者。

J2SE和JNDI

Java 2 SDK 1.3及以上的版本包含了JNDI.對於JDK 1.1和1.2也有一個標準的擴展。Java 2 SDK 1.4.x的最新版本包括了幾個增強和下面的命名/目錄服務提供者:

l LDAP(Lightweight Directory Access Protocol)服務提供者 l CORBA COS(Common Object Request Broker Architecture Common Object Services)命名服務提供者 l RMI(Java Remote Method Invocation)註冊服務提供者 l DNS(Domain Name System)服務提供者

更多的服務提供者

可以在如下網址找到可以下載的服務提供者列表:

http://java.sun.com/products/jndi/serviceproviders.html 特別有意思的或許是如下網址提供的Windows 註冊表JNDI服務提供者:http://cogentlogic.com/cocoon/CogentLogicCorporation/JNDI.xml 這個服務提供者使你可以訪問Windows XP/2000/NT/Me/9x的windows註冊表。

也可以在如下網址下載JNDI/LDAP Booster Pack:http://java.sun.com/products/jndi/ 這個Booster Pack包含了對流行的LDAP控制的支持和擴展。它代替了與LDAP 1.2.1服務提供者捆綁在一起的booster pack.關於控制和擴展的更多信息可以在如下網站看到: http://java.sun.com/products/jndi/tutorial/ldap/ext/index.html 另一個有趣的服務提供者是Sun的支持DSML v2.0(Directory Service Markup Language,目錄服務標記語言)的服務提供者。DSML的目的是在目錄服務和XML之間架起一座橋梁。

JNDI API

JNDI API由5個包組成:

l Javax.naming:包含了訪問命名服務的類和接口。例如,它定義了Context接口,這是命名服務執行查詢的入口。 l Javax.naming.directory:對命名包的擴充,提供了訪問目錄服務的類和接口。例如,它為屬性增加了新的類,提供了表示目錄上下文的DirContext接口,定義了檢查和更新目錄對象的屬性的方法。 l Javax.naming.event:提供了對訪問命名和目錄服務時的時間通知的支持。例如,定義了NamingEvent類,這個類用來表示命名/目錄服務產生的事件,定義了偵聽NamingEvents的NamingListener接口。 l Javax.naming.ldap:這個包提供了對LDAP 版本3擴充的操作和控制的支持,通用包javax.naming.directory沒有包含這些操作和控制。 l Javax.naming.spi:這個包提供了一個方法,通過javax.naming和有關包動態增加對訪問命名和目錄服務的支持。這個包是為有興趣創建服務提供者的開發者提供的。

JNDI 上下文

正如在前面提到的,命名服務將名稱和對象聯系起來。這種聯系稱之為綁定(binding)。一組這樣的綁定稱之為上下文(context),上下文提供了解析(即返回對象的查找操作)。其他操作包括:名稱的綁定和取消綁定,列出綁定的名稱。註意到一個上下文對象的名稱可以綁定到有同樣的命名約定的另一個上下文對象。這稱之為子上下文。例如,如果UNIX中目錄/home是一個上下文,那麽相對於這個目錄的子目錄就是子上下文?例如,/home/guests中guests就是home的子上下文。在JNDI中,上下文用接口javax.naming.Context表示,這個接口是與命名服務交互的關鍵接口。在Context(或稍後討論的DirContext)接口中的每一個命名方法都有兩種重載形式:

Lookup(String name):接受串名 l
Lookup(javax.naming.Name):接受結構名,
例如,CompositeName(跨越了多個命名系統的名稱)或CompondName(單個命名系統中的名稱);它們都實現了Name接口。Compound name的一個例子是:cn=mydir,cn=Q Mahmoud,ou=People,composite name的一個例子是:cn=mydir,cn=Q Mahmoud,ou=People/myfiles/max.txt(這裏,myfiles/max.txt是表示第二部分的文件名)

Javax.naming.InitialContext是實現了Context接口的類。用這個類作為命名服務的入口。為了創建InitialContext對象,構造器以java.util.Hashtable或者是其子類(例如,Properties)的形式設置一組屬性。下面給出了一個例子:

Hashtable env = new Hashtable(); // select a service provider factory env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContext"); // create the initial context Context contxt = new InitialContext(env);

INITIAL_CONTEXT_FACTORY指定了JNDI服務提供者中工廠類(factory class)的名稱。Factory負責為其服務創建適當的InitialContext對象。在上面的代碼片斷中,為文件系統服務提供者指定了工廠類。表1給出了所支持的服務提供者的工廠類。要註意的是文件系統服務提供者的工廠類需要從Sun公司單獨下載,J2SE 1.4.x沒有包含這些類。

表1:上下文INITIAL_CONTEXT_FACTORY的值 Name Service Provider Factory
File System com.sun.jndi.fscontext.RefFSContextFactory
LDAP com.sun.jndi.ldap.LdapCtxFactory
RMI com.sun.jndi.rmi.registry.RegistryContextFactory
CORBA com.sun.jndi.cosnaming.CNCtxFactory
DNS com.sun.jndi.dns.DnsContextFactory

為了用名稱從命名服務或目錄中取得或解析對象,使用Context的lookup方法:Object obj=contxt.lookup(name)。Lookup方法返回一個對象,這個對象表示的是你想要找的上下文的兒子。
----------------------------------------------------------------------------------------------------------------------------
對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());
}

參考資料:
http://java.sun.com/products/jndi/examples.html
http://java.sun.com/products/jndi/serviceproviders.html

常用的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),獲取目錄對象的操作就是查找;過濾器是類似查找條件的對象。

基本使用

2 註冊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);

資料:
jndi學習筆記 http://hankun.blogbus.com/logs/2005/12/1774694.html
[精華] JNDI http://www.chinaunix.net/jh/26/30421.html

JNDI