JNDI基本应用
JNDI是java naming and directory interface(JAVA命名和目录接口)的英文缩写,它是为java应用程序提供命名和目录访问的服务器API
1. 命名的概念与应用
JNDI中的命名(Naming),就是将java对象以某个名称的形式绑定(binding)到一个容器环境(Context)中,以后调用容器环境(Context)的查找(lookup)方法又可以查出某个名称所绑定的java对象。在实际项目中,通常是由系统程序或框架程序先将资源对象绑定到JNDI环境中,以后在该系统或框架中运行的模块程序就可以从JNDI环境中找到这些资源对象了。例如:Tomcat服务器在启动时可以创建一个连接到某种数据库系统的数据源(DataSource)对象,并将该数据源对象绑定到JNDI环境中,以后再这个Tomcat服务器中运行的Servlet和jsp程序就可以从JNDI环境中查出这个数据源(DataSource)对象,并进行使用,而不需要再关心这个数据源对象是如何创建出来的,这种方式极大第增强了系统的维护性,当数据库的连接发生变更时,这只是Tomcat服务器管理员一个人需要关心的事,而所有开发人员无需关心。
容器环境(Context)本身是一个java对象,它可以通过一个名称绑定到另一个容器环境中。将一个Context对象绑定到另一个Context对象中,这就形成了一种父子级联的关系,多个Context对象最终可以级联成一种树状结构,书中的每个Context对象中可以绑定若干个java对象。如下图所示:
图中的每个方框分别代表一个Context对象,他们绑定的名称分为a,b,c,d,e, b和c是a的子Context, d是b的子Context,e又是d的子Context.图中各个方框内的每个小椭圆分别表示一个java对象,它们也都有一个绑定的名称,这些绑定名称分别为dog, pig, sheep等。在同一个Context中不能绑定两个相同的名称的java对象,在不同的Context中可以出现同名的绑定对象。可见,Context树的级联结构于文件系统中的目录结构非常相似,Context与其中绑定的java对象的关系也非常类似于文件系统中的目录与文件的关系。
从图中还可以看到,要想得到Context树中的一个java对象,首先要得到所在的Context对象,只要得到了一个Context对象,就可以调用它的lookup查询方法来获得其绑定的java对象。另外,调用某个Context对象的lookup方法也可以获得Context树中的任意一个Context对象,这只需要在lookup方法中指定相应的Context路径即可。在JNDI中不存在着”根”Context的概念,也就是说进行JNDI操作不是从一个根Context对象开始的,而是可以从树中任意一个Context开始。无论如何,程序必须获得一个作为操作入口的Context对象后才能执行各种JNDI命名操作,为止,JNDI API中提供了一个InitialContext类来创建用作JNDI命名操作入口的Context对象。Context是一个接口,Context对象实际上是其某个实现类的实例对象,选择这个具体的Context实现类并创建其实例对象的过程是由一个Context工厂类来完成的,这个工厂类的类名可以通过JNDI的环境属性java.naming.factory.initial指定,也可以根据Context的操作方法的url参数的Scheme来选择。
2. 目录的概念与应用
JNDI中的目录(Directory)与文件系统中的目录概念上有很大的不同,JNDI中的目录是指一个对象的所有信息保存到一个容器环境中。JNDI的目录(Directory)原理与JNDI的命名(Naming)原理非常相似,主要区别在于目录容器环境中保存的是对象的属性信息,而不是对象本身,所以,目录提供的是对属性的各种操作。事实上,JNDI的目录(Directory)与命名往往是结合在一起使用的,JNDI API中提供的代表目录容器环境的类为DirContext, 它是Context的子类,显然除了能完成目录相关操作外,也能完成所有命名(Naming)操作。DirContext是对Context的扩展,它在Context的基础上增加了对目录属性的操作功能,可以在其中绑定对象的属性信息和查找对象属性信息。JNDI中的目录结构示意图如下所示:
图中的每个最外层的方框分别代表一个DirContext对象,它们绑定的名称分别为啊,吧, b是a的子DirContext。图中的各个最外层的方框内的每个小椭圆分别代表一个java对象,各个里层的方框分别代表一个对象的属性。从名称为a的DirContext中的内容可以看到,一个DirContext容器环境中可以绑定对象自身,也可以绑定对象的属性信息,绑定对象和绑定的属性是完全独立的两个事物,即使它们的绑定名称相同,它们的操作也是完全独立的。另外一个属性可以有多个属性值,如dog对象的category属性值设置了两个属性值,meat和pet.从名称为b的DirContext中的内容可以看到,一个DirContext容器环境中也可以只绑定对象的属性信息,而不绑定任何对象自身。与Context的操作原理类似,JNDI API中提供了一个InitialContext类来创建用做JNDI命名与目录属性操作的入口DirContext对象。
下面是一个javamail邮件开发中,将会话信息存放到JNDI容器环境中的示例:
import cn.itcast.domain.User;
public class RegisterServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try{
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
User user = new User();
user.setEmail(email);
user.setPassword(password);
user.setUsername(username);
System.out.println("把用户信息注册到数据库中");
Sendmail send = new Sendmail(user);
send.start();//起一个定时器,发送一封邮件,目的是避免因网络延时导致用户注册体验差
request.setAttribute("message", "恭喜您,注册成功,我们已经发了一封带了注册信息的电子邮件,请查收,如果没有收到,可能是网络原因,过一会儿就收到了!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
}catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "注册失败!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
public class Sendmail extends Thread {
private String from = "itcast1111@sohu.com";
private String username = "itcast1111";
private String password = "123456";
private String host = "smtp.sohu.com";
private User user; //aaa@sina.com
public Sendmail(User user){
this.user = user;
}
@Override
public void run() {
try{
/*Properties prop = new Properties();
prop.setProperty("mail.host", host);
prop.setProperty("mail.transport.protocol", "smtp");
prop.setProperty("mail.smtp.auth", "true");
Session session = Session.getInstance(prop);*/
//使用JNDI容器来保存session
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
Session session = (Session) envCtx.lookup("mail/Session"); //Session s = session
session.setDebug(true);
Transport ts = session.getTransport();
ts.connect(host, username, password);
Message message = makeMessage(session,user);
ts.sendMessage(message, message.getAllRecipients());
ts.close();
}catch (Exception e) {
throw new RuntimeException(e);
}
}
public Message makeMessage(Session session,User user) throws Exception{
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail()));
message.setSubject("ԃ»§ע²┊¼�
String info = "¹§ϲźע²ᴉ¹¦£¬źµœû§Ļ£º" + user.getUsername() + ",źµŃ݂룺" + user.getPassword() + "£¬ȫΗʆ±£¹ܣ¬ɧԐϊ͢ȫjϵθվ¿ͷ�
message.setContent(info, "text/html;charset=UTF-8");
message.saveChanges();
return message;
}
}
下面这个示例是利用JNDI容器查询DNS域名信息。
/*
* 第一个参数指定要查询的域或主机名,第二个参数指定查询的DNS服务器
*/
String[] arg = {"sohu.com"};
String domain = arg[0];
String dnsServer = arg.length<2 ? "":("//"+arg[1]);
//通过环境属性来指定Context的工厂类
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
env.put(Context.PROVIDER_URL, "dns:"+dnsServer);
DirContext ctx = new InitialDirContext(env);
//分别获取包含所有属性和只包含Mx属性的attributes对象
Attributes attrsAll = ctx.getAttributes(domain);
Attributes attrsMx = ctx.getAttributes(domain, new String[]{"MX"});
/*
* 上面这段代码可以使用下面这段代码替换,通过查询URL中的Scheme信息来自动选择Context的工厂类
*/
/* DirContext ctx = new InitialDirContext();
Attributes attrsAll = ctx.getAttributes("dns:"+dnsServer+"/"+domain);
Attributes attrsMx = ctx.getAttributes("dns:"+dnsServer + "/"+domain, new String[]{"MX"});
*/
System.out.println("打印出域"+domain + "的Attributes对象中的信息:");
System.out.println(attrsAll);
System.out.println("----------------------------");
System.out.println("打印只检索域"+domain+"的MX记录的Attributes对象:");
System.out.println(attrsMx);
System.out.println("------------------------");
System.out.println("逐一打印出Attributes对象中的各个属性:");
NamingEnumeration attributes = attrsAll.getAll();
while(attributes.hasMore()){
System.out.println(attributes.next());
}
System.out.println("------------------------");
//直接调用get方法从attrsMx集合检索MX属性
System.out.println("直接检索Attributes对象中的索MX属性:");
Attribute attrMx = attrsAll.get("MX");
System.out.println(attrMx);
System.out.println("------------------------");
//获取Mx属性的第一个值
System.out.println("获取Mx属性中的第一个值:");
String recordMx = (String)attrMx.get();
System.out.println(recordMx);
//从Mx属性的第一个值中提取邮件服务器地址
System.out.println("从MX属性值中提取邮件服务器地址: ");
String smtpServer = recordMx.substring(
recordMx.indexOf(" ")+1);
System.out.println(smtpServer);
结果:
打印出域sohu.com的Attributes对象中的信息:
{ns=NS: ns1.sohu.com., dns.sohu.com., ns4.sohu.com., ns2.sohu.com., mx=MX: 10 sohumx.h.a.sohu.com., 5 sohumx1.sohu.com., 5 sohumx2.sohu.com.}
----------------------------
打印只检索域sohu.com的MX记录的Attributes对象:
{mx=MX: 5 sohumx2.sohu.com., 10 sohumx.h.a.sohu.com., 5 sohumx1.sohu.com.}
------------------------
逐一打印出Attributes对象中的各个属性:
NS: ns1.sohu.com., dns.sohu.com., ns4.sohu.com., ns2.sohu.com.
MX: 10 sohumx.h.a.sohu.com., 5 sohumx1.sohu.com., 5 sohumx2.sohu.com.
------------------------
直接检索Attributes对象中的索MX属性:
MX: 10 sohumx.h.a.sohu.com., 5 sohumx1.sohu.com., 5 sohumx2.sohu.com.
------------------------
获取Mx属性中的第一个值:
10 sohumx.h.a.sohu.com.
从MX属性值中提取邮件服务器地址:
sohumx.h.a.sohu.com.
附录:
JNDI 查找及其关联的引用
JNDI 查找名称 |
关联的引用 |
---|---|
java:comp/env |
应用程序环境项 |
java:comp/env/jdbc |
JDBC 数据源资源管理器连接工厂 |
java:comp/env/ejb |
EJB 引用 |
java:comp/UserTransaction |
UserTransaction 引用 |
java:comp/env/mail |
JavaMail 会话连接工厂 |
java:comp/env/url |
URL 连接工厂 |
java:comp/env/jms |
JMS 连接工厂和目标 |
java:comp/ORB |
应用程序组件之间共享的 ORB 实例 |