JNDI原理

什麼是JNDI?爲什麼使用JNDI?

JNDI是Java 命名與目錄接口(Java Naming and Directory Interface)

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

沒有JNDI的做法:
程序員開發時,知道要開發訪問MySQL數據庫的應用,於是將一個對 MySQL JDBC 驅動程序類的引用進行了編碼,並通過使用適當的 JDBC URL 連接到數據庫。
就像以下代碼這樣:

Connection conn=null;
try {
   Class.forName("com.mysql.jdbc.Driver",true, Thread.currentThread().getContextClassLoader());        conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue");  /* 使用conn並進行SQL操作 */     conn.close();
} catch(Exception e) {
   e.printStackTrace();
} finally {
   if(conn!=null) {
    try {       conn.close();   
    } catch(SQLException e) {
    }
}}
這是傳統的做法,這種做法一般在小規模的開發過程中不會產生問題,只要程序員熟悉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、在程序中引用數據源:

Connection conn=null;
try { 
Context ctx=new InitialContext();
Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用數據源 
DataSource ds=(Datasource)datasourceRef;  conn=ds.getConnection();  /* 使用conn進行數據庫SQL操作 */
...... 
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 中的資源並不侷限於 JDBC 數據源。引用的類型有很多,其中包括資源引用(已經討論過)、環境實體和 EJB 引用。特別是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一項關鍵角色:查找其他應用程序組件。

 

JNDI原理

sun只是提供了JNDI的接口(即規範),IBM, Novell, Sun 和 WebLogic 和JBOSS已經爲 JNDI 提供了服務提供程序,

在JNDI中,在目錄結構中的每一個結點稱爲context。每一個JNDI名字都是相對於context的。這裏沒有絕對名字的概念存在。對一個應用來說,它可以通過使用 InitialContext 類來得到其第一個context: 

    Context ctx = new InitialContext();

    ctx.bind("name", Object);

    ctx.lookup("name");

Context:上下文,我的理解是相當與文件系統的中的目錄(JNDI的Naming Service是可以用操作系統的文件系統的,哈哈).

entry/object:一個節點,相當與文件系統中的目錄或文件.

filter:查詢/過濾條件是一個字符串表達式如:(&(objectClass=top)(cn=*))查詢出objectClass屬性爲top,cn屬性爲所有情況的entry.

Attribute:entry/object的屬性可以理解成JAVA對象的屬性,不同的是這個屬性可以多次賦值.

A.將接口分爲Context 和 DirContext  

   JNDI有兩個核心接口Context和DirContext,Context中包含 了基本的名字操作,而DirContext則將這些操作擴展到目錄服務。DirContext 對Context進行了擴展,提供了基本的目錄服務操作, 對名字對象屬性的維護、基於屬性的名字查找等等。  

B.上下文列表的多種方法  

   一般來說有兩種進行上下文列表的應用:上下文瀏覽應用和對上下文中的對象進行實際操作的應用。  

   上下文瀏覽應用一般只需要顯示上下文中包含內容的名字,或者再獲取一些諸如對象的類型之類的信息。這種類型的應用一般都是交互式的,可以允許用戶在列舉的上下文列表中選擇一些進行進一步的顯示。  

   另外有一些應用需要對上下文中的對象進行實際的操作,比如,一個備份程序需要對目錄中所有文件的狀態進行操作,或者某打印機管理員可能需要對大樓中的所有打印機進行復位。爲了進行這樣的操作,程序需要獲取上下文中的實際對象。  

   對於這樣兩種類型的應用,Context接口提供了兩種上下文列表方法list()和 listBindings()。其中list()只返回一系列名字/類映射,而listBindings() 則返回名字、類和對象本身。顯然 list()用於上下文瀏覽應用而listBindings()用於那些需要對對象進行實際操作的應用。  
例:
=================將以下代碼段添加到server.xml中的<Host>中============
<!-- configure DataSource. Add the following code into server.xml -->

<Context path="/bookstore" docBase="bookstore" debug="0"
reloadable="true" >

<!-- 數據源名稱 -->
<Resource name="jdbc/BookDB"
               auth="Container"
               type="javax.sql.DataSource" />

<ResourceParams name="jdbc/BookDB">
    <parameter>
      <name>factory</name>
      <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
    </parameter>

    <!-- Maximum number of dB connections in pool. Make sure you
         configure your mysqld max_connections large enough to handle
         all of your db connections. Set to 0 for no limit.
         -->
<!-- 活動狀態最大連接數 -->
    <parameter>
      <name>maxActive</name>
      <value>100</value>
    </parameter>

    <!-- Maximum number of idle dB connections to retain in pool.
         Set to 0 for no limit.
         -->
<!-- 空閒狀態數據庫連接最大數 -->
    <parameter>
      <name>maxIdle</name>
      <value>30</value>
    </parameter>

    <!-- Maximum time to wait for a dB connection to become available
         in ms, in this example 10 seconds. An Exception is thrown if
         this timeout is exceeded. Set to -1 to wait indefinitely.
        Maximum time to wait for a dB connection to become available
         in ms, in this example 10 seconds. An Exception is thrown if
         this timeout is exceeded. Set to -1 to wait indefinitely.
         -->
<!-- 數據庫處於空閒狀態的最長時間 -->
    <parameter>
      <name>maxWait</name>
      <value>10000</value>
    </parameter>

    <!-- MySQL dB username and password for dB connections -->
<!-- 指定連接數據庫的用戶名及密碼 -->
    <parameter>
     <name>username</name>
     <value>dbuser</value>
    </parameter>
    <parameter>
     <name>password</name>
     <value>1234</value>
    </parameter>

    <!-- Class name for mm.mysql JDBC driver -->
<!-- 指定JDBC驅動 -->
    <parameter>
       <name>driverClassName</name>
       <value>com.mysql.jdbc.Driver</value>
    </parameter>

    <!-- The JDBC connection url for connecting to your MySQL dB.
         The autoReconnect=true argument to the url makes sure that the
         mm.mysql JDBC Driver will automatically reconnect if mysqld closed the
         connection. mysqld by default closes idle connections after 8 hours.
         -->
<!-- 指定連接數據庫的URL -->
    <parameter>
      <name>url</name>
      <value>jdbc:mysql://localhost:3306/BookDB?autoReconnect=true</value>
    </parameter>
</ResourceParams>

</Context> 
運行機制:
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() 方法並沒有關閉事實上的數據庫連接,僅僅是釋 放了被應用程序佔用的數據庫連接,並將它還給數據庫連接緩衝池,數據庫連接 緩衝池會自動將這個數據庫連接交給請求隊列中下一個的應用程序使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章