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="[email protected]"
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功能相同。