一、JNDI
1.1 JNDI 的客户端开发简介
JNDI 的客户端开发包括下列步骤:
1.创建初始化上下文(IntialContext)。它相当于打开文件浏览器,就像要找文件先要打开Windows 上的“我的电脑”一样。对本地客户端(例如同一台服务器上的EJB 或者Web层的Servlet 中),可以直接初始化上下文:
Context ctx = new IntialContext();
对任意客户端的话,则需要设置一些初始化的参数:
Hashtable ht = new Hashtable();
// 设置工厂类(指定SPI),不同的服务器取值不同
ht.put
(Context.INITIAL_CONTEXT_FACTORY, ”org.jnp.interfaces.NamingContextFactory”);
// 指定要连接的服务器地址(相当于JDBC 的URL)
ht.put(Context.PROVIDER_URL,” jnp://localhost”);//如果要加端口,地址为:jnp://host:port
// 可以设置服务器的用户名和密码(也和JDBC 类似)
//ht.put(Context.SECURITY_PRINCIPAL,”user”);
//ht.put(Context.SECURITY_CREDENTIALS,”password”);
Context ctx = new InitialContext (ht);
当然编写这一步的时候需要确保放入了驱动类。不同的服务器,驱动程序类(Context
Factory)和URL 都是不一样的。
然而这样要写很多的代码才可以,另有一种办法就是在类路径根目录下放置配置文件
jndi.properties,文件代码清单如下:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=jnp://localhost
此时也可以直接编写代码:Context ctx = new IntialContext();来创建初始化上下文。
2.创建上下文(类似于创建目录)。代码如下:
ctx.createSubcontext("jdbc");//这相当于在C 盘创建了个子目录
如需创建多层目录结构,那么还可以继续这样创建:
ctx.createSubcontext("jdbc/mysql");
结构相当于下列层次结构:
/jdbc/mysql/
。既然有创建上下文,自然也就有删除上下文,对应的代码是:
ctx.destroySubcontext("jdbc/mysql");
注意:目前JBoss 并不支持这一销毁的操作,调用会报错:not supported。
3.绑定对象。因为对象不能凭空产生,需要服务器或者用户创建后,然后绑定到服务器
(相当于上传文件到服务器上),之后才能被访问和使用。JBoss 绑定也是这样做的, 它会
启动时首先创建EJB 等对象,然后绑定 EJB 等资源。被绑定的类唯一的要求就是实现
java.io.Serializable 接口。示意代码如下:
ctx.rebind("jdbc/MyName", "BeanSoft 刘长炯");// 我们可以绑定一个数据源对象等其它可序列化的对象
还有一个方法名为bind(),其主要区别是rebind()可以重复绑定(或者说更新老对象),而
bind()只能调用一次。
4.查找对象。这一步相当于从服务器下载文件,或者说读取文件,代码如下所示:
// 查找 JNDI 上的对象,
Object myName = ctx.lookup("jdbc/MyName");
// 打印读取的值
System.out.println(myName);
5.从服务器删除对象。这一步的操作叫unbind,取消绑定,相当于从服务器上删除文
件,后果就是服务器上指定位置的对象被删掉,不能再次访问到。代码如下:
ctx.unbind("jdbc/MyName");
6.关闭上下文。这一步相当于断开到服务器的连接,注意数据仍然在服务器上存在,可
以在下次连接时再次取到。即使服务器关闭了,这些数据也应该可以通过下次启动时加载。
ctx.close();// 关闭
1.2 如何查看 JBoss 服务器的JNDI 树
首先自然是需要启动 JBoss 服务器了,单独启动或者在MyEclipse 中启动均可,然后
用任意浏览器打开地址:
http://localhost:8080/jmx-console/HtmlAdaptor,这一步将打开JBoss的控制台,然后找到
页面中的下列内容:
jboss
• database=localDB,service=Hypersonic
• name=PropertyEditorManager,type=Service
• name=SystemProperties,type=Service
• readonly=true,service=invoker,target=Naming,type=http
• service=AttributePersistenceService
• service=ClientUserTransaction
• service=JNDIView
点击链接 service=JNDIView,打开如下地址:
http://localhost:8080/jmx-console/HtmlAdaptor?action=inspectMBean&name=jboss%3As
ervice%3DJNDIView,此时即可看到JBoss的JNDI的管理Bean,再找到页面中的List of
MBean operations:一栏,如图16.21 所示,点击Invoke按钮后即可看到JNDI列表了。
JBoss 的JNDI 大致分为三类。第一类是各个应用自带的局部JNDI , 例如
JBossWS.war:
java:comp namespace of the JBossWS.war application:
第二类是服务器提供的服务资源,例如数据源,事务管理器等等,这些内容只能通过
服务器配置,用户不可自行添加和修改,而且只能在服务器内部访问,不可远程访问(例如
可以在同一服务器的JSP 或者EJB 中访问,严格的说不能跨JVM 或者跨网访问)。
java: Namespace
+- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
+- DefaultDS (class: org.jboss.resource.adapter.jdbc.WrapperDataSource)
+- SecurityProxyFactory (class: org.jboss.security.SubjectSecurityProxyFactory)
+- DefaultJMSProvider (class: org.jboss.jms.jndi.JNDIProviderAdapter)
+- comp (class: javax.naming.Context)
这些对象位于java:下的命名空间中,例如下面的数据源DefaultDS 可以这样访问:
访问的时候代码应该这样写:lookup(“java:/DefaultDS”) 或者lookup(“java:DefaultDS”) ;
第三类是服务器提供的全局JNDI 资源,这些内容包括各种EJB,JMS 服务等等,用
户可以向其中加入自己的JNDI 内容。我们开发的EJB 和JNDI 测试都位于这下面,例如:
Global JNDI Namespace
+- TopicConnectionFactory (class: org.jboss.naming.LinkRefPair)
1.3 JNDI客户端开发实例
实例一:
1. 向JBoss JNDI 加入简单对象和自己的对象;
2. 从JSP 中访问JNDI;
3. 使用接口的方式访问JNDI。
首先让我们创建一个普通的Java 项目,名为JBossJNDITest,接着,请读者将文件
$JBOSS_HOME/client/jbossall-client.jar(文件大小4.7MB,包含了所有的远程访问
JBoss 服务所需的Java 类库)在Libraries 中通过Add External Jar 加入到项目的 BuildPath(相当于 CLASSPATH) 中,或者将其复制到项目目录中,然后点击右键加入BuildPath。
package jndi;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class JNDIBindTest {
InitialContext ctx ; //初始化Context对象
// 初始化上下文对象
void initJNDI() throws NamingException{
Hashtable properties = new Hashtable();
//配置驱动程序,JBoss特定
properties.put(Context.INITIAL_CONTEXT_FACTORY,"org.jnp.interfaces.NamingContextFactory");
properties.put(Context.PROVIDER_URL, "jnp://localhost");
ctx = new InitialContext(properties);
}
//关闭JNDI
void closeJNDI() throws NamingException{
ctx.close();
}
//创建层次结构
void createContext() throws NamingException{
ctx.createSubcontext("liangbinny"); //先创建liangbinny层,才能创建其下面的子层次,相当于文件夹
ctx.createSubcontext("liangbinny/stringtest");
}
//删除层次结构
void deleteContext() throws NamingException{
ctx.destroySubcontext("liangbinny/stringtest"); //先删除子层次,再删除上一层次,相当于先删除子文件夹,然后再删除上一级文件夹
ctx.destroySubcontext("liangbinny");
}
//绑定字符串
void bindString() throws NamingException{
ctx.rebind("liangbinny/stringtest/MyName", "wenbin"); //bind只绑定一次,rebind可绑定多次
}
void lookupString() throws NamingException{
Object myName = ctx.lookup("liangbinny/stringtest/MyName");
System.out.println(myName);
}
// 取消 JNDI 绑定, 相当于删除文件
void unbindString() throws NamingException{
ctx.unbind("liangbinny/stringtest/MyName");
}
void stringTest() throws NamingException{
initJNDI();
createContext();
bindString();
lookupString();
// unbindString();
// deleteContext();
closeJNDI();
}
public JNDIBindTest(){
try {
stringTest();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new JNDIBindTest();
}
}
,这段内容是从lookupString()这个方法中执行得到的。然而,此时我们查看JBoss 的JNDI
树,会看到全局JNDI 下面多出了下面所示的内容:
http://localhost:8080/jmx-console/HtmlAdaptor
。那么从此以后,这个对象就放在了JBoss 服务器上了,但是如果只是这样存放的数据,
关闭服务器后下次再打开,它们就消失了,具体错误为:
javax.naming.NameNotFoundException:liangbinny not bound(一般来说JBoss 是通过配置文件的方式来确保每次启动时都会绑定对象)。不过,在本次服务器未重启时,数据是一直可以看到的,那么第二次运行测试代码访问时,就不需要再次绑定数据就可以读取到了,此时的测试方法应修改为:
void stringTest() throws NamingException {
initJNDI();
lookupString();
closeJNDI();
}
读者可以试试第二次运行,采用这样的几个方法,包括打开,读取和关闭,同样可以读到数
据。
实例一:
保持上面服务器存放数据不变的情况下,从 Web 程序中访问JNDI 数据,很简单,创建一个Web 项目名为JBossJNDIWeb,然后修改其index.jsp 文件内容为:
<%@ page language="java" pageEncoding="GBK"%>
<%@page import="javax.naming.InitialContext"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>JNDI Web 访问测试</title>
</head>
<body>
<%
InitialContext ctx = new InitialContext();
Object name = ctx.lookup("liangbinny/stringtest/MyName"); //不需要创建上下文就可以访问了,因为JBoss下面现在已经有这个上下文了。
out.println(name);
%>
</body>
</html>
。此处没有设置上下文参数就可创建InitialContext 是因为默认在JBoss 中它连向JBoss 自
带的JNDI 服务,Context 工厂类采用JBoss 自带值,URL 则指向当前所在的JSP 程序所
在服务器。反过来如果需要连接到别的服务器,或者是用Tomcat 来作为客户端访问远程的
JNDI 数据,就需要采用那种设置初始化参数的方式了。
最后,我们要测试的是这样一种场景。假设我们和一家客户通过JNDI 提供服务,我们
编写了一个接口,告诉他们提供了什么样的服务,但是,我们不希望他们知道我们的核心技
术,在这种情况下,自然不能给他们实现类的源代码了(再次只能模拟EJB+客户端的一
种情况)。那么客户只需要拿到接口和JNDI 的地址,就可以调用此服务了,而类的源代码
它们是拿不到的,那么,我们分两部分来开发。首先看提供服务的人,我们,需要开发一个
接口和一个类,这个服务假设就是做汇率计算,从美元转换成人民币。继续刚才开发的项目
JBossJNDITest,新建两个类。
接口 bean.Calculator 代码清单如下:
package bean;
public interface Calculator {
double dollarToRMB(double input);
}
package bean;
import java.io.Serializable;
public class CalculatorImpl implements Calculator,Serializable{
public CalculatorImpl() {
}
public double dollarToRMB(double input) {
return input * 7.0026;
}
}
<%@ page language="java" pageEncoding="GBK"%>
<%@page import="javax.naming.InitialContext"%>
<%@page import="bean.CalculatorImpl"%>
<%@page import="bean.Calculator"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>JNDI Web 访问测试</title>
</head>
<body>
<%
InitialContext ctx = new InitialContext();
Calculator calc = (Calculator)ctx.lookup("Calculator/remote"); //不需要创建上下文就可以访问了,因为JBoss下面现在已经有这个上下文了。
out.println(calc.dollarToRMB(1.23));
%>
</body>
</html>
随 后 , 发 布 应 用 到 JBoss 服务器后,在浏览器中键入地址
http://localhost:8080/JBossJNDIWeb/calculator.jsp 即可测试,输出的结果应是:
8.613198
。读者需要注意上面的代码形式,可以看到JNDI 时可以完全不用关心实现类的名字,只需
要转换为接口即可。而JNDI 的对象地址Calculator/remote 实际上是我们自己通过JNDI
提供的创建Context 功能和绑定功能完成的,实际上JBoss 发布EJB 时,也会采用类似的
调用过程,本节内容就是为了帮助大家理解即将出现的JBoss EJB 客户端代码的访问方式
的。
1.4 JNDI 访问数据源
1、 找到JBoss 的安装目录下的docs/examples/jca 中找到配置文件oracle -ds,将其内容修改为如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!-- ===================================================================== -->
<!-- -->
<!-- JBoss Server Configuration -->
<!-- -->
<!-- ===================================================================== -->
<!-- $Id: oracle-ds.xml 63175 2007-05-21 16:26:06Z rrajesh $ -->
<!-- ==================================================================== -->
<!-- Datasource config for Oracle originally from Steven Coy -->
<!-- ==================================================================== -->
<datasources>
<local-tx-datasource>
<jndi-name>OracleDS</jndi-name>+
<use-java-context>false</use-java-context>
<connection-url>jdbc:oracle:thin:@10.200.25.151:1521:boss20dev</connection-url>
<!--
Here are a couple of the possible OCI configurations.
For more information, see http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/java.920/a96654/toc.htm
<connection-url>jdbc:oracle:oci:@youroracle-tns-name</connection-url>
or
<connection-url>jdbc:oracle:oci:@(description=(address=(host=youroraclehost)(protocol=tcp)(port=1521))(connect_data=(SERVICE_NAME=yourservicename)))</connection-url>
Clearly, its better to have TNS set up properly.
-->
<driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
<user-name>common</user-name>
<password>common</password>
<!-- Uses the pingDatabase method to check a connection is still valid before handing it out from the pool -->
<!--valid-connection-checker-class-name>org.jboss.resource.adapter.jdbc.vendor.OracleValidConnectionChecker</valid-connection-checker-class-name-->
<!-- Checks the Oracle error codes and messages for fatal errors -->
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter</exception-sorter-class-name>
<!-- sql to call when connection is created
<new-connection-sql>some arbitrary sql</new-connection-sql>
-->
<!-- sql to call on an existing pooled connection when it is obtained from pool - the OracleValidConnectionChecker is prefered
<check-valid-connection-sql>some arbitrary sql</check-valid-connection-sql>
-->
<!-- corresponding type-mapping in the standardjbosscmp-jdbc.xml -->
<metadata>
<type-mapping>Oracle9i</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
启动对应的数据库,然后将oracle的驱动class12.jar包加入到%jboss%/server/default/lib下,并将该配置文件放置%jboss%/server/default/deploy下,启动Jboss,后台可以看到如图输出,Bound ConnectionManager 'jboss.jca:service=DataSourceBinding,name=OracleDS' to JNDI name 'OracleDS'
表示数据源绑定成功。
,这样即是将数据源发布了,查看JBoss 的JNDI 的话,位于Global JNDI Namespace 下。
注意:默认情况下,JBoss 的数据源都是配置为仅服务器可见的(即服务器内部的EJB,
JSP 应用能够访问,外部例如独立运行的Java 程序客户端不能访问),也就是位于
Java:Namespace 下面。如果要做跨服务器的测试, 请加入标签
<use-java-context>false</use-java-context>,例如我们的例子所示。一般情况下如果没
有远程访问数据源的要求,则可以去掉此配置,就像JBoss 自带的数据源java:DefaultDS
一样。
加入了<use-java-context>false</use-java-context>这个标记,就是全局的,去掉此配置,则变为了JBoss自带的数据源一样,本机可以访问。
2、 测试数据源
在Java 项目JBossJNDITest 的src 目录下新建文件
jndi.properties,代码清单如下:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=jnp://localhost
这样,我们在代码中
InitialContext ctx = new InitialContext();
就已经初始化上下文了。不用再初始化了。
由于使用了JNDI 配置文件,所以初始化上下文的时候,就不需要再设置参数了,这是个节
省代码的好办法,推荐在普通Java 类中访问JNDI 时使用。这段代码运行后,输出内容如
下:
org.jboss.invocation.jrmp.interfaces.JRMPInvokerProxy@e102dc
Lee
CXY
即输出了表的用户名内容。
接着我们展示位于java:下面的服务器私有的数据源的访问方式,它们只能通过在同一
服务器上的JSP 或者EJB 来访问,所以在Web 项目JBossJNDIWeb 的WebRoot 下新建
JSP 文件datasource.jsp,代码清单如下:
<%@ page language="java" import="java.util.*" pageEncoding="GBK"%>
<%@page import="javax.naming.InitialContext"%>
<%@page import="javax.sql.DataSource"%>
<%@page import="java.sql.Connection"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>JNDI 私有数据源 访问测试</title>
</head>
<body>
<%
InitialContext ctx = new InitialContext();
Object obj = ctx.lookup("java:DefaultDS");
DataSource ds = (DataSource) obj;
Connection conn = ds.getConnection();
out.println(conn.getMetaData().getDatabaseProductName());
conn.close();
ctx.close();
%>
</body>
</html>
运行,输出
这说明JBoss默认的这个DefaultDS数据源,是HSQL,也是个开源的嵌入式Java数据库,
其官方网站地址是http://www.hsqldb.org/ 。