JNDI在Java EE中的应用
一、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服务,如:
ContextinitContext=newInitialContext();
ContextenvContext=(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(); //获取数据库状态
voidaccessDB();//访问数据库
voidsetProperty(intindex,Stringproperty);//设置数据库信息
}
日志服务接口 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;
importjavax.naming.Reference;
importjavax.naming.Referenceable;
import javax.naming.StringRefAddr;import xyz.service.DBService;
//为了将数据库服务实例加入JNDI的Context中,我们需要实现Referenceable接口,并实现RetReference方法。
//关于Reference和Referenceable,请参考上一篇:Java技术回顾之JNDI:JNDIAPI
publicclassSimpleDBServiceimplementsReferenceable,DBService{
privateStringlocation="mydb//local:8421/defaultdb";//数据库服务属性之一:数据库位置
private String state="start"; //数据库服务属性之二:数据库状态public Reference getReference() throws NamingException {
//Reference是对象的引用,Context中存放的是Reference,为了从Reference中还原出对象实例,
//我们需要添加RefAddr,它们是创建对象实例的线索。在我们的SimpleDBService中,location和state是这样两个线索。
Referenceref=newReference(getClass().getName(),SimpleDBServiceFactory.class.getName(),null);
ref.add(newStringRefAddr("location",location));
ref.add(newStringRefAddr("state",state));
returnref;
}public void accessDB() {
if(state.equals("start"))
System.out.println("weareaccessingDB.");
else
System.out.println("DBisnotstart.");
}public String getLocation() {
returnlocation;
}public String getState() {
returnstate;
}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;
importjavax.naming.Name;
importjavax.naming.Reference;
import javax.naming.spi.ObjectFactory;//数据库服务对象工厂类被JNDI提供者调用来创建数据库服务实例,对使用JNDI的客户不可见。
public class SimpleDBServiceF
actory implements ObjectFactory {//根据Reference中存储的信息创建出SimpleDBService实例
publicObjectgetObjectInstance(Objectobj,Namename,Contextctx,
Hashtable<?,?>env)throwsException{
if(objinstanceofReference){
Referenceref=(Reference)obj;
Stringlocation=(String)ref.get("location").getContent();
Stringstate=(String)ref.get("state").getContent();
SimpleDBServicedb=newSimpleDBService();
db.setProperty(0,location);
db.setProperty(1,state);
returndb;
}
returnnull;
}
}日志服务接口实现 SimpleLogService.java
package xyz.serviceprovider;
import java.text.SimpleDateFormat;import java.util.Date;
import javax.naming.NamingException;
importjavax.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了。
publicReferencegetReference()throwsNamingException{
returnnewReference(getClass().getName(),SimpleLogServiceFactory.class.getName(),null);
}public void log(String message) {
Stringdate=sdf.format(newDate());
System.out.println(date+":"+message);
}
}日志服务对象工厂类 SimpleLogServiceFactory.java
package xyz.serviceprovider;
import java.util.Hashtable;
import javax.naming.Context;
importjavax.naming.Name;
importjavax.naming.Reference;
import javax.naming.spi.ObjectFactory;public class SimpleLogServiceFactory implements ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context ctx,
Hashtable<?,?>env)throwsException{
if(objinstanceofReference){
returnnewSimpleLogService();
}
returnnull;
}
}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;
importjava.util.Hashtable;
import java.util.Properties;import javax.naming.Context;
importjavax.naming.InitialContext;
import javax.naming.NamingException;import xyz.service.DBService;import xyz.service.LogService;
public class JNDIContainer {
private Context ctx=null;
publicvoidinit()throwsException{
//初始化JNDI提供者。
Hashtableenv=newHashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL,"file:/c:/sample");//fscontext的初始目录,我们需要在c:\下创建sample目录。
ctx=newInitialContext(env);
loadServices();
}
//从配置文件JNDIContainer.properties中读取DBService和LogService实现,绑定到Context中。
privatevoidloadServices()throwsException{
InputStreamin=getClass().getResourceAsStream("JNDIContainer.properties");
Propertiesprops=newProperties();
props.load(in);
//injectdbservice
Strings=props.getProperty("DBServiceClass");
Objectobj=Class.forName(s).newInstance();
if(objinstanceofDBService){
DBServicedb=(DBService)obj;
String[]ss=props.getProperty("DBServiceProperty").split(";");
for(inti=0;i<ss.length;i++)
db.setProperty(i,ss[i]);
ctx.rebind(props.getProperty("DBServiceName"),db);
}
//injectlogservice
s=props.getProperty("LogServiceClass");
obj=Class.forName(s).newInstance();
if(objinstanceofLogService){
LogServicelog=(LogService)obj;
ctx.rebind(props.getProperty("LogServiceName"),log);
}
}
publicvoidclose()throwsNamingException{
ctx.close();
}
publicContextgetContext(){
returnctx;
}
}JNDI容器配置文件 JNDIContainer.properties
//和JNDIContainer.java文件位于同一目录
DBServiceName=DBService
DBServiceClass=xyz.serviceprovider.SimpleDBService
DBServiceProperty=mydb//192.168.1.2:8421/testdb;startLogServiceName=LogServiceLogServiceClass=xyz.serviceprovider.SimpleLogService
JNDI客户端 JNDIClient.java
package xyz.jndi;
import javax.naming.Context;
import xyz.service.DBService;import xyz.service.LogService;
public class JNDIClient {
publicstaticvoidmain(String[]args){
try{
JNDIContainercontainer=newJNDIContainer();
container.init();
//JNDI客户端使用标准JNDI接口访问命名服务。
Contextctx=container.getContext();
DBServicedb=(DBService)ctx.lookup("DBService");
System.out.println("dblocationis:"+db.getLocation()+",stateis:"+db.getState());
db.accessDB();
LogServicels=(LogService)ctx.lookup("LogService");
ls.log("thisisalogmessage.");
container.close();
}
catch(Exceptione){
e.printStackTrace();
}
}
}至此,我们的整个Java SE应用已经完成。下面是整个应用的源代码结构(Eclipse工程):