JNDI的基本应用

42 篇文章 0 订阅

一、 JNDI的基本应用

JNDI是Java Naming and Directory Interface(JAVA命名和目录接口)的英文简写,它是为JAVA应用程序提供命名和目录访问服务的API(Application Programing Interface,应用程序编程接口)。

1.命名的概念与应用

JNDI中的命名(Naming),就是将Java对象以某个名称的形式绑定(binding)到一个容器环境(Context)中,以后调用容器环境(Context)的查找(lookup)方法又可以查找出某个名称所绑定的Java对象。读者也许会感到奇怪:自己创建一个Java对象,将其绑定到JNDI容器环境中后又查询出来,这有什么意思?在真实的项目应用中,通常是由系统程序或框加程序先将资源对象绑定到JNDI环境中,以后在该系统或框架中运行的模块程序就可以从JNDI环境中查找这些资源对象了。例如,Tomcat服务器在启动时可以创建一个连接到某种数据库系统的数据源(DataSource)对象,并将该数据源(DataSource)对象绑定到JNDI环境中,以后在这个Tomcat服务器中运行的Servlet和JSP程序就可以从JNDI环境中查询出这个数据源(DataSource)对象进行使用,而不用关心数据源(DataSource)对象是如何创建出来的,这种方式极大地增强了系统的可维护性,当数据库系统的连接参数发生变更时,这只是Tomcat系统管理员一个人要关心的事情,而与所有的应用程序开发人员无关。

容器环境(Context)本身也是一个Java对象,它也可以通过一个名称绑定到另一个容器环境(Context)中。将一个Context对象绑定到另外一个Context对象中,这就形成了一种父子级联关系,多个Context对象最终可以级联成一种树状结构,树中的每个Context对象中都可以绑定若干个Java对象,如图6.10所示。

图6.10

图6.10中的每个方框分别代表一个Context对象,它们绑定的名称分别为a、b、c、d、e,b和c是a的子Context,d是b的子Context,e又是d的子Context。图9.x中的各个方框内的每个小椭圆分别代表一个Java对象,它们也都有一个绑定的名称,这些绑定名称分别为dog、pig、sheet等,在同一个Context不能绑定两个相同名称的Java对象,在不同的Context中可以出现同名的绑定对象。可见,Context树的级联结构与文件系统中的目录结构非常类似,Context与其中绑定的Java对象的关系也非常类似于文件系统中的目录与文件的关系。从图6.10中可以看到,要想得到Context树中的一个Java对象,首先要得到其所在的Context对象,只要得到了一个Context对象,就可以调用它的查询(lookup)方法来获得其中绑定的Java对象。另外,调用某个Context对象的lookup方法也可以获得Context树中的任意一个Context对象,这只需要在lookup方法中指定相应的Context路径即可。在JNDI中不存在着“根”Context的概念,也就是说,执行JNDI操作不是从一个“根”Context对象开始,而是可以从Context树中的任意一个Context开始。无论如何,程序必须获得一个作为操作入口的Context对象后才能执行各种JNDI命名操作,为此,JNDI API中提供了一个InitialContext类来创建用作JNDI命名操作的入口Context对象。Context是一个接口,Context对象实际上是Context的某个实现类的实例对象,选择这个具体的Context实现类并创建其实例对象的过程是由一个Context工厂类来完成的,这个工厂类的类名可以通过JNDI的环境属性java.naming.factory.initial指定,也可以根据Context的操作方法的url参数的Schema来选择。

2.目录的概念与应用

JNDI中的目录(Directory)与文件系统中的目录概念有很大的不同,JNDI中的目录(Directory)是指将一个对象的所有属性信息保存到一个容器环境中。JNDI的目录(Directory)原理与JNDI的命名(Naming)原理非常相似,主要的区别在于目录容器环境中保存的是对象的属性信息,而不是对象本身,所以,目录提供的是对属性的各种操作。事实上,JNDI的目录(Directory)与命名(Naming)往往是结合在一起使用的,JNDI API中提供的代表目录容器环境的类为DirContext,DirContext是Context的子类,显然它除了能完成目录相关的操作外,也能完成所有的命名(Naming)操作。DirContext是对Context的扩展,它在Context的基础上增加了对目录属性的操作功能,可以在其中绑定对象的属性信息和查找对象的属性信息。JNDI中的目录(Directory)的结构示意图如图6.11所示。

图6.11

图6.11中的每个最外层的方框分别代表一个DirContext对象,它们绑定的名称分别为a、b,b是a的子DirContext。图6.11中的各个最外层的方框内的每个小椭圆分别代表一个Java对象,各个里层的方框分别代表一个对象的属性。从名称为a的DirContext中的内容可以看到,一个DirContext容器环境中即可以绑定对象自身,也可以绑定对象的属性信息,绑定的对象和绑定的属性是完全独立的两个事物,即使它们的绑定名称相同,它们的操作也是完全独立的。另外,一个属性可以有多个属性值,例如,dog对象的category属性就设置了两个属性值:meat和pet。从名称为b的DirContext中的内容可以看到,一个DirContext容器环境中也可以只绑定对象的属性信息,而不绑定任何对象自身。与Context的操作原理类似,JNDI API中提供了一个InitialDirContext类来创建用作JNDI命名与目录属性操作的入口DirContext对象。

3. 用于DNS查询的JNDI服务程序

JNDI API是面向应用程序开发人员的编程接口,它在运行时需要调用某个具体的JNDI服务程序,JNDI API与JNDI服务程序之间的关系,犹如JDBC与JDBC驱动程序之间的关系。从JDK 1.3开始,JDK中就集成了JNDI API,从JDK 1.4开始的版本又集成了用于DNS查询的JNDI服务程序,所以,如果我们使用JDK 1.4及更高的JDK版本来开发DNS信息查询程序时,不需要下载和安装JNDI API和用于DNS查询的JNDI服务程序。SUN公司提供的用于查询DNS信息的JNDI服务程序,将某个域名的DNS信息以属性的形式绑定到代表该域名的DirContext对象上。打开JDK帮助文档的首页,在其中搜索“jndi”关键字,可以看到一条“jndi”的超链接,单击这个超链接,就可以进入“Java Naming and Directory Interface”的帮助页面,如图6.12所示。

图6.12

单击图6.12中的“The DNS Service Provider”超链接,进入DNS服务程序的帮助页面。只要我们具备JDNI编程的一些基本知识,再加上该帮助文档页提供的信息,我们就知道如何调用这个DNS服务程序来获得某个域的DNS信息和MX记录了。

下面编写一个试验性的JNDI程序,这个程序用于帮助我们熟悉和掌握JNDI API的使用,也帮助我们了解DNS的JNDI服务程序以怎样的形式返回DNS信息。

动手实践:使用JNDI API获取DNS信息

()按例程6-5编写一个名为DNSQuery.java的程序,这个程序使用JNDI API来获得某个域的DNS信息,并从中提取出域的一台SMTP服务器的名称,其中的很多代码都是为了帮助我们熟悉JNDI API的使用和了解DNS的JNDI服务程序返回的DNS信息内容而加入的。运行这个程序时,需要指定一个或两个参数,第一个参数是必须的,为要查询的域名,第二个参数是可选的,为查询时所使用的DNS服务器的IP地址,如果没有指定第二个参数,DNS的JNDI服务程序将使用底层操作系统上设置的DNS服务器。

import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class DNSQuery 
{
	public static void main(String[] args) throws NamingException 
	{
		/*第一个参数指定要查询的域或主机名,第二个参数指定查询的DNS服务器,
		为了程序的简单易读性,省略了严格的参数错误检查*/
		String domain = args[0];
		String dnsServer = args.length<2 ? "" : ("//" + args[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中的Schema信息来自动选择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);
}

例程6-5  DNSQuery.java

(2)在Windows命令行窗口中编译DNSQuery.java程序后,接着在命令行窗口中执行如下命令:

ipconfig /all

如果ipconfig命令显示的结果中包含有DNS Server的信息,那么我们接着就可以使用如下命令来启动执行DNSQuery类:

java  DNSQuery  sina.com

上面的命令的运行结果如图6.13。

图6.13

()假设在上一步用ipconfig命令查看到的本地计算机上配置的DNS Server为202.106.46.151,那么,我们接着执行如下命令:

java  DNSQuery  sina.com  202.106.46.151

这个命令执行完后,也能显示出图6.13中的信息。我们接着故意将上面命令中的DNS服务器参数指定为一个错误的IP地址进行执行,修改后的命令语句如下所示:

java  DNSQuery  sina.com  192.168.1.151

这个命令执行完后的结果如图6.14所示:

图6.14

如果计算机只能通过代理服务器连接到Internet,那么在该计算机上直接执行如下命令:

java  DNSQuery  sina.com

这也将导致图6.14中的错误。如果要想在通过代理服务器上网的情况下,正确执行上面的程序,可以采用如下命令:

java -DsocksProxyHost=162.105.1.200 -DsocksProxyPort=808 

DNSQuery  sina.com  202.106.46.151

由于上面的命令太长,在排版时分成了两行来书写,读者在输入上面这条命令时,不要手工换行。读者应该根据自己的实际情况,修改其中的代理服务器地址、代理端口号和DNS服务器的地址。

6.7.3 编写具有SMTP服务器功能的邮件发送程序

编写具有SMTP服务器功能的邮件发送程序的首要任务就是要能根据收件人地址获得其所在的SMTP服务器名称,也就是要能从DNS服务器上查询出收件人所在域的MX记录,并解析出MX记录中的SMTP服务器名称。获得收件人地址所在的SMTP服务器名称后,应用程序就可以直接连接上该SMTP服务器发送邮件,发送邮件时不需要进行用户认证。

:动手实践:以SMTP服务器方式发送邮件

(1)按例程6-6编写一个名为SMTPSender.java的程序,这个程序向属于多个不同域的收件人发送邮件,它直接连接到每个收件人所在域的SMTP服务器进行邮件发送。

import javax.naming.Context;

import javax.naming.NamingException;

import javax.naming.directory.Attribute;

import javax.naming.directory.Attributes;

import javax.naming.directory.DirContext;

import javax.naming.directory.InitialDirContext;



import java.util.Properties;

import java.util.Date;

import javax.mail.Session;

import javax.mail.Transport;

import javax.mail.internet.InternetAddress;

import javax.mail.Message;

import javax.mail.internet.MimeMessage;





public class SMTPSender

{

public static void main(String[] args) throws Exception

{

//下面是邮件要群发给的多个收件人地址

String [] to = {"it315_test@sohu.com","it315_test@sina.com",

"it315_test@163.com","it315_test@126.com"};

     

//创建Session对象

Properties props = new Properties();

/*mail.smtp.localhost属性用于设置SMTP协议的EHLO命令中的

主机名,其他SMTP服务器可能需要使用这个主机名确定发件SMTP

服务器的身份,这个信息可以从JavaMail的javadocs文档中的

com.sun.mail.smtp包的帮助页面内查看到。

*/

props.setProperty("mail.smtp.localhost","mail.itcast.cn");

Session session = Session.getInstance(props);

session.setDebug(true);



Message msg = createMessage(session);

for(int i=0; i<to.length; i++)

{

try

{

sendMessage(session,msg,to[i]);

}

catch(Exception e)

{

e.printStackTrace();

}

}

}



public static Message createMessage(Session session)

throws Exception

{



String from = "it315_test@sina.com ";//发件人地址

String subject = "SMTPSender demo";//邮件主题

String body = "SMTPSender demo";//邮件内容



//创建代表邮件的MimeMessage对象,不包含收件人地址

MimeMessage msg = new MimeMessage(session);

msg.setFrom(new InternetAddress(from));

msg.setSentDate(new Date());

msg.setSubject(subject);

msg.setText(body);

return msg;

}



public static void sendMessage(Session session,Message msg,String to)

throws Exception

{

//设置邮件内容的收件人并生成邮件消息内容

msg.setRecipients(Message.RecipientType.TO,

InternetAddress.parse(to));

msg.saveChanges();



//连接收件人地址所在的SMTP服务器并发送邮件

Transport transport = session.getTransport("smtp");

String domain = to.substring(to.indexOf("@")+1);

String smtpServer = getSmtpServer(domain,null);

transport.connect(smtpServer,null,null);

transport.sendMessage(msg,

           msg.getRecipients(Message.RecipientType.TO));

transport.close();

}



public static String getSmtpServer(String domain,String dnsServer)

throws Exception

{

DirContext ctx = new InitialDirContext();

Attributes attrsMx = null;

if(dnsServer != null)

{

attrsMx = ctx.getAttributes("dns:" + "//" + dnsServer +

"/" + domain, new String[]{"MX"});

}

else

{

attrsMx = ctx.getAttributes("dns:" + "/" + domain, new String[]{"MX"});

}



String recordMx = (String)attrsMx.get("MX").get();

String smtpServer = recordMx.substring(

recordMx.indexOf(" ") + 1);

return smtpServer;

}

}

例程6-6  SMTPSender.java

实际的SMTP服务器程序要比上面的程序复杂得多,它们每天要向大量的其他SMTP服务器发送大量的邮件,当它们连接上一台其他的SMTP服务器并发送一封邮件后,是否要立即与这个服务器断开连接呢?这就是一个比较难以处理的问题了,如果不断开连接,而随后一段时间内又没有要发送给该SMTP服务器的邮件了,这样就造成网络连接资源浪费;如果发送一封邮件后立即断开与该SMTP服务器的连接,而随后的邮件又是要发送到的该SMTP服务器,又要重新与该SMTP服务器建立连接,这样明显是一种低效率的方法。关于这个问题,有兴趣的读者可以去看一些开源的SMTP服务器程序的源代码,我们就不再这里讨论如何解决这些问题了。

(2)在Windows命令行窗口中编译并运行这个java程序,通常情况下,这个程序发送邮件的过程都会以失败告终,如图6.15所示。

图6.15

出现图6.15中的错误是因为现在的大多数SMTP服务器都增加了一些防垃圾邮件的措施,如果邮件发送程序以SMTP服务器的身份向它们发送邮件,它们要求运行该邮件发送程序的计算机必须在Internet上注册了主机名(也就是A记录),并且这台计算机的IP地址没有被列入进它们的黑名单,而我和读者们使用的计算机往往都无法满足这些条件。

对于sina这样的大型网站,它们提供了完全独立的SMTP服务器来分别与普通邮件客户端程序和其他SMTP服务器进行通信。邮件客户端程序连接的SMTP服务器是smtp.sina.com.cn,smtp.sina.com.cn服务器总是要求对方进行用户身份验证。其他smtp服务器连接的sina的SMTP服务器是sina.com域中MX记录所对应的计算机,例如,sinamx.sina.com.cn,这些注册成了MX记录的计算机不要求对方进行用户身份验证,但也采取各种方式来防止对方发送垃圾邮件。显然,smtp.sina.com.cn是不能被注册为MX记录的,因为它需要对方传递用户身份信息,其他SMTP无法根本无法与之正常通信。

对于小型网站,它们用同一台SMTP服务器来与普通邮件客户端程序和其他SMTP服务器进行通信,它们通常根据对方在ehlo命令中提供的主机名是否与该计算机的ip地址反向解析到的主机名一致来判断对方是普通的邮件客户端程序,还是其他SMTP服务器。如果对方在ehlo命令中指定的主机名与对方的IP地址不匹配,小型网站的SMTP服务器程序则认为对方是普通的邮件客户端程序,需要对方传递用户认证信息;如果对方在ehlo命令中指定的主机名与对方的IP地址匹配,小型网站的SMTP服务器程序则认为对方也是一台SMTP服务器,不需要对方提供用户认证信息即可直接发送邮件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵然间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值