首页 > 代码库 > 再说JNDI

再说JNDI

    说到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只是规范,所以我们编程还是需要具体实现,当然一般各种容器、服务器都会提供实现,本文我们以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 = http://www.mamicode.com/lookup(oldName);>    如上的代码是我精简后的代码(源码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对,也是一个Hashtable

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

    另外,JNDI支持分布式,我们知道JNDI在服务器如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有关的文章中再细说。