JNDI 筆記(一) 概述,jndi筆記概述
很多地方都會用到JNDI,一大堆的縮寫加上一大堆不清不楚的概念描述,使得在看到的時候都不認識,更不要說使用了。
JNDI,Java Naming Directory Interface,J2EE的標准之一,所有的J2EE容器都必須提供一個JNDI的服務,但是,我一直都沒用過,至少是沒有刻意地去用過。因為,我也 曾經把數據源配置在Tomcat的JNDI服務中,但那時,我也只是剛剛涉足JAVA,有人告訴我應該這麼干而已。後來,我開始使用自定義的數據庫連接配 置文件,就再也沒有跟JNDI打過交道了,直到今天。
現在之所以又想看一下,只是因為覺得這是J2EE的重要標准之一,如果不懂得的話,似乎有點說不過去。
JNDI
的主要功能可以這樣描述,它使用一張哈希表存儲對象(大多數的J2EE容器也的確是這樣做的),然後,開發人員可以使用鍵值——也就是一個字符串——來獲
取這個對象。這裡就包括取JNDI的兩個最主要操作,bind和lookup。bind操作負責往哈希表裡存對象,存對象的時候要定義好對象的鍵值字符
串,lookup則根據這個鍵值字符串往外取對象。
JNDI的命稱可能會讓人產生混淆,似乎覺得這是一個用來操作目錄的,事實上,我更願
意把這個目錄理解成為JNDI存放對象時使用的格式,也就是說,JNDI以目錄的方式存儲對象的屬性。例如,用戶通過JNDI存儲一個汽車對象,那麼,汽
車就是根目錄,汽車的輪子、引擎之類的子對象就算是子目錄,而屬性,比如說汽車的牌子、重量之類,就算是汽車目錄下的文件。
JNDI的功能既然就是根據一個字符串鍵值就可以取得一個想要得到的對象,我一開始就覺得這不是跟COM或CORBA一樣嗎?SUN也是有野心的企業啊,JNDI應該就是它要努力推行的JAVA下的分布式開發的標准吧。
JNDI
的出現應該就是為了分步式開發服務的,有人負責開發這種分布式對象,有人只需要使用這些分布式對象就可以了,這兩組人不必屬於同一個公司,而且這種開發通
常應該是不並行的,也不必是會了同一個項目服務。就如果數據源對象,它放在JNDI中,只要想要用的人,直接通過JNDI服務取來用就可以了,至於當初是
誰把它放進JNDI中的,還是不用操這份心了吧。而我一直沒有使用JNDI,也就是這個原因,項目中的所有對象都在我控制之下,我不去使用別人的對象,也
沒打算把我的對象貢獻出來給別人使用,那自然也就沒必要去跟JNDI打交道。我覺得是否使用JNDI,這應該是關鍵原因,至於什麼方便性、安全性之類的考
慮,應該不是JNDI的主要目的,就如同你可以用JAVA來做網站,但JAVA並不是專門用來做網站的。
可能有人覺得這種功能跟IoC也
很象,這個我倒不覺得,雖然對於對象的使用人員來說的確是這種感覺,且不說IoC需要為對象定義接口,而JNDI並無此限制,先說這裡有一個使用環境問
題,我覺得IoC是用來解決並行開發問題的,也就是說IoC主要是用於明確設計人員與實現/使用人員的分工,無論是設計的,還是使用的,通常是一個項目組
裡的人,使用IoC,可以使得設計人員專注於設計,加快設計速度。因此,IoC的用途要比JNDI廣泛的多,現在大型系統中,不使用IoC的,幾稀矣。
JNDI 筆記(二) J2EE下使用JNDI
在J2EE環境下使用JNDI是非常簡單的事,因為所有的J2EE容器都要實現JNDI服務,所以,在J2EE環境下使用JNDI,與使用
Hashtable也沒有什麼太大區別。只有一點限制,那就是綁定對象時,對象所屬的類必須實現java.io.Serializable接口,這一點也
實在一點也不困難,幾乎所有用到的Java類都實現了這個接口,對於自定義的類,在接口實現列表裡把這個接口加進去也就是了。
下面,我將演示一下如何在J2EE環境下使用JNDI,為了保證代碼的通用性,我不使用struts之類的框架,而是直接使用標准JSP和Servlet實現。我將該項目的名稱定為jndi_test
要使用JNDI,需要先到SUN的網站上去下載jndi.jar。
2.1 JSP
本項目包括5個JSP,功能說明如下:
- index.jsp:首頁
- bind.jsp:用於在JNDI中綁定對象
- bind_result.jsp:綁定對象後的返回頁面
- lookup.jsp:用於在JNDI中檢索對象
- lookup_result.jsp:用於顯示檢索對象
本節中用到的JSP代碼如下,代碼都簡單地很,就不多做解釋了。
2.1.1 index.jsp
>
bind an object
</
a
>
>
lookup the binded object
</
a
>
</
body
>
</
html
>
2.1.5 lookup_result.jsp
<%
@ page language
=
"
java
"
contentType
=
"
text/html; charset=GB18030
"
pageEncoding
=
"
GB18030
"
%>
<!
DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"
>
<
html
>
<
head
>
<
meta
http-equiv
="Content-Type"
content
="text/html; charset=GB18030"
>
<
title
>
JNDI Test - Lookup result
</
title
>
</
head
>
<
body
>
<%
Object
o
=
request.getAttribute(
"
found_jndi_obj
"
);
out.println(o);
%>
</
body
>
</
html
>
2.2 Servlet
本例包括兩個Servlet,功能說明如下:
- BindServlet:用於在JNDI服務中綁定一個對象
- LookupServlet:用於在JNDI服務中取出一個對象
2.2.1 BindServlet.java
package
lld.test.jndi;
import
java.io.IOException;
import
java.util.Date;
import
javax.naming.Context;
import
javax.naming.InitialContext;
import
javax.servlet.RequestDispatcher;
import
javax.servlet.ServletContext;
import
javax.servlet.ServletException;
import
javax.servlet.http.
*
;
public
class
BindServlet
extends
HttpServlet
{
private
static
final
long
serialVersionUID
=
5219969790998794367L
;
@Override
protected
void
doGet(HttpServletRequest req, HttpServletResponse resp)
throws
ServletException, IOException
{
this
.doPost(req, resp);
}
@Override
protected
void
doPost(HttpServletRequest req, HttpServletResponse resp)
throws
ServletException, IOException
{
try
{
Context jndi_ctx
=
new
InitialContext();
String key
=
"
jndi_object
"
;
jndi_ctx.rebind(key,
new
Date());
}
catch
(Exception ex)
{
ex.printStackTrace();
}
ServletContext context
=
this
.getServletContext();
RequestDispatcher dispatcher
=
context.getRequestDispatcher(
"
/bind_result.jsp
"
);
dispatcher.forward(req, resp);
}
}
使用rebind而不是bind綁定對象是因為,使用bind時,如果已經有對象綁定到該鍵值上,則會拋出異常。
因為只是示例代碼,所以我只是綁定了一個最簡單的日期對象。
2.2.2 LookupServlet.java
package
lld.test.jndi;
import
java.io.IOException;
import
javax.naming.Context;
import
javax.naming.InitialContext;
import
javax.servlet.RequestDispatcher;
import
javax.servlet.ServletContext;
import
javax.servlet.ServletException;
import
javax.servlet.http.HttpServlet;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
public
class
LookupServlet
extends
HttpServlet
{
private
static
final
long
serialVersionUID
=
6677219828267184673L
;
@Override
protected
void
doGet(HttpServletRequest req, HttpServletResponse resp)
throws
ServletException, IOException
{
this
.doPost(req, resp);
}
@Override
protected
void
doPost(HttpServletRequest req, HttpServletResponse resp)
throws
ServletException, IOException
{
try
{
Context jndi_ctx
=
new
InitialContext();
String key
=
"
jndi_object
"
;
Object o
=
jndi_ctx.lookup(key);
req.setAttribute(
"
found_jndi_obj
"
, o);
}
catch
(Exception ex)
{
ex.printStackTrace();
}
ServletContext context
=
this
.getServletContext();
RequestDispatcher dispatcher
=
context.getRequestDispatcher(
"
/lookup_result.jsp
"
);
dispatcher.forward(req, resp);
}
}
2.3 web.xml
在web.xml中,加入了servlet映射
<?
xml version="1.0" encoding="UTF-8"
?>
<
web-app
id
="WebApp_ID"
version
="2.4"
xmlns
="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
>
<
display-name
>
jndi_test
</
display-name
>
<
servlet
>
<
servlet-name
>
BindServlet
</
servlet-name
>
<
servlet-class
>
lld.test.jndi.BindServlet
</
servlet-class
>
</
servlet
>
<
servlet-mapping
>
<
servlet-name
>
BindServlet
</
servlet-name
>
<
url-pattern
>
/bind.do
</
url-pattern
>
</
servlet-mapping
>
<
servlet
>
<
servlet-name
>
LookupServlet
</
servlet-name
>
<
servlet-class
>
lld.test.jndi.LookupServlet
</
servlet-class
>
</
servlet
>
<
servlet-mapping
>
<
servlet-name
>
LookupServlet
</
servlet-name
>
<
url-pattern
>
/lookup.do
</
url-pattern
>
</
servlet-mapping
>
<
welcome-file-list
>
<
welcome-file
>
index.jsp
</
welcome-file
>
</
welcome-file-list
>
</
web-app
>
OK,所有的代碼都在這裡了,部署到Tomcat下運行即可。
JNDI 筆記(三) J2SE下使用JNDI
在J2SE下使用JNDI下就顯得困難一些,首先,我們沒有單獨的JNDI服務器可以用,JBoss提供了一個免費的JNP服務,通過配置可以作為
單獨的JNDI服務器啟用。不過這裡就不這麼麻煩了,如何使用JBOSS作為JNDI服務器,以後將單獨撰文講述,這裡我使用sun提供的
com.sun.jndi.fscontext.RefFSContextFactory作為JNDI服務器,其實這是使用文件系統來存儲JNDI對象。
至於如何存儲後文還將專門描述。
為了在J2SE下使用JNDI,我們首先得到sun的網站上下載3個包,jndi.jar、fscontext.jar和providerutil.jar,前者提供了JNDI服務的接口,後兩者是我們要使用的文件系統作為JNDI服務器的支持包。
使用RefFSContextFactory,要求綁定的對象必須實現javax.naming.Referencable接口,否則在綁定時將報如下錯誤:
Can only bind References or Referenceable objects
各個JDBC驅動提供商提供的DataSource類都實現了Referencable接口,可以直接使用。不過本著學習的態度,我還是在這裡演示一下如何實現Referencable接口。
這個如何實現將在後文結合代碼詳細介紹。本例包括4個類,說明如下:
- BindedClass:自定義的實現Referenceable接口的類
- BindedClassFactory:工廠類,能夠把一個Reference對象轉換為BindedClass對象
- Bind:測試類,用於在JNDI中綁定對象
- Loopup:測試類,用於從JNDI中獲取對象
3.1 BindedClass和BindedClassFactory
3.1.1 BindedClass
package
lld.test.jndi;
import
javax.naming.NamingException;
import
javax.naming.Reference;
import
javax.naming.Referenceable;
import
javax.naming.StringRefAddr;
public
class
BindedClass
implements
Referenceable
{
public
String value;
public
BindedClass()
{
}
@Override
public
Reference getReference()
throws
NamingException
{
Reference r
=
new
Reference(
this
.getClass().getName(), BindedClassFactory.
class
.getName(),
null
);
r.add(
new
StringRefAddr(
"
value
"
,
this
.getValue()));
return
r;
}
public
String getValue()
{
return
value;
}
public
void
setValue(String value)
{
this
.value
=
value;
}
}
3.1.2 BindedClassFactory
package
lld.test.jndi;
import
java.util.Hashtable;
import
javax.naming.
*
;
import
javax.naming.spi.
*
;
public
class
BindedClassFactory
implements
ObjectFactory
{
@Override
public
Object getObjectInstance(Object obj, Name name, Context nameCtx,