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技术是JDBC、EJB和JMS。它们在Java企业应用程序中都有广泛的用途。
例如,单独的DevOps团队可以管理环境变量,例如所有环境中敏感数据库连接的用户名和密码。可以在Web应用程序容器中创建JNDI资源,将JNDI用作适用于所有环境的一致抽象层。
此设置允许开发人员创建和控制用于开发目的的本地定义,同时通过相同的JNDI名称连接到生产环境中的敏感资源。
6. 总结
在本教程中,我们介绍了使用Java命名和目录接口连接、绑定和查找对象。我们还研究了JNDI抛出的常见异常。
最后,我们介绍了JNDI如何适应现代应用程序架构。
与往常一样,本教程的完整源代码可在GitHub上获得。