Java
命名和目录接口(Java Naming and Directory Interface
,缩写JNDI
),是Java
的一个目录服务应用程序接口(API
),通过名称将资源与服务进行关联。
(模仿Windows
注册表
JNDI
包括Naming Service
和Directory Service
Naming Service
(命名服务):是将名称与值相关联的实体,称为绑定
。它提供了一种使用find
或search
操作来根据名称查找对象的便捷方式。 通过名称去获取对应的服务。
Directory Service
(目录服务):是一种特殊的Naming Service
,它允许存储和搜索目录对象
,一个目录对象
不同于一个通用对象,目录对象
可以与属性关联,因此,目录服务提供了对象属性进行操作功能的扩展。用类似目录的方式来存取服务。
命名服务
可以理解类似于HashTable
的<K,V> pair
的方式去查找调用资源的。Key
就是名字,Value
则是资源。命名服务是利用key
获取到value
。
目录服务
则是通过描述这项资源,来得到对应的名字来调用这项服务。
Key 键名 | Value 数据 |
---|---|
字符串 | 在JNDI 中存的是对象 |
JNDI
服务可以:访问文件系统中的文件、定位远程RMI
注册的对象,访问LDAP
这样的目录服务等等。
光看概念不是好理解,下面看看怎么用JNDI
JNDI
中需要用到Context
Context
在java
中译为上下文,是用来存储系统的一些初始化信息
实际上Context
所代表的意义是一些:公用信息、环境、容器等
JNDI
客户端调用方式:
// 指定需要查找name名称
String jndiName= "jndiName";
// 初始化默认环境
Context context = new InitialContext();
// 查找该name的数据
context.lookup(jndiName);
一些常见的JNDI name
的例子:
xxxxxxxxxx
java:comp/env/<jndiname>
jdbc://<domain>:<port>
rmi://<domain>:<port>
ldap://<domain>:<port>
......
JNDI
的查找一般使用lookup()
方法如registry.lookup(jndiName)
。
JNDI
与RMI
配合使用(后面还有详细的脚本,这里手动设置了对应服务的工厂
以及对应服务的PROVIDER_URL
):
ximport javax.naming.Context;
import javax.naming.InitialContext;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Hashtable;
public class Test {
public static void main(String args[]) throws Exception{
Hashtable env = new Hashtable();
// 端口注册
LocateRegistry.createRegistry(9000);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:9000/");
// 创建上下文ctx
Context ctx = new InitialContext(env);
// 将名称Secret与SecretImpl对象绑定,这里底层也是调用的rmi的registry去绑定
ctx.bind("Secret", new SecretImpl());
System.out.println("Running~");
// 通过名称查找对象
Secret s = (Secret)ctx.lookup("Secret");
// 调用secret()函数获取内容
System.out.println(s.secret());
}
}
INITIAL_CONTEXT_FACTORY
的值用于指定Name Service Provider Factory
xxxxxxxxxx
File System com.sun.jndi.fscontext.RefFSContextFactory
LDAP com.sun.jndi.ldap.LdapCtxFactory
RMI com.sun.jndi.rmi.registry.RegistryContextFactory
CORBA com.sun.jndi.cosnaming.CNCtxFactory
DNS com.sun.jndi.dns.DnsContextFactory
在使用JNDI
之前,需要先获取JNDI
的提供者,并在系统注册它。
与JNDI
相关的系统属性在javax.naming.Context
中定义,常用的属性:
java.naming.factory.initial
,服务提供者用来创建InitialContext
的类名。java.naming.provider.url
,用来配置InitialContext
的初始url
java.naming.factory.object
,用来创建name-to-object
映射的类,用于NameClassPair
和References
。java.naming.factory.state
,用来创建jndi state
的类对于目录服务
,由于一般需要安全设置,还通常使用:
java.naming.security.authentication
,安全类型,三个值:none
,simple
或strong
。java.naming.security.principal
,认证信息。java.naming.security.credentials
,证书信息。java.naming.security.protocol
,安全协议名。注册方式:使用System.setProperty
注册,如果程序不显示说明,java
会在classpath
内查找jdni.properties
文件来完成注册。
使用方式:命名服务
由InitialContext
开始,目录服务
则使用InitialDirContext
。它们分别实现了Context
和DirContext
,这两个接口分别对应命名服务
和目录服务
的接口,也是JNDI
中最重要的两个接口。
上面JNDI与RMI配合使用
的脚本是将Server.java
与Client.java
合在一起并且手动设置了对应服务的工厂
以及对应服务的PROVIDER_URL
Server.java
:
xxxxxxxxxx
import javax.naming.Context;
import javax.naming.InitialContext;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Server {
public static void main(String args[]) throws Exception{
// 以9000端口作为LocateRegistry接收客户端请求的端口,并注册服务的映射关系
Registry registry = LocateRegistry.createRegistry(9000);
Context ctx = new InitialContext();
SecretImpl s = new SecretImpl();
// 绑定Secret为s
ctx.bind("rmi://127.0.0.1:9000/Secret",s);
System.out.println("Running~");
}
}
Client.java
:
xxxxxxxxxx
import javax.naming.Context;
import javax.naming.InitialContext;
public class Client {
public static void main(String args[]) throws Exception{
Context ctx = new InitialContext();
// 必须用Secret,否则报错$Proxy0 cannot be cast to ...
// 查找对象
Secret s = (Secret)ctx.lookup("rmi://127.0.0.1:9000/Secret");
System.out.println(s.secret());
}
}
SecretImpl.java
:
xxxxxxxxxx
import java.rmi.server.UnicastRemoteObject;
// 继承UnicastRemoteObject
public class SecretImpl extends UnicastRemoteObject implements Secret{
public SecretImpl() throws Exception{}
public String secret() throws Exception{
return "Some_secret_data";
}
}
Secret.java
:
xxxxxxxxxx
import java.rmi.Remote;
// 继承Remote
public interface Secret extends Remote{
String secret() throws Exception;
}
运行Server
后再执行Client
可以获取到Some_secret_data
第一层: Java 应用,访问JNDI的代码;
第二层: JNDI API :统一的命名和目录服务接口
第三层: Naming Manager : JNDI 管理器
第四层: JNDI SPI (Server Provider Interface ) :用于构建 JNDI 实现的框架,能够动态的插入命名和目录服务提供商的产品;
第五层:命名和目录服务提供商的产品,例如:LDAP, DNS, NIS, NDS, RMI 等
在SPI级别,JVM将允许从远程代码库加载类并实施安全性。管理器的安装取决于特定的提供程序(例如在上面说到的RMI那些利用方式就是SPI级别,必须设置安全管理器):
Provider | Property to enable remote class loading | 是否需要强制安装Security Manager |
---|---|---|
RMI | java.rmi.server.useCodebaseOnly = false (JDK 6u45、JDK 7u21之后默认为true) | 需要 |
LDAP | com.sun.jndi.ldap.object.trustURLCodebase = true(default = false) | 非必须 |
CORBA | 需要 |
但是,在Naming Manager层放宽了安全控制。解码JNDI命名时始终允许引用从远程代码库加载类,而没有JVM选项可以禁用它,并且不需要强制安装任何安全管理器,例如上面说到的命名引用那种方式。
void bind(String sName,Object object);
―― 绑定:把名称同对象关联的过程
void rebind(String sName,Object object);
―― 重新绑定:用来把对象同一个已经存在的名称重新绑定
void unbind(String sName);
―― 释放:用来把对象从目录中释放出来
lookup(String name);
―― 查找:返回目录总的一个对象
void rename(String sOldName,String sNewName);
―― 重命名:用来修改对象名称绑定的名称
NamingEnumeration listBinding(String sName);
―― 清单:返回绑定在特定上下文中对象的清单列表
NamingEnumeration list(String sName);
―― 清单:返回绑定在特定上下文中对象的清单列表