1. 简介

Java 命名和目录接口(JNDI)为 Java 应用程序提供了一套统一的 API,用于访问命名和目录服务。通过 JNDI,我们可以绑定对象、查找或查询对象,甚至监听对象的变化。

JNDI 支持多种后端服务,比如 LDAP、DNS、RMI 和 JDBC 相关的资源查找。虽然支持的服务种类繁多,但本文我们将聚焦在 JDBC 数据源的绑定与查找,并通过实际示例带你掌握 JNDI 的核心用法。

✅ 提示:JNDI 的价值在于解耦——把资源的配置交给容器或运维,应用只管“按名字取资源”,不用关心具体在哪。


2. JNDI 核心概念

使用 JNDI 前,你得先理解两个核心接口:NameContext。它们构成了整个 JNDI 操作的基础。

⚠️ 注意:虽然 JNDI 抽象了底层服务,但不同服务的配置细节(比如数据库连接属性)依然需要你了解清楚,否则踩坑是早晚的事。

2.1. Name 接口

Name 接口用于表示 JNDI 中的命名路径,类似于文件系统的路径结构。每个层级称为一个“组件”(component),用 / 分隔。

Name objectName = new CompositeName("java:comp/env/jdbc");

上面这行代码创建了一个复合名称(CompositeName),代表一个四层结构的 JNDI 路径。我们可以通过遍历查看每一层:

Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
  System.out.println(elements.nextElement());
}

输出结果为:

java:comp
env
jdbc

✅ 注意:这里的分隔符是 /,但 java:comp 是一个整体,表示 Java EE 环境下的全局上下文。

我们还可以动态添加子路径:

objectName.add("example");

验证是否添加成功:

assertEquals("example", objectName.get(objectName.size() - 1));

这个机制在构建动态 JNDI 路径时非常有用,比如根据环境拼接不同的数据源名。


2.2. Context 接口

Context 是 JNDI 的操作入口,相当于一个“命名空间”,你可以往里面绑定对象,也可以从中查找对象。

实际开发中,我们通常不会手动初始化完整的 JNDI 环境(那太重了),而是借助测试工具类来模拟。Spring 提供的 SimpleNamingContextBuilder 就是个好帮手:

SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); 
builder.activate();

这行代码会注册一个轻量级的 JNDI 提供者(Provider),并激活它,使得后续的 InitialContext 能正常工作。

接着我们通过 JndiTemplate 获取上下文实例:

JndiTemplate jndiTemplate = new JndiTemplate();
ctx = (InitialContext) jndiTemplate.getContext();

✅ 小贴士:InitialContextContext 的标准实现,几乎所有 JNDI 操作都从它开始。


3. JNDI 对象绑定与查找

有了上下文环境,接下来就可以玩点真的了——把一个 JDBC DataSource 绑定到 JNDI,并在需要时查找它。

先创建一个内存数据库的数据源(测试常用):

ds = new DriverManagerDataSource("jdbc:h2:mem:mydb");

3.1. 绑定 JNDI 对象

使用 bind() 方法将数据源注册到指定的 JNDI 名称下:

ctx.bind("java:comp/env/jdbc/datasource", ds);

📌 绑定规则说明:

  • java:comp/env/ 是 Java EE 应用的标准命名前缀,表示组件环境命名空间。
  • 后续路径可自定义,如 jdbc/datasource 表示这是一个 JDBC 数据源。

⚠️ 注意事项:

  • 这种方式在普通 Spring Boot 应用中不常见,因为 Spring 自己有一套 DI 机制更方便。
  • 但在传统 Java EE 容器(如 Tomcat、WebLogic)中,运维人员会在服务器层面配置数据源,开发者只需通过 JNDI 名称获取即可,真正做到“配置与代码分离”。

3.2. 查找 JNDI 对象

绑定完成后,其他组件就可以通过名称查找该数据源:

DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");

然后验证是否能正常获取连接:

assertNotNull(ds.getConnection());

✅ 成功拿到连接说明 JNDI 查找流程走通了。

📌 常见使用场景:

  • Web 应用启动时,由容器预加载数据源;
  • 应用代码通过 @Resource(name = "java:comp/env/jdbc/datasource") 注入;
  • 不依赖 Spring,也能实现资源解耦。

4. 常见 JNDI 异常

JNDI 虽然强大,但一旦配置不对,抛出的异常可能让人一脸懵。下面列出两个最典型的异常,帮你快速定位问题。


4.1. NameNotFoundException

当你查找一个不存在的 JNDI 名称时,会抛出此异常:

ctx.lookup("badJndiName");

错误堆栈如下:

javax.naming.NameNotFoundException: Name [badJndiName] not bound; 0 bindings: []
  at org.springframework.mock.jndi.SimpleNamingContext.lookup(SimpleNamingContext.java:140)
  at java.naming/javax.naming.InitialContext.lookup(InitialContext.java:409)

✅ 解决思路:

  • 检查绑定时使用的名称是否拼写正确;
  • 查看当前上下文中所有已绑定的对象(某些实现会打印 bindings 列表);
  • 确保 java:comp/env/ 前缀是否必要且存在。

4.2. NoInitialContextException

这个异常表示 没有可用的 JNDI 上下文提供者。哪怕你代码写得再对,没有 provider 也白搭。

示例代码:

assertThrows(NoInitialContextException.class, () -> {
  JndiTemplate jndiTemplate = new JndiTemplate();
  InitialContext ctx = (InitialContext) jndiTemplate.getContext();
  ctx.lookup("java:comp/env/jdbc/datasource");
}).printStackTrace();

错误信息:

javax.naming.NoInitialContextException: Need to specify class name in environment or system property, 
  or in an application resource file: java.naming.factory.initial
    at java.naming/javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:685)

✅ 原因分析:

  • 缺少 java.naming.factory.initial 系统属性;
  • 没有引入任何 JNDI 实现(如 Spring 的 SimpleNamingContextBuilder);
  • 在非 Java EE 环境中直接使用 InitialContext,但没做模拟。

✅ 解决方案:

  • 测试环境:使用 SimpleNamingContextBuilder 显式激活上下文;
  • 生产环境:确保应用服务器(如 Tomcat)已正确配置 JNDI 资源;
  • 避免在纯 Spring Boot 项目中硬编码 JNDI 查找,除非明确需要兼容老系统。

5. JNDI 在现代应用架构中的角色

不可否认,JNDI 在轻量级、容器化的现代 Java 应用中地位已大不如前。Spring Boot 推崇“约定优于配置”,直接注入 DataSource 更简单粗暴。

但 JNDI 并未完全退出历史舞台。以下三种技术仍在广泛使用 JNDI:

技术 使用场景
✅ JDBC 应用服务器管理的数据源(如 Tomcat JNDI DataSource)
✅ EJB 企业级 Bean 的查找与远程调用
✅ JMS 消息队列连接工厂(ConnectionFactory)的注册与获取

📌 典型应用场景:

假设你的公司有独立的 DevOps 团队,他们负责管理数据库账号密码等敏感信息。他们可以在不同环境(开发、测试、生产)的 Tomcat 中配置同名的 JNDI 数据源:

<!-- Tomcat context.xml -->
<Resource name="java:comp/env/jdbc/datasource" 
          auth="Container"
          type="javax.sql.DataSource"
          username="prod_user"
          password="secure_password_123"
          url="jdbc:mysql://prod-db:3306/app" />

而开发人员本地可以用 H2 内存库模拟:

SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:test"));
builder.activate();

✅ 最终效果:同一段代码,不同环境自动适配真实资源,无需改代码、无需硬编码。


6. 总结

本文带你系统了解了 JNDI 的核心机制:

  • Name:命名路径的结构化表示;
  • Context:对象绑定与查找的操作入口;
  • ✅ 绑定与查找 DataSource 的完整流程;
  • ✅ 两类常见异常及其排查方法;
  • ✅ JNDI 在现代架构中的适用场景与价值。

📌 最后提醒:

❌ 别在新项目中为了用 JNDI 而用 JNDI。
✅ 但在对接传统 Java EE 系统时,掌握 JNDI 是必备技能。

所有示例代码已托管至 GitHub:

https://github.com/tech-engineer/tutorials/tree/master/core-java-modules/core-java-jndi


原始标题:Java Naming and Directory Interface Overview