深入理解 JNDI

经常在阅读一些 Java 源码的时候,看到和 JNDI 相关的一些代码片段。那么 JNDI 到底是一种什么技术呢?又为什么要提出 JNDI 这种概念呢?以下是我对这个问题探寻的答案。

一、JNDI 与 J2EE 的关系

JNDI 是在 J2EE 的环境下才有的概念。

J2SE 是 Java 语言编程的核心版本,这个版本与平常提到的 C、Python、Swift 等其它编程语言是没有区别的,它们都可以满足个人基本的开发需求。然而,对于一个企业要开发的产品而言,尤其是基于 Web 的应用产品,这还远远不够。

通常,如果你想要开发一款 Web 应用产品,那么你不可避免的会遇到如下一些技术问题:

  • 如何接受来自客户端 (网页) 的请求
  • 如何处理一些事务操作
  • 如何与后台数据库进行交互

当然,Java 团队也意识到了这些问题,于是他们一块开发了一个容器,将其称之为 J2EE。在这个容器中,其包含了许多用于处理特定问题的组件,或者称之为规范

  • 为了解决客户端与服务器请求响应的 JSP/Servlet 规范
  • 为了解决事务操作的 EJB 规范
  • 为了解决与数据库交互的 JDBC 规范

依据容器提供的这些基础设施组件,企业可以很轻松地编写出易扩展、高性能的 Web 应用。JNDI 是 Java Naming and Directory Interface 这几个单词的缩写,其正是 J2EE 这个容器,为开发者提供的一个用来访问资源的组件 (API)。它使得开发人员在不同的环境下,都能够根据资源的名称,找到相应的资源。

二、命名服务

命名服务 (directory/name service) 能够将资源的名字映射到资源实际的网络地址中。不同的命名服务有不同的名字命名规则,但是这个命名出来的名字一定都是唯一的。现存的一些命名服务有:DNS、LDAP、CORBA、RMI 等。

DNS 命名服务器将域名映射到不同的 IP 地址中,人们只需要输入简单易记的网址就可以访问想要访问的网站:

DNS-Server

RMI 命名服务器将 API 方法名映射到不同的对应的实现了此方法的机器上,开发者可以实现远程调用方法:

RMI-Server

不同的命名服务器有不同的实现,但这些命名服务器的本质,就是为了让人们能够根据一个名字,能够很方便的访问到存储在不同计算机上的这个名字所对应的对象、文件、数据库、打印机等资源。而 JNDI 正是对这些不同的命名服务所提供的一个 API 编程接口,不同的命名服务器厂商可以有不同的实现,但是开发者访问他们的方式却是固定的。这就是 JNDI 中的 Interface 的意义。

三、成为 JNDI 资源提供者

Tomcat 是一个标准的 J2EE 容器,其理所应当提供了 JNDI 的实现,以便让开发者访问不同的资源。作为一个标准的 J2EE 容器,其至少需要支持如下格式的资源的访问:

JNDI-subcontext

就像文件可以有条理的放在同一个目录下,在 JNDI 中,资源也被组织成了不同的子上下文。例如所有的 JDBC 资源必须使用 java:comp/env/jdbc 子上下文来查找,也就是说资源名字必须以这个字符串开头才行。这是规范,没有为什么不为什么。

如下图所示,当应用程序在 Tomcat 中想要访问资源的时候,就直接通过 JNDI 去访问,然后再由 JNDI 去查找相关的资源,JNDI 扮演的角色就是一个中间人,在应用程序和资源之间加了一层抽象:

jndi-resources-pool

我们如果想要实现 Tomcat 中 JNDI 的代理访问资源的功能,只需要实现一个类就可以了:

1
2
3
4
5
6
public interface InitialContextFactory {

public Context getInitialContext(Hashtable<?,?> environment)
throws NamingException;

}

Context 类也是一个接口,但你可以理解为就是一个 Map ,其职责就是要做到正确无误的映射资源名字和资源。当我们在应用程序中通过如下代码获取一个数据库连接的时候:

1
2
3
4
5
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");

// Look up our data source
DataSource ds = (DataSource) envCtx.lookup("jdbc/EmployeeDB");

实际上,最终是通过 Context 类的 lookup 方法进行的资源的查找。

单单提供一个 InitialContextFactory 的实现类,你的代码是不会自动找到这个上下文(资源)工厂的。还需要使用 System 这个系统粘合剂,来主动地告诉 JVM,”JNDI 资源的实现是哪个类”。

1
2
3
4
static {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.naming.java.javaURLContextFactory");
}

这一切都完成之后,你便可以通过 JNDI 来访问外部资源了。

四、参考

推荐文章