JNDI基礎
一 簡介
1.JNDI:Java Naming and Directory Interface,即Java命名和目錄接口。JNDI包含了一些標准API接口,Java程序可以通過這些接口來訪問命名目錄服務。JNDI不依賴於任何獨立的命名目錄服務器,不管采用哪種命名目錄服務器,應用程序都可以通過統一的JNDI接口來調用。要使用JNDI,必須要安裝jdk 1.3以上版本。
2.命名服務:就是將名字和計算機系統內的一個對象建立關聯,從而允許應用程序通過該名字訪問該對象。簡而言之,命名服務就是為計算機系統內的對象起名字。
例如在Internet上的域名服務 (domain naming service,DNS) 就是提供將域名映射到IP地址的命名服務,在打開網站時一般都是 在瀏覽器中輸入名字,通過DNS找到相應的IP地址,然后打開。所有的因特網通信都使用TCP、UDP或IP協議。IP地址由4個字節32位二進制數字組成,數字和名字相比,對於人來說名字比數字要容易記憶,但對於計算機來講,它更善於處理數字。
其實所有的命名服務都提供DNS這種基本功能,即一個系統向命名服務注冊,命名服務提供一個值到另一個值的映射。然后,另外一個系統訪問命名服務就可以取得映射信息。這種交互關系對分布式企業級應用來講顯得非常重要。
3.目錄服務:目錄服務是命名服務的拓展,目錄服務不僅需要保存名稱和對象的關聯,還要保存對象的各種屬性,這樣就允許開發者操作對象的屬性,包括增刪改查對象的屬性。在目錄服務中,你可以根據屬性搜索對象。JNDI允許你訪問文件系統中的文件,定位遠程RMI注冊的對象,訪問象LDAP這樣的目錄服務,定位網絡上的EJB組件。從我們日常生活中去理解目錄服務的概念可以從電話簿說起,電話簿本身就是一個比較典型的目錄服務,如果你要找到某個人的電話號碼,你需要從電話簿里找到這個人的名稱,然后再看其電話號碼。
4.JNDI結構


JNDI結構包括JNDI API和JNDI SPI
開發者通過JNDI API以一致的方式來訪問各種命名服務、目錄服務,而JNDI API則保證各種命名服務、目錄服務透明的加入JNDI結構中,Naming Manager則負責管理二者之間的轉換。。
在開發企業級應用時,JNDI顯得尤其重要:客戶端代碼可以通過JNDI來訪問EJB,客戶端代碼需要通過JNDI來訪問容器管理的數據源......Java EE應用中所有遠程對象都需要通過JNDI來訪問。
二 使用JNDI配置數據源
1.數據源的由來
在Java開發中,使用JDBC操作數據庫的四個步驟如下:
加載數據庫驅動程序(Class.forName("數據庫驅動類");)
連接數據庫(Connection con = DriverManager.getConnection();)
操作數據庫(PreparedStatement stat = con.prepareStatement(sql);stat.executeQuery();)
關閉數據庫,釋放連接(con.close();)
也就是說,所有的用戶都需要經過此四步進行操作,但是這四步之中有三步(加載數據庫驅動程序、連接數據庫、關閉數據庫,釋放連接)對所有人都是一樣的,而所有人只有在操作數據庫上是不一樣,那么這就造成了性能的損耗。
那么最好的做法是,准備出一個空間,此空間里專門保存着全部的數據庫連接,以后用戶用數據庫操作的時候不用再重新加載驅動、連接數據庫之類的,而直接從此空間中取走連接,關閉的時候直接把連接放回到此空間之中。
那么此空間就可以稱為連接池(保存所有的數據庫連接),但是如果要想實現此空間的話,則必須有一個問題要考慮?
如果沒有任何一個用戶使用連接,那么那么應該維持一定數量的連接,等待用戶使用。
如果連接已經滿了,則必須打開新的連接,供更多用戶使用。
如果一個服務器就只能有100個連接,那么如果有第101個人過來呢?應該等待其他用戶釋放連接
如果一個用戶等待時間太長了,則應該告訴用戶,操作是失敗的。
直接用程序實現以上功能,則會比較麻煩,所以在Tomcat 4.1.27之后,在服務器上就直接增加了數據源的配置選項, 直接在服務器上配置好數據源連接池即可。在J2EE服務器上保存着一個數據庫的多個連接。每一個連接通過DataSource可以找到。DataSource被綁定在了JNDI樹上(為每一個DataSource提供一個名字)客戶端通過名稱找到在JNDI樹上綁定的DataSource,再由DataSource找到一個連接。如下圖所示:


那么在以后的操作中,除了數據庫的連接方式不一樣之外,其他的所有操作都一樣,只是關閉的時候不是徹底地關閉數據庫,而是把數據庫的連接放回到連接池中去。
2.在tomcat中配置JNDI
主要包括三種方式:web.xml context.xml server.xml
這三種的區別是,server.xml與context.xml類似都是所有應用通用的,但是context.xml只是把它分離出來單獨形成了一個文件而已。在WEB-INF/下的context.xml則是應用自己的,所以如果不想把某些信息公開,放在這里就可以了。
(1)server.xml方式
這種方式設置的是全局JNDI配置,在server.xml下配置你必需重啟服務器才能生效,而context.xml配置保存后tomcat會自動加載無需重啟。
Step1:在tomcat服務器的lib目錄下加入數據庫連接的驅動jar包,如
mysql-connector-java-5.1.27.jar -->mySql數據庫的jar包
ojdbc14.jar -->Oracle數據庫的jar包
sqljdbc4.jar -->SQLServer數據庫的jar包
Step2:
(1)server.xml方式
修改tomcat服務器的conf目錄下的server.xml配置文件
在server.xml配置文件中有一個自帶的全局JNDI配置,如圖:


在server.xml中添加全局JNDI數據源配置,在<GlobalNamingResources>下繼續添加<Resource>標簽,常見的幾個數據庫的配置如下所示:
<!--配置Oracle數據庫的JNDI數據源-->
<Resource
name="jdbc/oracle"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="lead_oams"
password="p"
driverClassName="oracle.jdbc.driver.OracleDriver"
url="jdbc:oracle:thin:@192.168.1.229:1521:lead"/>
<!--配置MySQL數據庫的JNDI數據源-->
<Resource
name="jdbc/mysql"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="root"
password="root"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://192.168.1.144:3306/leadtest?useUnicode=true&characterEncoding=utf-8"/>
<!--配置SQLServer數據庫的JNDI數據源-->
<Resource
name="jdbc/sqlserver"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="sa"
password="p@ssw0rd"
driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
url="jdbc:sqlserver://192.168.1.51:1433;DatabaseName=demo"/>
其中,name:表示以后要查找的名稱。通過此名稱可以找到DataSource,此名稱任意更換,但是程序中最終要查找的就是此名稱,為了不與其他的名稱混淆,所以使用jdbc/oracle,現在配置的是一個jdbc的關於oracle的命名服務。
auth:由容器進行授權及管理,指的用戶名和密碼是否可以在容器上生效
type:此名稱所代表的類型,現在為javax.sql.DataSource
maxActive:表示一個數據庫在此服務器上所能打開的最大連接數
maxIdle:表示一個數據庫在此服務器上維持的最小連接數
maxWait:最大等待時間。10000毫秒
(2)context.xml方式
選擇你所要連接的數據庫,打開comcat下的config里的context.xml,將<resource>下的內容復制過去
<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<Resource
name="jdbc/mysql"
auth="Container"
driverClassName="com.mysql.jdbc.Driver"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="root"
password="123456"
url="jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8"/>
</Context>
Step3:測試
在項目的WEB-INF的web.xml文件中添加JNDI配置的資源引用:
<!--Oracle數據庫JNDI數據源引用 -->
<resource-ref>
<description>Oracle DB Connection</description>
<res-ref-name>oracleDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<!--MySQL數據庫JNDI數據源引用 -->
<resource-ref>
<description>MySQL DB Connection</description>
<res-ref-name>mysqlDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<!--SQLServer數據庫JNDI數據源引用 -->
<resource-ref>
<description>SQLServer DB Connection</description>
<res-ref-name>sqlserverDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
JNDI配置的資源引用包括:
res-ref-name:表示引用資源的名稱
res-type:此資源對應的類型為javax.sql.DataSource
res-auth:容器授權管理
(2)編寫測試內容
<%@ page import="java.sql.*,javax.sql.*,javax.naming.*" %>
<%
Connection conn=null;
try
{
//初始化查找命名空間
Context ctx = new InitialContext();
//InitialContext ctx = new InitialContext();亦可
//找到DataSource,對名稱進行定位java:comp/env是必須加的,后面跟你的DataSource名
DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/mysql");
//取出連接
conn = ds.getConnection();
System.out.println("connection pool connected !!");
} catch (NamingException e) {
System.out.println(e.getMessage());
} catch (SQLException e) {
e.printStackTrace();
}finally {
if(conn!=null) {
try {
conn.close();
} catch(SQLException e) { }
}
}
%>
報錯:org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot create JDBC driver of class '' for connect URL 'null'
三 JNDI常用操作
1.JNDI架構提供了一組標准命名系統的API,這些API在JDK1.3之前是作為一個單獨的擴展包jndi.jar(通過這個地址下載),這個基礎API構建在與SPI之上。這個API提供如下五個包javax.naming,javax.naming.directory,javax.naming.event,javax.naming.ldap,javax.naming.spi
2.通過JNDI來訪問被綁定對象要按一下操作:
(1)創建Context對象
(2)調用Context的lookup方法根據JNDI名稱查找被綁定對象;或者調用bind方法來執行綁定;或者調用unbind方法來解除綁定...就是調用Context的方法來執行綁定、查找等操作。
(3)關閉Context
3.Context只是一個接口,通常會使用它的實現類InitialContext來創建實例。InitialContext提供了兩個構造器:
InitialContext():讀取系統屬性作為Context屬性來創建InitialContext
InitialContext(Hashtable<?,?> environment):以environment參數指定的屬性作為Context屬性來創建InitialContext
如果創建InitialContext對象時沒有傳入任何參數,那么它必須能從系統屬性(System.getProperties()方法返回值)中讀到合適的Context屬性來執行初始化,否則它將拋出NoInitialContextException異常。
在JSP頁面中執行以下代碼:
<%
Properties props=System.getProperties();
for(String name:props.stringPropertyNames()){
out.println(name+"--->"+props.getProperty(name)+"<br/>");
}
%>
會看到如下的結果:


由此可見,當在服務器環境下的Web應用中創建InitialContext時,由於服務器啟動時已經添加了它所需的系統屬性,因此直接創建InitialContext()就可以了。
Hashtable至少包含如下兩個key:
java.naming.factory.initial:可用Context內的INITIA_CONTEXT_FACTORY常量代替,該key的值應該為初始化Context的工廠類。
java.naming.providor.url:可用Context內的PROVIDER_URL常量代替,該key的值應該為Context服務提供者的URL
4.舉例
以文件系統的JNDI為例,創建如下InitialContext對象:
public class myTest {
public static void main(String[] args) throws NamingException {
final String fileName="lyy.doc";
Hashtable env=new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"org.apache.naming.java.javaURLContextFactory");
env.put(Context.PROVIDER_URL, "file:/d:/wscite");
Context ctx=new InitialContext(env);
Object file=ctx.lookup(fileName);
System.out.println(fileName+"名稱被綁定到:"+file);
ctx.close();
}
}
報錯:java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory
打開window->Preferences->你所用的jdk版本->edit->add external jars->選擇tomact包里的bin的tomcat-juli.jar->添加
上面的程序將d:/wscite目錄作為一個Context,然后以此Context來查找對應的名稱。對於文件系統的JNDI來說,每個文件夾相當於一個Context,每個文件名相當於一個JNDI名,而文件對象則是實際被綁定的對象。
方法:
(1)查找對象
通過Context提供的lookup(jndi)方法來實現,該方法接受被綁定的JNDI名,返回與之綁定的對象
該方法只能返回一個Object類型的對象,因此要注意強制類型轉換
(2)綁定
JNDI通過Context的bind(String name,Object obj)方法來執行綁定,第一個參數是被綁定的JNDI名,第二個參數是被綁定的對象
該方法就相當於為obj對象起了一個name
(3)重新綁定
rebind(String name,Object obj),如果該名稱已被綁定,則是修改,如果沒綁定就與bind功能相同。