说到JNDI,即熟悉又陌生,熟悉在常常使用,如EJB3.0中的@EJB注入,底层实现即是JNDI的方式;喜闻乐见的:

Context ctx=new InitialContext();
Object obj=(Object)ctx.lookup("java:comp/env/XXX");

更是最经常使用的方式。说它陌生,是由于,对于JNDI,我们仅限于主要的使用,本文就是带领大家进入JNDI分析阶段。

JNDI作用

以数据源为例。

未使用JNDI

假设我们没有使用JNDI,代码例如以下

Connection conn=null;
try {
Class.forName("com.mysql.jdbc.Driver",
true, Thread.currentThread().getContextClassLoader());
conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue");
/* 使用conn并进行SQL操作 */
......
conn.close();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
if(conn!=null) {
try {
conn.close();
} catch(SQLException e) {}
}
}

这样的方法有非常大的问题,假设数据库变了怎么办?假设口令变了怎么办?写的太死,灵活性差。

使用JNDI

在来看看使用了JNDI的方式。

配置

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>MySqlDS</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/lw</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>root</user-name>
<password>rootpassword</password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>

使用

Connection conn=null;
try {
Context ctx=new InitialContext();
Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用数据源
DataSource ds=(Datasource)datasourceRef;
conn=ds.getConnection();
/* 使用conn进行数据库SQL操作 */
......
c.close();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
if(conn!=null) {
try {
conn.close();
} catch(SQLException e) { }
}
}

小结

使用JNDI,能够将代码和数据库配置分离,减少了耦合,使得程序更灵活,能够看到JNDI的使用方式:

  • 定义内容(名称,内容)
  • 依据名称使用内容

这仅仅是JNDI的用处之中的一个,你能够使用JNDI配置和使用很多其它内容,知道大概怎么使用,開始介绍JNDI。

JNDI

JNDI(Java Naming and Directory Interface)Java 命名与文件夹接口,JavaEE规范中重要的规范之中的一个,前面的文章说到过:每个规范都是对编程某一方面的抽象定义,JNDI定位于:定义、查找。它为编程人员提供了一个统一的方式,无论是文件夹、文件、注冊表、对象、配置等等,我们能够仅仅依据它们的名字就能够以统一的方式操作他们。

规范源代码

规范位于命名空间javax.naming下:

上面是部分规范源码,由上图能够知道,JNDI规范分为5部分:

    * javax.naming

    * javax.naming.directory

    * javax.naming.event

    * javax.naming.ldap

    * javax.naming.spi 



    从更宏观的方面来说,JNDI规范分为两部分,API和SPI:

  • API:编程人员可见的接口,能够直接使用
  • SPI:API底层的实现接口,对编程人员不可见

架构图

JNDI宏观架构图例如以下:

如上,各种厂商提供的产品,他们的命名和文件夹服务的标准不一致,各个文件夹服务採用的訪问协议也不一样(跟JDBC类同),假设直接訪问,须要编写不同的java代码。因此须要JNDI SPI,它能动态的插入这些命名和文件夹服务,可以将其协议专属的文件夹产品集成到系统中使得开发者仅仅须要知道名字,就可以获取到操作各种类型的内容。

当前JNDI支持的操作类型为:DNS、XNam 、Novell文件夹服务、LDAP(轻型文件夹訪问协议)、 CORBA对象服务、文件系统、Windows注冊表、RMI、DSML、NIS。

源代码分析

JNDI仅仅是规范,所以我们编程还是须要详细实现,当然一般各种容器、server都会提供实现,本文我们以Tomcat为例。

    首先是下载tomcat源代码,能够从《Tomcat 8.0.10源代码》,也能够从Tomcat官方SVN下载源代码:http://svn.apache.org/repos/asf/tomcat/。在下载的源代码中,JNDI的相关实现位于\java\org\apache\naming下,我们此处仅仅分析最核心的一个类,也就是我们常常使用的Context
ctx=new InitialContext();中的“InitailContext”,在Tomcat中是NamingContext.java:

/**
* Catalina JNDI Context implementation.
*
* @author Remy Maucherat
*/
public class NamingContext implements Context { // -------------------------------------------------------------- Constants /**
* Name parser for this context.
*/
protected static final NameParser nameParser = new NameParserImpl(); private static final org.apache.juli.logging.Log log =
org.apache.juli.logging.LogFactory.getLog(NamingContext.class); // ----------------------------------------------------------- Constructors /**
* Builds a naming context using the given environment.
*/
public NamingContext(Hashtable<String,Object> env, String name,
HashMap<String,NamingEntry> bindings) throws NamingException { this.env = new Hashtable<>();
// FIXME ? Could be put in the environment ?
this.name = name;
// Populating the environment hashtable
if (env != null ) {
Enumeration<String> envEntries = env.keys();
while (envEntries.hasMoreElements()) {
String entryName = envEntries.nextElement();
addToEnvironment(entryName, env.get(entryName));
}
}
this.bindings = bindings;
}
// ----------------------------------------------------- Instance Variables /**
* Environment.
*/
protected final Hashtable<String,Object> env;
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
/**
* Bindings in this Context.
*/
protected final HashMap<String,NamingEntry> bindings;
/**
* Name of the associated Catalina Context.
*/
protected final String name;
/**
* Determines if an attempt to write to a read-only context results in an
* exception or if the request is ignored.
*/
private boolean exceptionOnFailedWrite = true; // -------------------------------------------------------- Context Methods
@Override
public void unbind(Name name) throws NamingException { if (!checkWritable()) {
return;
} while ((!name.isEmpty()) && (name.get(0).length() == 0))
name = name.getSuffix(1);
if (name.isEmpty())
throw new NamingException
(sm.getString("namingContext.invalidName")); NamingEntry entry = bindings.get(name.get(0)); if (entry == null) {
throw new NameNotFoundException
(sm.getString("namingContext.nameNotBound", name, name.get(0)));
} if (name.size() > 1) {
if (entry.type == NamingEntry.CONTEXT) {
((Context) entry.value).unbind(name.getSuffix(1));
} else {
throw new NamingException
(sm.getString("namingContext.contextExpected"));
}
} else {
bindings.remove(name.get(0));
} }
@Override
public void rename(Name oldName, Name newName)
throws NamingException {
Object value = lookup(oldName);
bind(newName, value);
unbind(oldName);
} @Override
public NamingEnumeration<NameClassPair> list(Name name)
throws NamingException {
// Removing empty parts
while ((!name.isEmpty()) && (name.get(0).length() == 0))
name = name.getSuffix(1);
if (name.isEmpty()) {
return new NamingContextEnumeration(bindings.values().iterator());
} NamingEntry entry = bindings.get(name.get(0)); if (entry == null) {
throw new NameNotFoundException
(sm.getString("namingContext.nameNotBound", name, name.get(0)));
} if (entry.type != NamingEntry.CONTEXT) {
throw new NamingException
(sm.getString("namingContext.contextExpected"));
}
return ((Context) entry.value).list(name.getSuffix(1));
} /**
* Enumerates the names bound in the named context, along with the
* objects bound to them. The contents of any subcontexts are not
* included.
* <p>
* If a binding is added to or removed from this context, its effect on
* an enumeration previously returned is undefined.
*
* @param name the name of the context to list
* @return an enumeration of the bindings in this context.
* Each element of the enumeration is of type Binding.
* @exception NamingException if a naming exception is encountered
*/
@Override
public NamingEnumeration<Binding> listBindings(Name name)
throws NamingException {
// Removing empty parts
while ((!name.isEmpty()) && (name.get(0).length() == 0))
name = name.getSuffix(1);
if (name.isEmpty()) {
return new NamingContextBindingsEnumeration(bindings.values().iterator(), this);
} NamingEntry entry = bindings.get(name.get(0)); if (entry == null) {
throw new NameNotFoundException
(sm.getString("namingContext.nameNotBound", name, name.get(0)));
} if (entry.type != NamingEntry.CONTEXT) {
throw new NamingException
(sm.getString("namingContext.contextExpected"));
}
return ((Context) entry.value).listBindings(name.getSuffix(1));
} /**
* @param name the name of the context to be destroyed; may not be empty
* @exception NameNotFoundException if an intermediate context does not
* exist
* @exception NotContextException if the name is bound but does not name
* a context, or does not name a context of the appropriate type
*/
@Override
public void destroySubcontext(Name name) throws NamingException { if (!checkWritable()) {
return;
} while ((!name.isEmpty()) && (name.get(0).length() == 0))
name = name.getSuffix(1);
if (name.isEmpty())
throw new NamingException
(sm.getString("namingContext.invalidName")); NamingEntry entry = bindings.get(name.get(0)); if (entry == null) {
throw new NameNotFoundException
(sm.getString("namingContext.nameNotBound", name, name.get(0)));
} if (name.size() > 1) {
if (entry.type == NamingEntry.CONTEXT) {
((Context) entry.value).destroySubcontext(name.getSuffix(1));
} else {
throw new NamingException
(sm.getString("namingContext.contextExpected"));
}
} else {
if (entry.type == NamingEntry.CONTEXT) {
((Context) entry.value).close();
bindings.remove(name.get(0));
} else {
throw new NotContextException
(sm.getString("namingContext.contextExpected"));
}
} } /**
* Creates and binds a new context. Creates a new context with the given
* name and binds it in the target context (that named by all but
* terminal atomic component of the name). All intermediate contexts and
* the target context must already exist.
*
* @param name the name of the context to create; may not be empty
* @return the newly created context
* @exception NameAlreadyBoundException if name is already bound
* @exception javax.naming.directory.InvalidAttributesException if creation
* of the sub-context requires specification of mandatory attributes
* @exception NamingException if a naming exception is encountered
*/
@Override
public Context createSubcontext(Name name) throws NamingException {
if (!checkWritable()) {
return null;
} NamingContext newContext = new NamingContext(env, this.name);
bind(name, newContext); newContext.setExceptionOnFailedWrite(getExceptionOnFailedWrite()); return newContext;
} /**
* Adds a new environment property to the environment of this context. If
* the property already exists, its value is overwritten.
*
* @param propName the name of the environment property to add; may not
* be null
* @param propVal the value of the property to add; may not be null
* @exception NamingException if a naming exception is encountered
*/
@Override
public Object addToEnvironment(String propName, Object propVal)
throws NamingException {
return env.put(propName, propVal);
} /**
* Removes an environment property from the environment of this context.
*
* @param propName the name of the environment property to remove;
* may not be null
* @exception NamingException if a naming exception is encountered
*/
@Override
public Object removeFromEnvironment(String propName)
throws NamingException {
return env.remove(propName);
} /**
* Retrieves the environment in effect for this context. See class
* description for more details on environment properties.
* The caller should not make any changes to the object returned: their
* effect on the context is undefined. The environment of this context
* may be changed using addToEnvironment() and removeFromEnvironment().
*
* @return the environment of this context; never null
* @exception NamingException if a naming exception is encountered
*/
@Override
public Hashtable<?,?> getEnvironment()
throws NamingException {
return env;
} /**
* Closes this context. This method releases this context's resources
* immediately, instead of waiting for them to be released automatically
* by the garbage collector.
* This method is idempotent: invoking it on a context that has already
* been closed has no effect. Invoking any other method on a closed
* context is not allowed, and results in undefined behaviour.
*
* @exception NamingException if a naming exception is encountered
*/
@Override
public void close() throws NamingException {
if (!checkWritable()) {
return;
}
env.clear();
} /**
* Retrieves the full name of this context within its own namespace.
* <p>
* Many naming services have a notion of a "full name" for objects in
* their respective namespaces. For example, an LDAP entry has a
* distinguished name, and a DNS record has a fully qualified name. This
* method allows the client application to retrieve this name. The string
* returned by this method is not a JNDI composite name and should not be
* passed directly to context methods. In naming systems for which the
* notion of full name does not make sense,
* OperationNotSupportedException is thrown.
*
* @return this context's name in its own namespace; never null
* @exception OperationNotSupportedException if the naming system does
* not have the notion of a full name
* @exception NamingException if a naming exception is encountered
*/
@Override
public String getNameInNamespace()
throws NamingException {
throw new OperationNotSupportedException
(sm.getString("namingContext.noAbsoluteName"));
//FIXME ?
} // ------------------------------------------------------ Protected Methods /**
* Retrieves the named object.
*
* @param name the name of the object to look up
* @param resolveLinks If true, the links will be resolved
* @return the object bound to name
* @exception NamingException if a naming exception is encountered
*/
protected Object lookup(Name name, boolean resolveLinks)
throws NamingException { // Removing empty parts
while ((!name.isEmpty()) && (name.get(0).length() == 0))
name = name.getSuffix(1);
if (name.isEmpty()) {
// If name is empty, a newly allocated naming context is returned
return new NamingContext(env, this.name, bindings);
} NamingEntry entry = bindings.get(name.get(0)); if (entry == null) {
throw new NameNotFoundException
(sm.getString("namingContext.nameNotBound", name, name.get(0)));
} if (name.size() > 1) {
// If the size of the name is greater that 1, then we go through a
// number of subcontexts.
if (entry.type != NamingEntry.CONTEXT) {
throw new NamingException
(sm.getString("namingContext.contextExpected"));
}
return ((Context) entry.value).lookup(name.getSuffix(1));
} else {
if ((resolveLinks) && (entry.type == NamingEntry.LINK_REF)) {
String link = ((LinkRef) entry.value).getLinkName();
if (link.startsWith(".")) {
// Link relative to this context
return lookup(link.substring(1));
} else {
return (new InitialContext(env)).lookup(link);
}
} else if (entry.type == NamingEntry.REFERENCE) {
try {
Object obj = NamingManager.getObjectInstance
(entry.value, name, this, env);
if(entry.value instanceof ResourceRef) {
boolean singleton = Boolean.parseBoolean(
(String) ((ResourceRef) entry.value).get(
"singleton").getContent());
if (singleton) {
entry.type = NamingEntry.ENTRY;
entry.value = obj;
}
}
return obj;
} catch (NamingException e) {
throw e;
} catch (Exception e) {
log.warn(sm.getString
("namingContext.failResolvingReference"), e);
throw new NamingException(e.getMessage());
}
} else {
return entry.value;
}
} } /**
* Binds a name to an object. All intermediate contexts and the target
* context (that named by all but terminal atomic component of the name)
* must already exist.
*
* @param name the name to bind; may not be empty
* @param obj the object to bind; possibly null
* @param rebind if true, then perform a rebind (ie, overwrite)
* @exception NameAlreadyBoundException if name is already bound
* @exception javax.naming.directory.InvalidAttributesException if object
* did not supply all mandatory attributes
* @exception NamingException if a naming exception is encountered
*/
protected void bind(Name name, Object obj, boolean rebind)
throws NamingException { if (!checkWritable()) {
return;
} while ((!name.isEmpty()) && (name.get(0).length() == 0))
name = name.getSuffix(1);
if (name.isEmpty())
throw new NamingException
(sm.getString("namingContext.invalidName")); NamingEntry entry = bindings.get(name.get(0)); if (name.size() > 1) {
if (entry == null) {
throw new NameNotFoundException(sm.getString(
"namingContext.nameNotBound", name, name.get(0)));
}
if (entry.type == NamingEntry.CONTEXT) {
if (rebind) {
((Context) entry.value).rebind(name.getSuffix(1), obj);
} else {
((Context) entry.value).bind(name.getSuffix(1), obj);
}
} else {
throw new NamingException
(sm.getString("namingContext.contextExpected"));
}
} else {
if ((!rebind) && (entry != null)) {
throw new NameAlreadyBoundException
(sm.getString("namingContext.alreadyBound", name.get(0)));
} else {
// Getting the type of the object and wrapping it within a new
// NamingEntry
Object toBind =
NamingManager.getStateToBind(obj, name, this, env);
if (toBind instanceof Context) {
entry = new NamingEntry(name.get(0), toBind,
NamingEntry.CONTEXT);
} else if (toBind instanceof LinkRef) {
entry = new NamingEntry(name.get(0), toBind,
NamingEntry.LINK_REF);
} else if (toBind instanceof Reference) {
entry = new NamingEntry(name.get(0), toBind,
NamingEntry.REFERENCE);
} else if (toBind instanceof Referenceable) {
toBind = ((Referenceable) toBind).getReference();
entry = new NamingEntry(name.get(0), toBind,
NamingEntry.REFERENCE);
} else {
entry = new NamingEntry(name.get(0), toBind,
NamingEntry.ENTRY);
}
bindings.put(name.get(0), entry);
}
} }
}

如上的代码是我精简后的代码(源代码966行),删除了各种Overload的函数和其它辅助函数,精简后的函数能够概括为下面几个:

  • bind/rebind:绑定一项内容到上下文
  • unbind:取消绑定某项内容
  • lookup:依据名字查找一项内容
  • rename:重命名某项内容
  • NamingEnumeration:枚举绑定的内容
  • destroySubcontext:销毁子上下文
  • createSubcontext:创建子上下文
  • addToEnvironment:加入�设置參数
  • removeFromEnvironment:移除某项设置參数
  • getEnvironment:得到某项设置參数
  • Close:清空环境变量env的设置

假设再抽象一步,能够看到,这些函数都是对上面源代码中:

/**
* Environment.
*/
protected final Hashtable<String,Object> env;
/**
* Bindings in this Context.
*/
protected final HashMap<String,NamingEntry> bindings;
/**
* Name of the associated Catalina Context.
*/

这两个成员变量的各种操作,这两个成员变量为:

  • env:初始化上下文使用的变量集合,是一个Hashtable
  • bindings:我们常操作的name-object对,是一个HashMap

我们知道JNDI提供的是一个树状的结构,它的最顶是一个initialContext节点,以下就是绑定的一些对象或是一些subContext,用JNDI树能够查找到树中每个节点上内容引用,可是Tomcat的HashMap怎样提供树状组织?从createSubcontext的源码能够看到,它调用了如上代码类的构造函数,又一次构造了一个Context对象,然后再放到bindings中,同一时候存有一个指向父上下文的名称。

另外,JNDI支持分布式,我们知道JNDI在server如Jboss中是以服务的形式存在,配置jboss提供的文件,我们能够訪问其它jboss上的JNDI服务,从而获取其它Jboss中的内容:

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=jnp://localhost:1099

总结

JNDI的定位是怎样方便、统一的定义和訪问资源,其API就效果看来,相当于外观模式,屏蔽了底层注冊表、对象等不同訪问方式之间的差异,另外@EJB注入底层使用的是JNDI实现,这个在EJB有关的文章中再细说。

再说JNDI的更多相关文章

  1. Java学习笔记之JNDI(六)

    JNDI 是什么 JNDI是 Java 命名与目录接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之一,不少专家认为,没有透彻理解JNDI的 ...

  2. JNDI

    这两天研究了一下 context.lookup("java:comp/env/XXX")和直接context.lookup("XXX")的区别 网上关于这两个的 ...

  3. Tomcat7.0+的JNDI问题

    上次搭建spring+springmvc+mybatis框架时用的第三方连接池jar包,但是部署到tomcat中后访问没有问题,但是启动时报了个JNDI的错,我没用JNDI你给我报什么,fuck!把错 ...

  4. Tomcat下使用c3p0配置jndi数据源

    下载c3p0包: 下载地址:https://sourceforge.net/projects/c3p0/files/?source=navbar 解压后得到包:c3p0-0.9.2.jar,mchan ...

  5. Spring配置JNDI和通过JNDI获取DataSource

    一.SpringJNDI数据源配置信息 <bean id="dataSource" class="org.springframework.jndi.JndiObje ...

  6. 帆软报表FineReport中数据连接之Weblogic配置JNDI连接

    1. 制作报表的原理 在帆软报表FineReport设计器中先用JDBC连接到数据库,建立数据库连接,然后用SQL或者其他方法创建数据集,使用数据集制作报表,然后把建立的数据库连接从JDBC连接改成J ...

  7. 帆软报表FineReport中数据连接之Jboss配置JNDI连接

    使用sqlsever 2000数据库数据源来做实例讲解,帆软报表FineReport数据连接中Jboss配置JNDI大概的过程和WEBSPHERE以及WEBLOGIC基本相同,用JDBC连接数据库制作 ...

  8. 帆软报表FineReport中数据连接之Websphere配置JNDI连接

    以oracle9i数据源制作的模板jndi.cpt为例来说明如何在FineReport中的Websphere配置JNDI连接.由于常用服务器的JNDI驱动过大,帆软报表FineReport中没有自带, ...

  9. 帆软报表FineReport中数据连接之Tomcat配置JNDI连接

    1. 问题描述 在帆软报表FineReport中,通过JNDI方式定义数据连接,首先在Tomcat服务器配置好JNDI,然后在设计器中直接调用JNDI的名字,即可成功使用JNDI连接,连接步骤如下: ...

  10. mysql连接超时与jndi数据源配置

    昨天有运营说添加活动不能用了,我就看了一下后台日志,发现访问数据库是报错: at java.lang.Thread.run(Thread.java:722) Caused by: com.mysql. ...

随机推荐

  1. EditTex属性

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tool ...

  2. 16位图像Alpha混合的实现(用汇编写的,比MMX还要快)

    Alpha 混合的算法很简单,基于下面的公式就可以实现: D := A * (S - D) / 255 + D D 是目标图像的像素, S 是源图像的像素 A 是 Alpha 值, 0 为全透明, 2 ...

  3. 关于window.history.back()后退问题

    Windows下的window.history.back()后退后返回的不仅仅是前一个页而是前一个页的状态.假设一个页我改动了3次那必须后退3次才干回到前一个页.并且数据库中删除的数据依旧显示在上面感 ...

  4. 算法起步之A星算法

    原文:算法起步之A星算法 用途: 寻找最短路径,优于bfs跟dfs 描述: 基本描述是,在深度优先搜索的基础上,增加了一个启发式算法,在选择节点的过程中,不是盲目选择,而是有目的的选的,F=G+H,f ...

  5. 超人学院Hadoop大数据技术资源分享

    超人学院Hadoop大数据技术资源分享 http://bbs.superwu.cn/forum.php?mod=viewthread&tid=807&fromuid=645 很多其它精 ...

  6. PVPlayer的实现方式

    关于opencore下多媒体播放,在mediaserver进程里面仅仅有一行代码: MediaPlayerService::instantiate(); 这行代码的作用是初始化一个MediaPlaye ...

  7. 初始化openwrt的rootpassword

    更改openwrt源代码 shadow 文件 package/base-files/files/etc/shadow shadow 文件參考http://blog.csdn.net/u01164188 ...

  8. CC 3-Palindromes(manacher)

    传送门:3-Palindromes 题意:求为回文串且能整除3且不前导0的子串个数. 分析:由 manacher算法O(N)可算出以i为坐标的最长为p[i]回文子串,且Si-k,Si-k+1..... ...

  9. hdu1151+poj2594(最小路径覆盖)

    传送门:hdu1151 Air Raid 题意:在一个城镇,有m个路口,和n条路,这些路都是单向的,而且路不会形成环,现在要弄一些伞兵去巡查这个城镇,伞兵只能沿着路的方向走,问最少需要多少伞兵才能把所 ...

  10. iOS 搜索框控件 最简单的dome

    刚学习搜索框控件,写了个最简单的dome #import <UIKit/UIKit.h> .h @interface ViewController : UIViewController&l ...