首页 > 代码库 > 再说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仅仅是规范,所以我们编程还是须要详细实现,当然一般各种容器、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 = 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对,是一个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