JNDI(Java Naming Directory Interface),Java命名和目录接口,是SUN公司提供的一种标准的Java命名系统接口。通过调用JNDI的API应用程序可以定位资源和其他程序对象。
JNDI可访问的现有目录及服务包括:JDBC(Java数据库连接)、LDAP(轻型目录访问协议)、RMI(远程方法调用)、DNS(域名服务)、NIS(网络信息服务)、CORBA(公共对象请求代理系统结构)
JNDI包括命名服务(Naming Service)和目录服务(Directory Service)。
是一种通过名称来查找实际对象的服务。例如在RMI中,Naming.lookup方法通过查找名称来获取远程对象的代理类
相关概念:
Name:名称。要么在命名系统中查找对象,需要提供对象的名称,如java:comp/env/jdbc/DataSource
Naming Convention:命名规范。一个命名系统中的所有名称必须遵循的语法规范
Binding:绑定。一个名称和一个对象的关联,如bind("payService", paymentObj)
Reference:引用。一些命名服务系统不是直接存储对象,而是保存对象的引用。引用包含了如何访问实际对象的信息。如<Reference class="com.Evil" url="http://attacker/"/>
Address:地址。引用通常用一个或多个地址(通信端口)来表示,如ldap://192.168.1.1:389
Context:上下文。一个上下文是一系列名称和对象的绑定的集合。一个上下文中的名称可以绑定到一个具有相同命名规范的上下文中,称之为子上下文(subcontext)。例如:在文件系统中,/usr是一个Context,/usr/bin是usr的subcontext
Naming System:命名系统。一个相同类型的上下文集合,例如整个 DNS 系统
Namespace:命名空间。一个命名系统的所有名称的集合,例如DNS的所有域名
Atomic Name:原子名。一个简单基本结构,如文件名config.txt
Compound Name:混合名。由多个原子名一起构成的名称,如文件路径/home/user/.bashrc
Composite Name:复合名称。是跨越多个命名系统的名称,如ldap://ldap.com/cn=user,dc=com+file:/shared.txt
是命名服务的扩展,除了提供名称和对象的关联,还允许对象具有属性。目录服务中的对象称为目录对象,目录对象可以跟属性关联,一个目录是由相关联的目录对象组成的系统。目录服务提供创建、添加、删除目录对象以及修改目录对象属性等操作。
相关概念:
Attribute:属性。一个目录对象可以包含属性。一个属性具有一个属性标识符和一系列属性值
Search Filter:查找过滤器。通常还提供通过目录对象的属性来查找对象的操作,这种的查找一般通过规定的表达式来表示,称之为查找过滤器。
远程接口:
package rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface RMIInterface extends Remote {
public String Hello() throws RemoteException;
}
远程对象:
package rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class RMItest extends UnicastRemoteObject implements RMIInterface {
protected RMItest() throws Exception{
super();
}
@Override
public String Hello() throws RemoteException {
System.out.println("hello");
return "b1uel0n3";
}
}
开启服务:
package rmi;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class server {
//rmi服务地址
public static String HOST="127.0.0.1";
public static int PORT=5432;
public static String RMI_PATH="/b1uel0n3";
public static String RMI_NAME="rmi://"+HOST+":"+PORT+RMI_PATH;
public static void main(String[] args) throws Exception {
//注册RMI端口
LocateRegistry.createRegistry(PORT);
//绑定远程对象
RMIInterface o=new RMItest();
Naming.rebind(RMI_NAME,o);
System.out.println("RMI服务在:"+RMI_NAME);
}
}
在访问JNDI目录服务时会通过预先设置好环境变量访问对应的服务,这里主要通过Context接口实现的,在Context接口中定义的变量,分别用到了INITIAL_CONTEXT_FACTORY
和PROVIDER_URL
:
INITIAL_CONTEXT_FACTORY
:保存环境属性名称的常量,用于指定要使用的初始上下文工厂,即指定 JNDI 服务提供者的入口类。
PROVIDER_URL
:保存环境属性名称的常量,用于指定服务提供者要使用的配置信息,该属性应包含一个URL字符串
先设置环境变量:
Hashtable env = new Hashtable();
env.put(INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(PROVIDER_URL, "rmi://127.0.0.1:5432");
这里相当于告诉JNDI要连接的是RMI注册表,它在127.0.0.1的5432端口
初始化上下文需要用到InitialContext对象来为我们获取命名服务资源,JNDI也提供InitialDirContext对象为我们获取目录服务资源
这里初始化上下文,传入我们设置好的环境变量:
InitialContext ctx = new InitialContext(env);
传入的是Hashtable对象
如果不指定环境变量的话:
InitialContext ctx = new InitialContext();
JNDI就会自动搜索系统属性System.getProperty()、applet参数和应用程序资源文件jndi.properties
所以也可以通过System.getProperty()设置环境变量:
System.getProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
System.getProperty(Context.PROVIDER_URL, "rmi://127.0.0.1:5432");
InitialContext ctx = new InitialContext();
利用提供的lookup方法,通过查询名字获取远程对象代理类:
RMIInterface o = (RMIInterface) ctx.lookup("b1uel0n3");
最后调用远程方法即可:
System.out.printf(o.Hello());
完整代码:
package rmi;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Hashtable;
public class Client {
public static void main(String[] args) throws Exception {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:5432");
InitialContext ctx = new InitialContext(env);
RMIInterface o=(RMIInterface)ctx.lookup("b1uel0n3");
System.out.printf(o.Hello());
}
}
与rmi一样:
package rmi;
import javax.naming.Context;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;
public class Client {
public static void main(String[] args) throws Exception {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
env.put(Context.PROVIDER_URL, "dns://114.114.114.114");
Context ctx = new InitialDirContext(env);
Object o=ctx.lookup("www.baidu.com");
System.out.println(o);
}
}
这里我们打算使用DNS服务,从dns://114.114.114.114这个dns服务器上查询www.baidu.com域名对应的IP地址。但这里返回的是一个对象,由于我们用的是JNDI目录服务,而目录服务允许目录对象具有属性,那么我们就能通过目录服务获取ip这个属性值:
DirContext ctx = new InitialDirContext(env);
Attributes o= ctx.getAttributes("www.baidu.com", new String[]{"A"});
System.out.println(o);
DNS记录类型:
记录类型 | 含义 | 示例 |
---|---|---|
A | IPv4 地址记录 | www.baidu.com → 14.215.177.38 |
AAAA | IPv6 地址记录 | www.example.com → 2001:db8::1 |
CNAME | 别名记录 | www → real-server.example.com |
MX | 邮件交换记录 | @ → mail.example.com |
TXT | 文本记录 | "v=spf1 include:_spf.google.com ~all" |
NS | 域名服务器记录 | @ → ns1.alidns.com |
获得ipv4地址,通过这个地址可以访问百度
完整代码:
package rmi;
import javax.naming.Context;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;
public class Client {
public static void main(String[] args) throws Exception {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
env.put(Context.PROVIDER_URL, "dns://114.114.114.114");
DirContext ctx = new InitialDirContext(env);
Attributes o= ctx.getAttributes("www.baidu.com", new String[]{"A"});
System.out.println(o);
}
}
前面rmi和dns例子中我们都是通过设置INITIAL_CONTEXT_FACTORY和PROVIDER_URL的值来告诉JNDI我们要调用何种服务,从哪里获取这个服务。
而在Context.lookup()方法的参数中,用户可以指定自己的查找协议。JNDI会通过用户输入来动态识别要调用的服务以及路径:
package rmi;
import javax.naming.Context;
import javax.naming.InitialContext;
public class Client {
public static void main(String[] args) throws Exception {
String str="rmi://127.0.0.1:5432/b1uel0n3";
Context ctx = new InitialContext();
RMIInterface o=(RMIInterface)ctx.lookup(str);
System.out.println(o.Hello());
}
}
这里我们并没有设置相应的环境变量来初始化Context,但是JNDI仍旧通过lookup()的参数识别出要调用的服务以及路径
默认支持自动转换的协议有:
这里拿rmi为例子
InitialContext ctx = new InitialContext(env);
此时env不为空,调用enviroment.clone()方法:
重新赋值,这里对原始环境变量没用改动,随后调用init方法,传入环境变量:
调用了ResourceManager的静态方法getInitialEnvironment,传入环境变量,看文件名像时获取初始环境变量:
定义了一个PROPS变量,里面记录了Context接口的常量信息
然后获取APPLET属性,由于我们传入的环境变量只记录了Context.INITIAL_CONTEXT_FACTORY和Context.PROVIDER_URL的信息,所以这里applet参数为null
helper为VersionHelper对象,这里调用VersionHelper.getJndiProperties静态方法:
最后返回了长度为7的字符串数组,且都为null,似乎更像是在初始化
初始化Context接口每个常量的值,这里通过三个方面来获取值,首先从传入的环境变量找值,如果没用则通过applet,如果还没有就通过系统属性来获取值,这也正好解释了前面初始化上下文时可通过不传入env来设置环境变量
初始化环境变量后回到initialContext.init方法:
设置了初始化上下文工厂就会调用getDefaultInitCtx()方法,获取默认初始化上下文,先看下此时的变量:
跟进getDefaultInitCtx()方法:
调用了NamingManager.getInitialContext静态方法,传入myProps变量:
获取用户设置的工厂类
如果不为null就加载并实例化这个类:
最后调用RegistryContextFactory.getInitialContext方法,传入环境变量:
先拷贝参数值给var1,再调用URLToContext(getInitCtxURL(var1), var1),先看getInitCtxURL方法:
获取环境变量指定的服务地址,这里就是rmi://127.0.0.1:5432
然后看URLToContext方法:
这里实例化了一个rmiURLContextFactory,这是一个专门用于处理 RMI URL 的上下文工厂,然后调用了getObjectInstance方法传入了服务地址和环境变量:
调用 getUsingURL方法:
实例化rmiURLContext对象,传入环境变量:
然后调用var2.lookup(var0);方法,即rmiURLContext父类GenericURLContext.lookup方法,传入服务地址:
最终调用RegistryContext.lookup方法:
根据上述过程中获取的信息初始化了一个新的RegistryContext,RegistryCo