Java命名和目录接口概述

2023/06/15

1. 概述

Java命名和目录接口(JNDI)提供一致地使用命名和/或目录服务作为Java API,该接口可用于绑定对象、查找或查询对象,以及检测同一对象的变化。

虽然JNDI的使用包括各种受支持的命名和目录服务列表,但在本教程中,我们将在探索JNDI的API时重点介绍JDBC。

2. JNDI说明

使用JNDI的任何工作都需要了解底层服务以及可访问的实现。例如,数据库连接服务调用特定的属性和异常处理。

但是,JNDI的抽象将连接配置与应用程序分离。

让我们探索Name和Context,它们包含JNDI的核心功能。

2.1 Name接口

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

Name接口提供了管理组件名称和JNDI名称语法的能力。字符串的第一个标记代表全局上下文,之后添加的每个字符串代表下一个子上下文:

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

我们的输出如下所示:

java:comp
env
jdbc

正如我们所见,/是Name子上下文的分隔符。现在,让我们添加一个子上下文:

objectName.add("example");

然后测试我们的添加:

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

2.2 Context接口

Context包含命名和目录服务的属性。在这里,让我们使用Spring中的一些辅助代码来方便地构建Context:

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

Spring的SimpleNamingContextBuilder创建一个JNDI提供程序,然后使用NamingManager激活构建器:

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

最后,JndiTemplate帮助我们访问InitialContext。

3. JNDI对象绑定和查找

现在我们已经了解了如何使用Name和Context,让我们使用JNDI来存储JDBC DataSource:

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

3.1 绑定JNDI对象

我们有了上下文,因此让我们将对象绑定到它:

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

通常,服务应该在目录上下文中存储对象引用、序列化数据或属性。这完全取决于应用程序的需求。

请注意,以这种方式使用JNDI并不常见。通常,JNDI与在应用程序运行时外部管理的数据交互。

但是,如果应用程序已经可以创建或找到它的DataSource,那么使用Spring连接它可能会更容易。相反,如果应用程序之外的某些东西绑定了JNDI中的对象,那么应用程序可以使用它们。

3.2 查找JNDI对象

让我们查找DataSource:

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

然后让我们测试以确保DataSource符合预期:

assertNotNull(ds.getConnection());

4. 常见的JNDI异常

使用JNDI有时可能会导致运行时异常,以下是一些常见的。

4.1 NameNotFoundException异常

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)

我们应该注意到堆栈跟踪包含所有绑定的对象,这对于跟踪异常发生的原因很有用。

4.2 NoInitialContextException异常

与InitialContext的任何交互都可能抛出NoInitialContextException:

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

我们应该注意到JNDI的这种使用是有效的,因为我们之前使用过它。但是,这次没有JNDI上下文提供程序,会抛出一个异常:

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)

5. JNDI在现代应用程序架构中的作用

虽然JNDI在轻量级、容器化的Java应用程序(如Spring Boot)中发挥的作用较小,但还有其他用途。仍然使用JNDI的三种Java技术是JDBCEJBJMS。它们在Java企业应用程序中都有广泛的用途。

例如,单独的DevOps团队可以管理环境变量,例如所有环境中敏感数据库连接的用户名和密码。可以在Web应用程序容器中创建JNDI资源,将JNDI用作适用于所有环境的一致抽象层。

此设置允许开发人员创建和控制用于开发目的的本地定义,同时通过相同的JNDI名称连接到生产环境中的敏感资源。

6. 总结

在本教程中,我们介绍了使用Java命名和目录接口连接、绑定和查找对象。我们还研究了JNDI抛出的常见异常。

最后,我们介绍了JNDI如何适应现代应用程序架构。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章