一、JNDI在Java EE中的應用
JNDI技術是Java EE規范中的一個重要“幕后”角色,它為Java EE容器、組件提供者和應用程序之間提供了橋梁作用:Java EE容器同時扮演JNDI提供者角色,組件提供者將某個服務的具體實現部署到容器上,應用程序通過標準的JNDI接口就可以從容器上發現并使用服務,而不用關心服務的具體實現是什么,它的具體位置在哪里。
下面以一個常見的J2EE應用場景來看四種角色(組件接口、容器、組件提供者、應用程序)是如何圍繞JNDI來發揮作用的:
組件接口
數據源DataSource是一種很常見的服務。我們通常將組件接口綁定到容器的Context上供客戶調用。
Java EE容器
Tomcat是一種常見的Java EE容器,其他的還有JBoss,WebLogic,它們同時也實現了JNDI提供者規范。容器通常提供一個JNDI注入場所供加入組件的具體實現,比如Tomcat中的Server.xml配置文件。
|
|
? 組件提供者
眾多數據庫廠商提供了DataSource的實現,比如OracleDataSource,MySQLDataSource,XXXDataSource等。我們將該實現的部署到容器中:將一系列jar加入classpath中,在Server.xml中配置DataSource實現,如:
<Resource name="jdbc/MyDB" auth="Container" type="javax.sql.DataSource" ..../>
應用程序
一個JSP/Servlet應用程序。通過JNDI接口使用DataSource服務,如:
Context initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/MyDB");
關于在Tomcat中如何配置DataSource,可以參考文檔:http://tomcat.apache.org/tomcat-5.5-doc/jndi-datasource-examples-howto.html。
關于在Tomcat中如何配置其他JNDI服務,可以參考文檔:http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html。
二、JNDI實例演練:在Java SE中使用JNDI
要在Java EE中環境中提供一個獨立的實例不太容易。下面以一個獨立的Java SE應用來演練JNDI的使用過程。在該應用中,我們使用JNDI來使用兩種假想的服務:數據庫服務DBService和日志服務LogService。
1、指定JNDI提供者
要使用JNDI,首先要配置JNDI提供者。在我們的Java SE應用中,沒有Java EE容器充當JNDI提供者,因此我們要指定其他的JNDI提供者。在我們的例子里,我們使用SUN的文件系統服務提供者File System Service Provider。由于SUN的文件系統服務提供者并沒有包含在JDK中,我們需要從SUN網站上下載:http://java.sun.com/products/jndi/downloads/index.html。它包含兩個jar文件:fscontext.jar和providerutil.jar。我們將這兩個文件加入項目的類路徑中。
2、定義服務接口
首先,我們在服務接口package(xyz.service)中定義服務接口:DBService和LogService,分別表示數據庫服務和日志服務。
數據庫服務接口 DBService.java
package xyz.service;
public interface DBService {
String getLocation(); //獲取數據庫位置
String getState(); //獲取數據庫狀態
void accessDB(); //訪問數據庫
void setProperty(int index,String property); //設置數據庫信息
}
日志服務接口 LogService.java
package xyz.service;
public interface LogService {
void log(String message); //記錄日志
}
3、組件提供者實現服務接口
接下來,我們在組件提供者package(xyz.serviceprovider)中提供DBService和LogService的實現:SimpleDBService和SimpleLogService。為了讓服務能夠在JNDI環境中使用,根據JNDI規范,我們同時定義兩個對象工廠類SimpleDBServiceFactory和SimpleLogServiceFactory,分別用來創建服務實例。
數據庫服務接口實現 SimpleDBService.java
package xyz.serviceprovider;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import xyz.service.DBService;
//為了將數據庫服務實例加入JNDI的Context中,我們需要實現Referenceable接口,并實現RetReference方法。
//關于Reference和Referenceable,請參考上一篇:Java技術回顧之JNDI:JNDI API
public class SimpleDBService implements Referenceable, DBService {
private String location="mydb//local:8421/defaultdb";//數據庫服務屬性之一:數據庫位置
private String state="start"; //數據庫服務屬性之二:數據庫狀態
public Reference getReference() throws NamingException {
//Reference是對象的引用,Context中存放的是Reference,為了從Reference中還原出對象實例,
//我們需要添加RefAddr,它們是創建對象實例的線索。在我們的SimpleDBService中,location和state是這樣兩個線索。
Reference ref=new Reference(getClass().getName(),SimpleDBServiceFactory.class.getName(),null);
ref.add(new StringRefAddr("location",location));
ref.add(new StringRefAddr("state",state));
return ref;
}
public void accessDB() {
if(state.equals("start"))
System.out.println("we are accessing DB.");
else
System.out.println("DB is not start.");
}
public String getLocation() {
return location;
}
public String getState() {
return state;
}
public void setProperty(int index,String property){
if(index==0)
location=property;
else
state=property;
}
}
數據庫服務對象工廠類 SimpleDBServiceFactory.java
package xyz.serviceprovider;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
//數據庫服務對象工廠類被JNDI提供者調用來創建數據庫服務實例,對使用JNDI的客戶不可見。
public class SimpleDBServiceFactory implements ObjectFactory {
//根據Reference中存儲的信息創建出SimpleDBService實例
public Object getObjectInstance(Object obj, Name name, Context ctx,
Hashtable<?, ?> env) throws Exception {
if(obj instanceof Reference){
Reference ref=(Reference)obj;
String location=(String)ref.get("location").getContent();
String state=(String)ref.get("state").getContent();
SimpleDBService db= new SimpleDBService();
db.setProperty(0, location);
db.setProperty(1, state);
return db;
}
return null;
}
}
日志服務接口實現 SimpleLogService.java
package xyz.serviceprovider;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import xyz.service.LogService;
public class SimpleLogService implements Referenceable, LogService {
private SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//SimpleLogService沒有任何屬性,通過SimpleLogService類名創建出來的SimpleLogService實例都是一樣的,
//因此這里無需添加RefAddr了。
public Reference getReference() throws NamingException {
return new Reference(getClass().getName(),SimpleLogServiceFactory.class.getName(),null);
}
public void log(String message) {
String date=sdf.format(new Date());
System.out.println(date+":"+message);
}
}
日志服務對象工廠類 SimpleLogServiceFactory.java
package xyz.serviceprovider;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
public class SimpleLogServiceFactory implements ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context ctx,
Hashtable<?, ?> env) throws Exception {
if(obj instanceof Reference){
return new SimpleLogService();
}
return null;
}
}
4、JNDI容器和JNDI客戶端
最后,我們在JNDI應用package(xyz.jndi)中實現一個JNDI容器JNDIContainer和一個JNDI客戶端應用JNDIClient。
JNDIContainer在內部使用文件系統服務提供者fscontext來提供命名和目錄服務,配置文件JNDIContainer.properties是服務注入場所,供配置DBService和LogService實現。
JNDI容器類 JNDIContainer.java
package xyz.jndi;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import xyz.service.DBService;
import xyz.service.LogService;
public class JNDIContainer {
private Context ctx=null;
public void init() throws Exception{
//初始化JNDI提供者。
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:/c:/sample"); //fscontext的初始目錄,我們需要在c:\下創建sample目錄。
ctx=new InitialContext(env);
loadServices();
}
//從配置文件JNDIContainer.properties中讀取DBService和LogService實現,綁定到Context中。
private void loadServices() throws Exception{
InputStream in=getClass().getResourceAsStream("JNDIContainer.properties");
Properties props=new Properties();
props.load(in);
//inject dbservice
String s=props.getProperty("DBServiceClass");
Object obj=Class.forName(s).newInstance();
if(obj instanceof DBService){
DBService db=(DBService)obj;
String[] ss=props.getProperty("DBServiceProperty").split(";");
for(int i=0;i<ss.length;i++)
db.setProperty(i, ss[i]);
ctx.rebind(props.getProperty("DBServiceName"), db);
}
//inject logservice
s=props.getProperty("LogServiceClass");
obj=Class.forName(s).newInstance();
if(obj instanceof LogService){
LogService log=(LogService)obj;
ctx.rebind(props.getProperty("LogServiceName"), log);
}
}
public void close() throws NamingException{
ctx.close();
}
public Context getContext(){
return ctx;
}
}
JNDI容器配置文件 JNDIContainer.properties
//和JNDIContainer.java文件位于同一目錄
DBServiceName=DBService
DBServiceClass=xyz.serviceprovider.SimpleDBService
DBServiceProperty=mydb//192.168.1.2:8421/testdb;start
LogServiceName=LogService
LogServiceClass=xyz.serviceprovider.SimpleLogService
JNDI客戶端 JNDIClient.java
package xyz.jndi;
import javax.naming.Context;
import xyz.service.DBService;
import xyz.service.LogService;
public class JNDIClient {
public static void main(String[] args){
try{
JNDIContainer container=new JNDIContainer();
container.init();
//JNDI客戶端使用標準JNDI接口訪問命名服務。
Context ctx=container.getContext();
DBService db=(DBService)ctx.lookup("DBService");
System.out.println("db location is:"+db.getLocation()+",state is:"+db.getState());
db.accessDB();
LogService ls=(LogService)ctx.lookup("LogService");
ls.log("this is a log message.");
container.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
至此,我們的整個Java SE應用已經完成。
|