JNDI(Java Naming and Directory Interface,即Java命名和目录接口)是JavaEE平台提供的查找和访问命名服务的一组标准接口。这些服务可以是各种形式的,包括常用的数据源(DataSource)、消息连接工厂(ConnectionFactory)以及其他一些自定义的命名服务等。通过JNDI,开发人员可以编写灵活、可移植的Java应用程序,而无需关心底层的具体实现。
使用JNDI具有如下优势:
松耦合:通过使用JNDI可以将对象的创建和配置与应用程序的代码分离开来,这样做可以降低组件之间的耦合度,使得应用程序更容易维护和修改。当需要更改对象实现类或者配置时,只需更新JNDI配置,而不必修改应用程序代码。
灵活性:使用JNDI可以实现运行时的对象查找和动态绑定,应用程序可以动态地获取不同的实现类实例,而无需修改代码。这种灵活性可以帮助应对不同的环境或者配置需求,使得应用程序更具可移植性。
资源共享和重用:通过将对象配置为JNDI资源,可以实现资源的共享和重用。多个组件或者应用程序可以共享同一个JNDI资源,从而节省系统资源并提高系统的利用率。
集中管理:通过集中管理对象的配置和生命周期,可以更方便地进行管理和监控。JNDI提供了统一的管理接口和管理工具,使得管理人员可以更轻松地监控和管理应用程序中的对象。
前面这么说可能比较抽象,我们看下面的例子。
假如我们有若干JavaEE应用程序需要连接数据库,此时我们在每个工程内手动创建了数据源然后部署到JavaEE应用服务器内,它们运行的很好。然而不久之后情况发生了变化,我们必须将数据源切换到另一个数据库,我们就不得不分别修改每个工程的数据源配置。
但如果使用JNDI,我们将数据源配置在JavaEE应用服务器上,我们给数据源统一起个名叫java:/comp/env/jdbc/mysql
,每个工程都通过这个名字引用数据源,数据源实例由JavaEE应用服务器注入到我们的工程,此时即使需要修改数据库配置,我们修改一处即可,不必再每个工程内修改了。此外,假如我们有多个数据源,这种情况下,我们集中使用JNDI配置相比在每个工程分别手动配置更是要方便的多。
简而言之,JNDI接口规范了这样的一个功能:给一些可复用的服务起个名字,使用时根据这些名字找到服务的实例。
虽然JNDI的概念很简单,但要想使用JNDI还真没那么容易。JNDI只是一组接口,它需要一个实现,这个实现负责JNDI配置具体如何存储,通常来说JavaEE应用服务器如Wildfly、WebSphere等负责实现这部分功能,因此JNDI也通常仅用于JavaEE工程。
不过这里我们先不考虑JavaEE的情况,我们使用最简单的Java工程体验JNDI的基础用法,这需要一个简易的JNDI实现,我们使用simple-jndi
这个第三方库,它实现了JNDI,通常用于调试和JNDI相关的程序而不必启动JavaEE应用服务器。
我们引入如下Maven依赖。
<dependency>
<groupId>com.github.h-thurow</groupId>
<artifactId>simple-jndi</artifactId>
<version>0.24.0</version>
</dependency>
除此之外,还需要一个jndi.properties
配置文件放在类路径下。
java.naming.factory.initial=org.osjava.sj.SimpleContextFactory
此时我们就可以在普通Java工程中体验JNDI的使用了,下面是一些例子代码。
package com.gacfox.demo;
import javax.naming.*;
public class Main {
public static void main(String[] args) {
Context context = null;
try {
context = new InitialContext();
register(context);
list(context);
getComponent(context);
} catch (NamingException e) {
throw new RuntimeException(e);
} finally {
if (context != null) {
try {
context.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
}
/**
* 注册JNDI
*
* @param context JNDI上下文
* @throws NamingException JNDI异常
*/
public static void register(Context context) throws NamingException {
DemoComponent demoComponent = new DemoComponent("Hello, JNDI!");
context.bind("java:/comp/env/demoComponent", demoComponent);
}
/**
* 列出JNDI中注册的信息
*
* @param context JNDI上下文
* @throws NamingException JNDI异常
*/
public static void list(Context context) throws NamingException {
NamingEnumeration<NameClassPair> enumeration = context.list("");
while (enumeration.hasMore()) {
NameClassPair pair = enumeration.next();
System.out.println(pair.getName() + ": " + pair.getClassName());
}
}
/**
* 获取注册到JNDI的组件
*
* @param context JNDI上下文
* @throws NamingException JNDI异常
*/
public static void getComponent(Context context) throws NamingException {
DemoComponent demoComponent = (DemoComponent) context.lookup("java:/comp/env/demoComponent");
demoComponent.sayHello();
}
}
代码中演示了JNDI信息的注册、迭代和获取。
JavaEE工程中,JNDI通常用于管理那些由应用服务器统一维护的组件,如数据源、JMS连接工厂等,引用这类资源实际上不需要我们手动编写context.lookup()
,更推荐的方式是直接使用@Resource
注解。下面例子代码从应用服务器中获取了JMS连接工厂和队列的实例。
package com.gacfox.demo.demoweb;
import javax.annotation.Resource;
import javax.jms.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "ProducerServlet", urlPatterns = "/producer")
public class ProducerServlet extends HttpServlet {
@Resource(lookup = "java:/ConnectionFactory")
private ConnectionFactory connectionFactory;
@Resource(lookup = "java:/jms/queue/DemoQueue")
private Queue queue;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try (JMSContext context = connectionFactory.createContext()) {
JMSProducer producer = context.createProducer();
TextMessage textMessage = context.createTextMessage("Hello, JMS!");
producer.send(queue, textMessage);
}
}
}
上面例子中,JMS相关的配置都在Wildfly应用服务器中维护,我们的工程无需关心,我们只需知道JNDI名字即可引用配置好的资源。