天天看點

JNDI學習總結(一):JNDI到底是什麼?



JNDI是 Java 命名與目錄接口(Java Naming and Directory Interface),在J2EE規範中是重要的規範之一,不少專家認為,沒有透徹了解JNDI的意義和作用,就沒有真正掌握J2EE特别是EJB的知識。

那麼,JNDI到底起什麼作用?

要了解JNDI的作用,我們可以從“如果不用JNDI我們怎樣做?用了JNDI後我們又将怎樣做?”這個問題來探讨。

沒有JNDI的做法:

程式員開發時,知道要開發通路MySQL資料庫的應用,于是将一個對 MySQL JDBC 驅動程式類的引用進行了編碼,并通過使用适當的 JDBC URL 連接配接到資料庫。

就像以下代碼這樣:

Java code
Connection conn=null; 
try { 
	Class.forName("com.mysql.jdbc.Driver", true, Thread.currentThread().getContextClassLoader()); 
	conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=xxx&password=xxx"); 
	...... 
	conn.close(); 
} catch(Exception e) { 
	e.printStackTrace(); 
} finally { 
	if(conn!=null) { 
		try { 
			conn.close(); 
		} catch(SQLException e) {} 
	}
}
           

這是傳統的做法,也是以前非Java程式員(如Delphi、VB等)常見的做法。這種做法一般在小規模的開發過程中不會産生問題,隻要程式員熟悉Java語言、了解JDBC技術和MySQL,可以很快開發出相應的應用程式。

沒有JNDI的做法存在的問題:

1、資料庫伺服器名稱MyDBServer 、使用者名和密碼都可能需要改變,由此引發JDBC URL需要修改;

2、資料庫可能改用别的産品,如改用DB2或者Oracle,引發JDBC驅動程式包和類名需要修改;

3、随着實際使用終端的增加,原配置的連接配接池參數可能需要調整;

4、......

解決辦法:

程式員應該不需要關心“具體的資料庫背景是什麼?JDBC驅動程式是什麼?JDBC URL格式是什麼?通路資料庫的使用者名和密碼是什麼?”等等這些問題,程式員編寫的程式應該沒有對 JDBC 驅動程式的引用,沒有伺服器名稱,沒有使用者名稱或密碼 —— 甚至沒有資料庫池或連接配接管理。而是把這些問題交給J2EE容器來配置和管理,程式員隻需要對這些配置和管理進行引用即可。

由此,就有了JNDI。

用了JNDI之後的做法:

首先,在在J2EE容器中配置JNDI參數,定義一個資料源,也就是JDBC引用參數,給這個資料源設定一個名稱;然後,在程式中,通過資料源名稱引用資料源進而通路背景資料庫。

具體操作如下(以JBoss為例):

1、配置資料源

在JBoss 的 D:/jboss420GA/docs/examples/jca 檔案夾下面,有很多不同資料庫引用的資料源定義模闆。将其中的 mysql-ds.xml 檔案Copy到你使用的伺服器下,如 D:/jboss420GA/server/default/deploy。

修改 mysql-ds.xml 檔案的内容,使之能通過JDBC正确通路你的MySQL資料庫,如下:

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
  <jndi-name>MySqlDS</jndi-name>
  <connection-url>jdbc:mysql://localhost:3306/lw</connection-url>
  <driver-class>com.mysql.jdbc.Driver</driver-class>
  <user-name>root</user-name>
  <password>rootpassword</password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
  <metadata>
  <type-mapping>mySQL</type-mapping>
  </metadata>
</local-tx-datasource>
</datasources>
           

這裡,定義了一個名為MySqlDS的資料源,其參數包括JDBC的URL,驅動類名,使用者名及密碼等。

2、在程式中引用資料源:

Java code
Connection conn=null; 
try { 
	Context ctx = new InitialContext(); 
	Object datasourceRef = ctx.lookup("java:MySqlDS"); 
	//引用資料源 
	DataSource ds = (Datasource) datasourceRef; 
	conn = ds.getConnection(); 
	...... 
	c.close(); 
} catch(Exception e) { 
	e.printStackTrace(); 
} finally { 
	if(conn!=null) { 
		try { 
			conn.close(); 
		} catch(SQLException e) {} 
	} 
}
           

直接使用JDBC或者通過JNDI引用資料源的程式設計代碼量相差無幾,但是現在的程式可以不用關心具體JDBC參數了。

在系統部署後,如果資料庫的相關參數變更,隻需要重新配置 mysql-ds.xml 修改其中的JDBC參數,隻要保證資料源的名稱不變,那麼程式源代碼就無需修改。

由此可見,JNDI避免了程式與資料庫之間的緊耦合,使應用更加易于配置、易于部署。

總結:

J2EE 規範要求所有 J2EE 容器都要提供 JNDI 規範的實作。JNDI 在 J2EE 中的角色就是“交換機” —— J2EE 元件在運作時間接地查找其他元件、資源或服務的通用機制。在多數情況下,提供 JNDI 供應者的容器可以充當有限的資料存儲,這樣管理者就可以設定應用程式的執行屬性,并讓其他應用程式引用這些屬性(Java 管理擴充(Java Management Extensions,JMX)也可以用作這個目的)。JNDI 在 J2EE 應用程式中的主要角色就是提供間接層,這樣元件就可以發現所需要的資源,而不用了解這些間接性。

在 J2EE 中,JNDI 是把 J2EE 應用程式合在一起的粘合劑,JNDI 提供的間接尋址允許跨企業傳遞可伸縮的、功能強大且很靈活的應用程式。這是 J2EE 的承諾,而且經過一些計劃和預先考慮,這個承諾是完全可以實作的。

說白了就是把資源取個名字,再根據名字來找資源。

//=============================================================================================================== 首先我們來回顧一下簡單的問題,列在下面第一點。

1.我們知道,Java 的運作從 static main 開始,為什麼一定要從 static 方法開始呢?

2.在我們知道這個世界上的另外一個地方有一個對象存在而且伺服器也會在我們開始工作前為我們準備好,那麼我該怎麼找到它呢?如果這個對象是我這個類建立的,那麼當然簡單,直接用對象的引用就能調用它的方法,那如果這個對象不是我建立的,我想主動調用它的方法這似乎在任何程式設計語言中都不可能,記得寫一個方法那是被别人調用的不是主動調用别人。

就像你找人一樣,如果他還沒有和你建立聯系的話(建立聯系就是儲存一份對象的引用,如果兩個對象彼此沒有建立另外一個而且也沒有被中間的第三方建立這種關系問題就出現了),請問你如何和他打交道?

現實中是:

a. 我們撥通 114   

  Java 中:Context ctx = new InitialContext();

b. 請問哪裡有通馬桶的?114 答,xxx... 為您轉接中,請稍候。   

  Java 中:DataSource ds = (DataSource) ctx.lookup("便民服務公司");

c. 過了一會兒,人來了,你說:師付,請幫我通馬桶吧。

  Java 中: ds.getConnection();

上面的話,我沒有回答你什麼是 JNDI, 但是我回答了為什麼我們需要 JNDI. 希望你在概念上了解了它存在的必要性。

下面的話,給你一點指導如何更好的了解 JNDI 實作:

1.一個對象如果它在另外一個地方(可能與目前運作的程式不在同一個 VM / 同一程序中), 對象怎麼可能從一個 VM 中發送到另外一個 VM 中呢?像 LDAP 這種,對象的狀态還需要持久地儲存的話(重新開機伺服器程序後它還在),又該怎麼辦呢?請看 JNDI StateFactory, 它用一種方法把一個對象轉換成某種方式儲存下來,就像我們把一個 Entity 對象儲存下來時,我們會用 SQL 來做一樣。

2.有一個對象上次已經儲存了狀态,現在伺服器重新開機了,上次的對象肯定不在記憶體裡面,我們怎麼恢複上次的狀态呢?

請看 JNDI ObjectFactory. 它讀取一些上次儲存的狀态資訊,來建立并初始化一個對象。比如:我們配置了一個 XML,它是某個 JDBC 資料源的配置資料,Application Server 啟動時讀取這個資訊(相當于上次的狀态),然後重新開機對象。

3.企業應用這麼複雜,面向接口程式設計,那如何用一種簡單的方式來配置新的實作類呢?Java 的做法是:

已經定義了 SPI (Service Provider Interface). 包括以下幾點:

  接口準備好了,如:StateFactory / ObjectFactory.

  配置:先搜尋 JRE 下面的某個 jndiprovider.properties 檔案當作預設實作,再查找使用者 classpath 根路徑下 /jndi.properties. 另外還有 System.getProperties() 和在建立 InitialContext 給一個 hashtable 作為參數,這三個參數, 有優先級的關系,越是後面具體的參數優先級越高,越前面越通用型的參數優先級越低。這一點,請看 JDK ResourceManager 這個類的源碼。

  實作類與初始化它們是如何自動完成的呢?這個你需要看 Context 接口裡面的常量,以及拿 Sun LDAP InitialContextFactory 運作樣例來看 Context 接口的常量一個樣本參數值,一般我們很重要的是 InitialContextFactory 這個參數,但也有時候也有其他參數要配置,比如:pkgs, 它是說,我們給一個包名,JNDI 管理器要查找實作時用這個包名清單當成包名,類名就是 協定名 + 固定的字尾:比如: ldap://localhost:389, 它會用一個'包名字首.協定名.協定名 + URLContextFactory' 作為類名來搜尋一個類,如果它存在就把它當成實作類,如果沒找到再嘗試另外一個包名字首。你可以看 com.sun.jndi.url 名,下面有例子看,比如說 ldap:// 的情況就是 找一個類 com.sun.jndi.url.ldap.ldapURLContextFactory,如果是 dns://www.163.com/xxx 就找個 com.sun.jndi.url.dns.dnsURLContextFactory。這是 URL context factory 也就是當你使用 ctx.lookup("java:xxxx/yyy") 這種帶協定字首的時候。

另外你也可以類比地看 com.sun.www.protocol 包裡面的類,它是另外不一個與 JNDI 不相關的 URLStreamHandler 處理的規則,與些設計和配置幾乎完全相同。我以前寫過一個 jdbc:oracle:username/password:@localhost:1521:training/[select A from C where DEL_IND = 0] , 在 java 程式中輸入這個 URL 我們可以把資料庫裡面的資料讀取出來,效果就根你輸入 file:/C:/boot.ini 讀取了這個檔案内容一樣,辦法就是我寫了一個支援 jdbc 協定的 URLStreamHandler 在指令行配置一個使用它,其他的應用程式類就能自動處理,它們都不知道我是從資料庫裡面讀取的資料。

4. J2EE 1.3 開始,資源的管理由應用伺服器單獨來管理和配置,這與 J2EE 1.2 不同,在 J2EE 1.2 中我們直接在應用程式中配置我們要用的資源。J2EE 1.3 中我們配置一個資料源在伺服器上,我們在應用程式中隻需要說明我們配置的資源的引用就行了,比如我們隻在 web.xml 或 ejb-jar.xml 配置 <resource-ref /> 而不是 data source 本身。這有什麼好處?比如:我們定義了兩個 training 的資料源:jdbc/training/db2. jdbc/oracle/db2. 一個是開發環境,一個是 UAT 環境,現在開發時我們建立一個 <resource-ref /> 指向jdbc/training/db2,那麼就用 db2 資料庫,UAT 測試時我們建立另外一個 <resource-ref /> 指向 jdbc/training/oracle, 就會使用 oracle 資料庫,而這本身不需要修改代碼,隻是修改了 web.xml / ejb-jar.xml ,而且現在連接配接到資料的使用者名和密碼不再是應用程式開發本身的事情,因為你不需要配置資源也就不需要知道它的登入名和密碼,而是由管理者在伺服器上配置資料源,這裡注意,開發人員做他代碼部分的事情,伺服器管理者負責配置資源源,J2EE component provider 和 Deployer 兩個角色的職責分開了,雖然現實中 deployer 都是委托給了開發人員,但 J2EE 規範是分開來描述的。

5.上面說了半天,目的是什麼呢?這是我的痛苦經曆,第一次寫 EJB, 買了本書,J2EE 從入門到精通(就是那本傳說中的黃皮寶典系列),寫了一個無狀态 session bean 來通路資料源,死活找不到資料源:NamingException: xxx not found. 在 IBM developerworks 上看到一篇文章,茅塞頓開,原來那本書講的是J2EE 1.2, 我用的 WSAD 5.1.2 開發用的預設配置都是 J2EE 1.3。這裡面引出了 JNDI LinkRef, 為了實作上面 4 裡面所說的伺服器上配置一個資源,但應用程式裡面配置一個引用的話,現在的應用伺服器在處理這點JNDI技術實作上基本上都是用 LinkRef 來實作的,這是 JNDI 裡面的一個類。伺服器啟動時會建立一個 jdbc/training/db2 和 jdbc/training/oracle 兩個 DataSource 對象 (用的是 ObjectFactory), 當一個應用程式通路了準備通路資料源時,伺服器檢測到了 web.xml/ejb-jar.xml 中指定了 <resource-ref /> 它就會建立一個 LinkRef 放到 context 中去,它的名稱是:jdbc/training,但它的 ref 是 jdbc/training/db2.

這樣我們 ctx.lookup("java:jdbc/training") 時,java 協定對應的 javaURLContextFactory 會把這個 jdbc/training 對象找出來,在檢測到它是一個 LinkRef 對象時,會自動再用它的 ref 值(這裡是 jdbc/training/db2) 再 lookup 一遍,這下終于找到 jdbc/training/db2 這個 data source 對象。

6.JNDI 裡面還有其他的相關的東西。再結合一個 Reference 概念看,LinkRef 是繼承它的。想再具體的了解一個實作細節,請拿一份 apache commons-xxx.jar (名字我忘記了,不過用過 spring / hibernate 來建立資料源的人可能知道它們用 xxxDataSource 做一個不需要在伺服器上配置,但卻能使用 data source 的辦法),我不是推薦你這個 jar, 我是推薦你看這個xxxDataSource 源碼,裡面示範了一個 ObjectFactory 用法。這和 JMS ConnectionFactory等其他 J2EE 托管資源的配置和使用都是用的同樣的技術實作的。舉一反三。

想了解更,就再看一個 StateFactory 的實作以及 Reference 的源碼之類的。作為期望邁入 J2EE 中級程式設計的你,至少在概念和理論上要知道 ObjectFactory / LinkRef / SPI / resource ref 配置這幾點,如果你再知道 StateFactory 是怎麼實作的就更好了。

7.剩下的自學開源項目的源碼吧。雖然多花時間,但能舉一反三,不用老是 Google 那些灌水貼。

繼續閱讀