阿将邮件工具结合到您独立于平台的 Java 解决方案中吗?再没有比 JavaMail API 更合适的了,它提供了个独立于协议的模块,能用于 IMAP、POP、SMTP、MIME 和其它涉及因特网的消息传递协议。现在,有了 JavaBeans Activation Framework (JAF)的支持,您的应用程序经由 JavaMail API 就能启用邮件功能啦。
概念
学完这个模块后,您将了解:
· 因特网邮件协议 SMTP、POP3、IMAP 和 MIME 的基本知识
· JavaMail 框架的体系结构
· JavaMail API 和 JavaBeans Activation Framework 之间的连接
目的
学完这个模块后,您将能够:
· 使用 JavaMail API 发送和阅读邮件
· 处理发送和接受邮件附件
· 处理 HTML 文件格式的消息
· 使用搜索条件搜索消息
先决条件
课程含有如何下载及安装 JavaMail API 的指导。此外,您还需一个开发环境,如 JDK 1.1.6+ 或 Java 2 平台标准版(Java 2 Platform, Standard Edition ,J2EE)1.2.x 或 1.3.x。
有必要对面向对象编程概念和 Java 编程语言大致熟悉。教程 Java 语言要点会有所帮助。
JavaMail API 是什么呢?
JavaMail API 是一个用于阅读、编写和发送电子消息的可选包(标准扩展)。与 Eudora、pine 及 Microsoft Outlook 相似,这个包用来创建邮件用户代理(Mail User Agent,MUA)类型程序。API 的主要用途并不在于传输、发送和转发消息;这一功能范围属于某些应用程序,如 sendmail 及其它邮件传输代理(Mail Transfer Agent,MTA)类型程序。MUA 类型的程序能让用户阅读和书写邮件,而它却依赖 MTA 处理实际发送。
JavaMail API 打算将 API 分成两部分来支持独立于协议的发送和接受消息:
· 此 API 的第一部分是这个课程的焦点—基本上来说,是如何发送和接受独立于供应商/协议消息。
· 第二部分谈到特定协议的语言,如 SMTP、POP、IMAP 和 NNTP。有了 JavaMail API,要与服务器通信,还需要每个协议的供应商(provider)。此课程不涉及创建特定的协议供应商,因为 Sun 已经为我们免费提供了很充分的一组。
在研究 JavaMail API 的细则之前,让我们回顾用于 API 的协议。基本上,您会逐渐熟悉并喜爱的协议有四个:
· SMTP
· POP
· IMAP
· MIME
您还将碰到 NNTP 和其它协议。理解所有协议的基本知识将有助于您理解如何使用 JavaMail API。虽然不了解这些协议您照样可以用这个 API,却不能够克服那些基础协议的局限性。如果我们精选的协议不能支持某种性能,JavaMail API 决不能魔术般的将这种性能添加上去。(您很快就会看到,在处理 POP 时这将成为一个难题。)
SMTP:简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)由 RFC 821 定义。它定义了发送电子邮件的机制。在 JavaMail API 环境中,您基于 JavaMail 的程序将和您的公司或因特网服务供应商的(Internet Service Provider's,ISP's)SMTP 服务器通信。SMTP 服务器会中转消息给接收方 SMTP 服务器以便最终让用户经由 POP 或 IMAP 获得。这不是要求 SMTP 服务器成为开放的中继,尽管 SMTP 服务器支持身份验证,不过还是得确保它的配置正确。像配置服务器来中继消息或添加删除邮件账号这类任务的实现,JavaMail API 中并不支持。
POP: 代表邮局协议(Post Office Protocol)。目前用的是版本 3,也称 POP3,RFC 1939 定义了这个协议。POP 是一种机制,因特网上大多数人用它得到邮件。它规定每个用户一个邮箱的支持。这就是它所能做的,而这也造成了许多混淆。使用 POP 时,用户熟悉的许多性能并不是由 POP 协议支持的,如查看有几封新邮件消息这一性能。这些性能内建于如 Eudora 或 Microsoft Outlook 之类的程序中,它们能记住一些事,诸如最近一次收到的邮件,还能计算出有多少是新的。所以当使用 JavaMail API 时,如果您想要这类信息,您就必须自己算。
IMAP 是更高级的用于接收消息的协议。在 RFC 2060 中被定义,IMAP 代表因特网消息访问协议(Internet Message Access Protocol),目前用的是版本 4,也称 IMAP4。在用到 IMAP 时,邮件服务器必需支持这个协议。不能仅仅把使用 POP 的程序用于 IMAP,并指望它支持 IMAP 所有性能。假设邮件服务器支持 IMAP,基于 JavaMail 的程序可以利用这种情况—用户在服务器上有多个文件夹(folder),并且这些文件夹可以被多个用户共享。
因为有这一更高级的性能,您也许会认为所有用户都会使用 IMAP。事实并不是这样。要求服务器接收新消息,在用户请求时发送到用户手中,还要在每个用户的多个文件夹中维护消息。这样虽然能将消息集中备份,但随着用户长期的邮件夹越来越大,到磁盘空间耗尽时,每个用户都会受到损失。使用 POP,就能卸载邮件服务器上保存的消息了。
MIME 代表多用途因特网邮件扩展标准(Multipurpose Internet Mail Extensions)。它不是邮件传输协议。但对传输内容的消息、附件及其它的内容定义了格式。这里有很多不同的有效文档:RFC 822、RFC 2045、RFC 2046 和 RFC 2047。作为一个 JavaMail API 的用户,您通常不必对这些格式操心。无论如何,一定存在这些格式而且程序会用到它。
因为 JavaMail API 将供应商和所有其它的东西分开了,您就能轻松添加额外的协议支持。Sun 保留了一张第三方供应商列表,他们利用了 Sun 不提供超出(out-of-the-box)支持范围的协议。您会找到 NNTP(网络新闻传输协议)[新闻组]、S/MIME(安全多用途因特网邮件扩展)及其它支持。
安装JavaMail:
现在有两种常用的 JavaMail API 版本:1.2 和 1.1.3。这一课程的所有示例都能在这两种版本上正常运行。虽然版本 1.2 是最新版,但版本 1.1.3 包含了 Java 2 平台企业版(Java 2 Platform, Enterprise Edition,J2EE)的版本 1.2.1,所以仍然有很多人使用它。您要用的 JavaMail API 的版本取决于您下载和安装的东西,所有这些将在JDK 1.1.6+、J2EE 版本 1.2.x 和 J2SE 版本 1.3.x 下工作。
注意:安装完 Sun 的 JavaMail 实现后,您可以在demo目录中找到很多示例程序。
要使用 JavaMail 1.2 API,请下载 JavaMail 1.2 实现,解开javamail-1_2.zip文件,并将mail.jar文件添加到您的 CLASSPATH 中。除了核心类,随版本 1.2 实现一起提供的还有 SMTP、IMAP4 和 POP3 供应商。
安装完 JavaMail 1.2 之后,安装 JavaBeans Activation Framework。
JavaMail API 的所有版本都需要 JavaBeans Activation Framework 来支持任意数据块的输入及相应处理。功能似乎不多,但目前许多浏览器和邮件工具中都能找到这种基本的 MIME 型支持。下载完框架后,解开jaf1_0_1.zip文件,并将activation.jar文件添加到 CLASSPATH 中。
对于 JavaMail 1.2 用户来说,现在您应该已将mail.jar和activation.jar文件添加到 CLASSPATH 中了。
而对于 JavaMail 1.1.3 用户,现在您应该已将mail.jar、pop3.jar和activation.jar文件添加到 CLASSPATH 中了。如果不打算用 POP3,就不必将pop3.jar添加到 CLASSPATH 中去。
如果您不想更改 CLASSPATH 环境变量,将 jar 文件复制到您 Java 运行时环境(Java Runtime Environment,JRE)目录下的lib/ext目录中去。例如,J2SE 1.3 发行版的缺省目录在 Windows 平台的C:/jdk1.3/jre/lib/ext。
如果您使用 J2EE,就没有什么特定的事非要用基本 JavaMail API 来做不可;J2EE 的类就能处理了。只要确保j2ee.jar文件已在您的CLASSPATH 中并已全部设置好。
练习 1. 如何设置 JavaMail 环境
在这个练习中,您将安装 Sun 的 JavaMail 相关工具。安装结束后,将介绍符合参考实现的演示程序。
任务 1:请下载 Sun 的 JavaMail API 实现 的最新版本。
任务 2:请下载 Sun 的 JavaBeans Activation Framework 的最新版本。
任务 3:解压缩下载的软件包。您就得到能支持所有平台的两个包的 ZIP 文件。
任务 3 的帮助:你可以使用jar工具将包解压缩。
任务 4:将 JavaMail 1.2 下载软件包中的mail.jar文件以及 JavaBeans Activation Framework 下载软件包中的activation.jar文件添加到您的CLASSPATH中。
任务 4 的帮助:将文件复制到您的扩展库目录下面。Microsoft Windows 的用户使用缺省的安装复制,可输入如下命令:
cd /javamail-1.2
copy mail.jar /jdk1.3/jre/lib/ext
cd /jaf-1.0.1
copy activation.jar /jdk1.3/jre/lib/ext
如果您不愿意将文件复制到扩展库目录,Sun 提供在 Windows NT 上设置 CLASSPATH 的详细指导。
任务 5:进入 JavaMail API 工具的demo目录,编译msgsend程序以发送一条测试消息。
任务 5 的帮助:
javac msgsend.java
任务 6:执行程序,用-o选项传入from地址、用-M选项传入 SMTP 服务器,还有传入to地址(没有选项)。然后,您输入主题,消息正文和文件结束符(CTRL-Z)标志消息输入结束。
任务 6 的帮助:务必替换from地址、SMTP 服务器和to地址。
java msgsend -o from@address -M SMTP.Server to@address
如果您不能确定您的 SMTP 服务器,请联系您的系统管理员或与您的互联网服务供应商核对。
任务 7:
用您的标准邮件阅读程序(Eudora、Outlook Express、pine...)检查,确保消息已收到。
JavaMail核心类概览:
在采用深入探索 JavaMail 类的入门方法前,这一章向您展示构成 API 的核心类:Session、Message、Address、Authenticator、Transport、Store和Folder。尽管您常会发现自己使用的子类在javax.mail.internet中,但上述这些类却放在 JavaMail API的顶级包javax.mail里。
Session
Session类定义了一个基本邮件会话(session)。所有其它类都是经由这个session 才得以生效。Session对象用java.util.Properties对象获取信息,如邮件服务器、用户名、密码及整个应用程序中共享的其它信息。
类的构造器是私有的(private)。您可以得到单个缺省 session,它能用getDefaultInstance()方法被共享:
Properties props = new Properties();
// fill props with any information
Session session = Session.getDefaultInstance(props, null);
或者,您还可以用getInstance()创建一个独立的 session:
Properties props = new Properties();
// fill props with any information
Session session = Session.getDefaultInstance(props, null);
对于这两种情况,null参数都是Authenticator对象(在这次没有使用)。
对于大多数情况,共享的 session 已经够用了,即使要处理多个用户邮箱的邮件 session 也一样。您可以在通信过程中稍后的步骤加入用户名和密码组合,让一切保持独立。
Message
一旦获得Session对象,就可以继续创建要发送的消息。这由Message类来完成。因为Message是个抽象类,您必需用一个子类,多数情况下为javax.mail.internet.MimeMessage。MimeMessage是个能理解 MIME 类型和头的电子邮件消息,正如不同 RFC 中所定义的。虽然在某些头部域非 ASCII 字符也能被译码,但 Message 头只能被限制为用 US-ASCII 字符。
要创建一个Message,请将Session对象传递给MimeMessage构造器:
MimeMessage message = new MimeMessage(session);
注意:还存在其它构造器,如用按 RFC822 格式的输入流来创建消息。
一旦获得消息,您就可以设置各个部分,因为Message实现Part 接口(且MimeMessage实现MimePart )。设置内容的基本机制是setContent()方法,同时使用参数,分别代表内容和 mime 类型:
message.setContent("Hello", "text/plain");
但如果,您知道您在使用MimeMessage,而且消息是纯文本格式,您就可以用setText()方法,它只需要代表实际内容的参数,( MIME 类型缺省为 text/plain):
message.setText("Hello");
后一种格式是设置纯文本消息内容的首选机制。至于发送其它类型的消息,如 HTML 文件格式的消息,我们首选前者。
用setSubject()方法设置 subject(主题):
message.setSubject("First");
Address
一旦您创建了Session和Message,并将内容填入消息后,就可以用 Address确定信件地址了。和Message一样,Address也是个抽象类。您用的是javax.mail.internet.InternetAddress类。
若创建的地址只包含电子邮件地址,只要传递电子邮件地址到构造器就行了。
Address address = new InternetAddress("president@whitehouse.gov");
若希望名字紧挨着电子邮件显示,也可以把它传递给构造器:
Address address = new InternetAddress("president@whitehouse.gov", "George Bush");
需要为消息的from域和to域创建地址对象。除非邮件服务器阻止,没什么能阻止你发送一段看上去是来自任何人的消息。
一旦创建了 address(地址),将它们与消息连接的方法有两种。如果要识别发件人,您可以用setFrom()和setReplyTo()方法。
message.setFrom(address)
需要消息显示多个from地址,可以使用addFrom()方法:
Address address[] = ...;
message.addFrom(address);
若要识别消息 recipient(收件人),您可以使用addRecipient()方法。除 address(地址)外,这一方法还请求一个Message.RecipientType。
message.addRecipient(type, address)
三种预定义的地址类型是:
· Message.RecipientType.TO
· Message.RecipientType.CC
· Message.RecipientType.BCC
如果消息是发给副总统的,同时发送一个副本(carbon copy)给总统夫人,以下做法比较恰当:
Address toAddress = new InternetAddress("vice.president@whitehouse.gov");
Address ccAddress = new InternetAddress("first.lady@whitehouse.gov");
message.addRecipient(Message.RecipientType.TO, toAddress);
message.addRecipient(Message.RecipientType.CC, ccAddress);
JavaMail API 没有提供电子邮件地址有效性核查机制。虽然通过编程,自己能够扫描有效字符(如 RFC 822 中定义的)或验证邮件交换(mail exchange,MX)记录,但这些功能不属于 JavaMail API。
Authenticator
与java.net类一样,JavaMail API 也可以利用Authenticator通过用户名和密码访问受保护的资源。对于JavaMail API 来说,这些资源就是邮件服务器。JavaMail Authenticator在javax.mail包中,而且它和java.net中同名的类Authenticator不同。两者并不共享同一个Authenticator,因为JavaMail API 用于 Java 1.1,它没有java.net类别。
要使用Authenticator,先创建一个抽象类的子类,并从getPasswordAuthentication()方法中返回 PasswordAuthentication实例。创建完成后,您必需向 session 注册Authenticator。然后,在需要认证的时候,就会通知Authenticator。您可以弹出窗口,也可以从配置文件中(虽然没有加密是不安全的)读取用户名和密码,将它们作为PasswordAuthentication对象返回给调用程序。
Properties props = new Properties();
// fill props with any information
Authenticator auth = new MyAuthenticator();
Session session = Session.getDefaultInstance(props, auth);
Transport
消息发送的最后一部分是使用Transport类。这个类用协议指定的语言发送消息(通常是 SMTP)。它是抽象类,它的工作方式与Session有些类似。仅调用静态send()方法,就能使用类的缺省版本:
Transport.send(message);
或者,您也可以从针对您的协议的会话中获得一个特定的实例,传递用户名和密码(如果不必要就不传),发送消息,然后关闭连接。
message.saveChanges(); // implicit with send()
Transport transport = session.getTransport("smtp");
transport.connect(host, username, password);
transport.sendMessage(message, message.getAllRecipients());
transport.close();
后面这种方法在您要发送多条消息时最好,因为它能保持邮件服务器在消息间的活动状态。基本send()机制为每个方法的调用设置与服务器独立的连接。
注意:要观察传到邮件服务器上的邮件命令,请用session.setDebug(true)设置调试标志。
Store 和 Folder
用Session获取消息与发送消息开始很相似。但是,在 session 得到后,很可能使用用户名和密码或使用Authenticator连接到一个Store。类似于Transport,您告知Store使用什么协议:
// Store store = session.getStore("imap");
Store store = session.getStore("pop3");
store.connect(host, username, password);
连接到Store之后,接下来,您就可以获取一个Folder,您必需先打开它,然后才能读里面的消息。
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
Message message[] = folder.getMessages();
POP3 唯一可以用的文件夹是INBOX。如果使用 IMAP,还可以用其它文件夹。
注意:Sun 的供应商有意变得聪明。虽然Message message[] = folder.getMessages();看上去是个很慢的操作,它从服务器上读取每一条消息,但仅在你实际需要消息的一部分时,消息的内容才会被检索。
一旦有了要读的Message,您可以用getContent()来获取其内容,或者用writeTo()将内容写入流。getContent()方法只能得到消息内容,而writeTo()的输出却包含消息头。
System.out.println(((MimeMessage)message).getContent());
一旦读完邮件,要关闭与 folder 和 store 的连接。
folder.close(aBoolean);
store.close();
传递给 folder 的close()方法的 boolean 表示是否清除已删除的消息从而更新 folder。
基本上,对于 JavaMail API 差不多所有内容,了解如何使用这七个类已经足够。JavaMail API 其它功能建立在这七个类的基础上,它们做的事稍有不同,或者方法独特,如内容是附件的情况。某些如搜索之类的任务,是个别情况,我们稍候讨论。
使用JavaMail API
您已经了解如何运用 JavaMail API 的核心部分。在接下来的章节,您会看到如何连接从而实现给定任务块的入门方法。
发送消息
发送电子邮件消息这一过程包括获取一个会话,创建并填充一则消息,然后发送。得到Session时,经由设置传递的Properties对象中的mail.smtp.host属性,可以指定您的 SMTP 服务器:
String host = ...;
String from = ...;
String to = ...;
// Get system properties
Properties props = System.getProperties();
// Setup mail server
props.put("mail.smtp.host", host);
// Get session
Session session = Session.getDefaultInstance(props, null);
// Define message
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
message.setSubject("Hello JavaMail");
message.setText("Welcome to JavaMail");
// Send message
Transport.send(message);
您应该将代码放在一个 try-catch 程序块中,这样创建和发送消息时就能够抛出异常。
练习 2. 如何发送第一条消息
在上一个练习中,您使用 JavaMail 实现提供的演示程序发送了一条邮件消息。在这个练习中,您将亲自创建程序。
需要更多练习的帮助,请参阅关于练习。
先决条件:
· 练习 1. 如何设置 JavaMail 环境
框架代码:
任务 1:
参照框架代码开始着手,获取系统Properties。
任务 1 的帮助:
Properties props = System.getProperties();
任务 2:
将您的 SMTP 服务器名添加到mail.smtp.host关键字的属性中。
任务 2 的帮助:
props.put("mail.smtp.host", host);
任务 3:
获取基于PropertiesSession对象。
任务 3 的帮助:
Session session = Session.getDefaultInstance(props, null);
任务 4:
从 session 创建一个MimeMessage。
任务 4 的帮助:
MimeMessage message = new MimeMessage(session);
任务 5:
设置消息的from域。
任务 5 的帮助:
message.setFrom(new InternetAddress(from));
任务 6:
设置消息的to域。
任务 6 的帮助:
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
任务 7:
设置消息主题。
任务 7 的帮助:
message.setSubject("Hello JavaMail");
任务 8:
设置消息内容。
任务 8 的帮助:
message.setText("Welcome to JavaMail");
任务 9:
用Transport发送消息。
任务 9 的帮助:
Transport.send(message);
任务 10:
在命令行上编译并运行程序—传递 SMTP 服务器、from地址和to地址。
任务 10 的帮助:
java MailExample SMTP.Server from@address to@address
任务 11:
用标准邮件阅读程序(Eudora、Outlook Express、pine...)检查,确保消息已收到。
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
public class MailExample {
public static void main (String args[]) throws Exception {
String host = args[0];
String from = args[1];
String to = args[2];
// Get system properties
Properties props = System.getProperties();
// Setup mail server
props.put("mail.smtp.host", host);
// Get session
Session session = Session.getDefaultInstance(props, null);
// Define message
MimeMessage message = new MimeMessage(session);
// Set the from address
message.setFrom(new InternetAddress(from));
// Set the to address
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
// Set the subject
message.setSubject("Hello JavaMail");
// Set the content
message.setText("Welcome to JavaMail");
// Send message
Transport.send(message);
}
}
消息的提取
为读邮件,您获取一个会话,获取并连接一个用于邮箱的适宜的存储(store),打开适宜的文件夹,然后获取您的消息。同样,切记完成后关闭连接。
String host = ...;
String username = ...;
String password = ...;
// Create empty properties
Properties props = new Properties();
// Get session
Session session = Session.getDefaultInstance(props, null);
// Get the store
Store store = session.getStore("pop3");
store.connect(host, username, password);
// Get folder
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
// Get directory
Message message[] = folder.getMessages();
for (int i=0, n=message.length; i<n; i++) {
System.out.println(i + ": " + message[i].getFrom()[0]
+ "/t" + message[i].getSubject());
}
// Close connection
folder.close(false);
store.close();
对每条消息做些什么由您决定。上面的代码块只是显示这些消息的发件人和主题。技术上讲,from地址列表可能为空,而getFrom()[0]调用会抛出一个异常。
要显示全部信息,您可以在用户看完 from 和 subject 域之后给出提示,如用户有需要,就调用消息的writeTo()方法来实现。
BufferedReader reader = new BufferedReader (
new InputStreamReader(System.in));
// Get directory
Message message[] = folder.getMessages();
for (int i=0, n=message.length; i<n; i++) {
System.out.println(i + ": " + message[i].getFrom()[0]
+ "/t" + message[i].getSubject());
System.out.println("Do you want to read message? " +
"[YES to read/QUIT to end]");
String line = reader.readLine();
if ("YES".equals(line)) {
message[i].writeTo(System.out);
} else if ("QUIT".equals(line)) {
break;
}
}
练习 3. 如何检查邮件
在这个练习中,我们创建了一个程序,它能够显示每条消息的from地址和主题,并提示消息内容的显示。
需要更多练习的帮助,请参阅关于练习.
先决条件:
· 练习 1. 如何设置 JavaMail 环境
框架代码
任务 1:
参考框架代码开始着手,获取(或说创建)一个Properties对象。
任务 1 的帮助:
Properties props = new Properties();
任务 2:
获取基于Properties的Session对象。
任务 2 的帮助:
Session session = Session.getDefaultInstance(props, null);
任务 3:
获取电子邮件协议pop3或imap的Store。
任务 3 的帮助:
Store store = session.getStore("pop3");
任务 4:
用恰当的用户名和密码连接到您的邮件主机的 store。
任务 4 的帮助:
store.connect(host, username, password);
任务 5:
获取您要读的 folder。很可能会是INBOX。
任务 5 的帮助:
Folder folder = store.getFolder("INBOX");
任务 6:
以只读方式打开 folder。
任务 6 的帮助:
folder.open(Folder.READ_ONLY);
任务 7:
获取一个 folder 中的消息目录。将消息列表保存在名为message的数组变量中。
任务 7 的帮助:
Message message[] = folder.getMessages();
任务 8:
显示每条消息的from域和主题。
任务 8 的帮助:
System.out.println(i + ": " + message[i].getFrom()[0]
+ "/t" + message[i].getSubject());
任务 9:
在提示时显示消息内容。
任务 9 的帮助:
System.out.println(message[i].getContent());
任务 10:
关闭到 folder 和 store 的连接。
任务 10 的帮助:
folder.close(false);
store.close();
任务 11:
在命令行上编译并运行程序—传递 SMTP 服务器、用户名和密码。对于想要阅读的消息,以 YES 响应。反之,敲击ENTER。如果想在读完所有消息前中止,可以输入 QUIT。
任务 11 的帮助:
java GetMessageExample POP.Server username password
import java.io.*;
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
public class GetMessageExample {
public static void main (String args[]) throws Exception {
String host = args[0];
String username = args[1];
String password = args[2];
// Create empty properties
Properties props = new Properties();
// Get session
Session session = Session.getDefaultInstance(props, null);
// Get the store
Store store = session.getStore("pop3");
// Connect to store
store.connect(host, username, password);
// Get folder
Folder folder = store.getFolder("INBOX");
// Open read-only
folder.open(Folder.READ_ONLY);
BufferedReader reader = new BufferedReader (
new InputStreamReader(System.in));
// Get directory
Message message[] = folder.getMessages();
for (int i=0, n=message.length; i<n; i++) {
// Display from field and subject
System.out.println(i + ": " + message[i].getFrom()[0]
+ "/t" + message[i].getSubject());
System.out.println("Do you want to read message? [YES to read/QUIT to end]");
String line = reader.readLine();
if ("YES".equals(line)) {
// Display message content
System.out.println(message[i].getContent());
} else if ("QUIT".equals(line)) {
break;
}
}
// Close connection
folder.close(false);
store.close();
}
}
消息和标志的删除
消息的删除涉及使用与消息相关的Flags(标志)。不同 flag 对应不同的状态,有些由系统定义而有些则由用户定义。下面列出在内部类Flags.Flag中预定义的标志:
· Flags.Flag.ANSWERED
· Flags.Flag.DELETED
· Flags.Flag.DRAFT
· Flags.Flag.FLAGGED
· Flags.Flag.RECENT
· Flags.Flag.SEEN
· Flags.Flag.USER
仅仅因为存在一个标志,并不意味着所有邮件服务器或供应商都支持这个标志。例如,除了删除消息标志外,POP 协议不再支持其它任何标志。检查是否存在新邮件,这不是个 POP 任务,而是内建于邮件客户机的任务。为找出哪些标志能被支持,可以用getPermanentFlags()向 folder 提出要求。
要删除消息,您可以设置消息的DELETED flag:
message.setFlag(Flags.Flag.DELETED, true);
首先,请以READ_WRITE模式打开 folder:
folder.open(Folder.READ_WRITE);
然后,当所有消息的处理完成后,关闭 folder,并传递一个 true 值,从而擦除(expunge)有 delete 标志的消息。
folder.close(true);
一个Folder的expunge()方法可以用来删除消息。但 Sun 的 POP3 供应商不支持。其它供应商有的或许能够实现这一功能,而有的则不能。IMAP 供应商极有可能实现此功能。因为 POP 只支持单个对邮箱的访问,对 Sun 的供应商来说,您必需关闭 folder 以删除消息。
要取消标志,只要传递 false 给setFlag()方法就行了。想知道是否设置过标志,可以用isSet()检查。
亲自认证
您已经知道—如果需要可以用一个Authenticator提示用户输入用户名和密码,而不是将用户名和密码作为字符串传递。在这里您会明确了解怎样更充分的使用认证。
不用主机、用户名和密码与Store相连接,而是设置Properties来拥有主机,然后告诉Session自定义的Authenticator实例,如下所示:
// Setup properties
Properties props = System.getProperties();
props.put("mail.pop3.host", host);
// Setup authentication, get session
Authenticator auth = new PopupAuthenticator();
Session session = Session.getDefaultInstance(props, auth);
// Get the store
Store store = session.getStore("pop3");
store.connect();
然后,您创建一个Authenticator子类并从getPasswordAuthentication()方法中返回PasswordAuthentication对象。下面就是这样一种实现,其中用户名和密码仅占用一个域。(这不是一个 Swing 工程教程;只要将两部分输入同一个域,用逗号分隔就行。)
import javax.mail.*;
import javax.swing.*;
import java.util.*;
public class PopupAuthenticator extends Authenticator {
public PasswordAuthentication getPasswordAuthentication() {
String username, password;
String result = JOptionPane.showInputDialog(
"Enter 'username,password'");
StringTokenizer st = new StringTokenizer(result, ",");
username = st.nextToken();
password = st.nextToken();
return new PasswordAuthentication(username, password);
}
}
因为PopupAuthenticator涉及到 Swing,它会启动 AWT 的事件处理线程。这一点基本上要求您在代码中添加一个对System.exit()的调用来终止程序。
消息的回复
Message类引入一个reply()方法来配置一个新Message,包括正确的 recipient(收件人)和添加“Re”(如果没有就添加)的正确的 subject。这样做并没有为消息添加新内容,仅仅将from或reply-to(被回复人)头复制给新的收件人。这种方法用一个 boolean 参数指定消息只回复给发件人(false)或回复给全体(true)。
MimeMessage reply = (MimeMessage)message.reply(false);
reply.setFrom(new InternetAddress("president@whitehouse.gov"));
reply.setText("Thanks");
Transport.send(reply);
在发送消息时要配置reply to(被回复人)地址,可以用setReplyTo()方法。
练习 4. 如何回复邮件
在这个练习中,我们创建了一个程序,它能够创建一个固定格式的回复消息,并将纯文本的原始消息附加到消息中。
需要更多练习的帮助,请参阅关于练习。
先决条件:
· 练习 3. 如何检查邮件
框架代码:
任务 1:
框架代码 中已含有代码,以便从 folder 中获取消息清单并提示您创建一条回复消息。
任务 2:
如果回答“是”,则基于原始消息创建一个新的MimeMessage。
任务 2 的帮助:
MimeMessage reply = (MimeMessage)message[i].reply(false);
任务 3:
在您的电子邮件地址上设置from域。
任务 4:
创建要回复的文本。从引入固定格式的消息开始。若原始消息是纯文本,就在原始消息的每行前添加"> " 字符并添加到回复的消息中。
任务 4 的帮助:
对于纯文本消息,请用mimeMessage.isMimeType("text/plain")检查 MIME 类型。
任务 5:
一旦消息内容已完全确定,就可以对其进行设置。
任务 6:
发送消息。
任务 7:
在命令行上编译并运行程序—传递邮件服务器、 SMTP 服务器、用户名、密码以及from地址。对您将回复的消息,响应 YES。反之,敲击ENTER。如果想在回复完所有消息前中止,可以输入 QUIT。
任务 7 的帮助:
java ReplyExample POP.Server SMTP.Server username password from@address
任务 8:
请检查以确认用标准邮件阅读程序(Eudora、Outlook Express、pine...)收到了消息。
import java.io.*;
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
publicclass ReplyExample {
publicstaticvoid main (String args[]) throws Exception {
String host = args[0];
String sendHost = args[1];
String username = args[2];
String password = args[3];
String from = args[4];
// Create empty properties
Properties props = System.getProperties();
props.put("mail.smtp.host", sendHost);
// Get session
Session session = Session.getDefaultInstance(props, null);
// Get the store
Store store = session.getStore("pop3");
store.connect(host, username, password);
// Get folder
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
BufferedReader reader = new BufferedReader (
new InputStreamReader(System.in));
// Get directory
Message message[] = folder.getMessages();
for (int i=0, n=message.length; i<n; i++) {
System.out.println(i + ": " + message[i].getFrom()[0]
+ "/t" + message[i].getSubject());
System.out.println("Do you want to reply to the message? [YES to reply/QUIT to end]");
String line = reader.readLine();
if ("YES".equals(line)) {
// Create a reply message
MimeMessage reply = (MimeMessage)message[i].reply(false);
// Set the from field
reply.setFrom(new InternetAddress(from));
// Create the reply content, copying over the original if text
MimeMessage orig = (MimeMessage)message[i];
StringBuffer buffer = new StringBuffer("Thanks/n/n");
if (orig.isMimeType("text/plain")) {
String content = (String)orig.getContent();
StringReader contentReader = new StringReader(content);
BufferedReader br = new BufferedReader(contentReader);
String contentLine;
while ((contentLine = br.readLine()) != null) {
buffer.append("> ");
buffer.append(contentLine);
buffer.append("/r/n");
}
}
// Set the content
reply.setText(buffer.toString());
// Send the message
Transport.send(reply);
} elseif ("QUIT".equals(line)) {
break;
}
}
// Close connection
folder.close(false);
store.close();
}
}
附件的处理
附件是邮件消息的相关资源,如通常不包含在消息正文里文本文件、电子表格或图像等。常见的邮件程序,如 Eudora 和 pine 之类,可以用 JavaMail API 将资源attach(附加)到您的消息上,就可以在收到消息时得到。
附件的发送:
发送附件非常像转发消息。您建立各部分以组成完整消息。完成第一部件,即消息正文后,您添加其它部件,其中每个DataHandler都代表附件,而不是转发消息情况下的共享处理程序。如果从文件中读附件,附件的数据源是FileDataSource。而如果从 URL 中读时,附件的数据源是URLDataSource。一旦存在DataSource,只要先把它传递给DataHandler构造器,最后再用setDataHandler()把它附加到BodyPart。假定您要保留附件的原始文件名,最终要做的是用BodyPart的setFileName()方法设置与附件相关的文件名。如下所示:
// Define message
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
message.setSubject("Hello JavaMail Attachment");
// Create the message part
BodyPart messageBodyPart = new MimeBodyPart();
// Fill the message
messageBodyPart.setText("Pardon Ideas");
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);
// Part two is attachment
messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(filename);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(filename);
multipart.addBodyPart(messageBodyPart);
// Put parts in message
message.setContent(multipart);
// Send the message
Transport.send(message);
就消息引入附件时,若程序是个 servlet (小服务程序),除告知消息发送到何处外,还必需上载附件。可以将multipart/form-data表单编码类型(form encoding type)用于每个上载文件的处理。
<FORM ENCTYPE="multipart/form-data"
method=post action="/myservlet">
<INPUT TYPE="file" NAME="thefile">
<INPUT TYPE="submit" VALUE="Upload">
</FORM>
注意:消息大小由 SMTP 服务器而不是 JavaMail API 来限制。如果您碰到问题,可以考虑用设置 ms 和 mx 参数的方法增大 Java 堆大小。
练习:
练习 5. 如何发送附件
附件的获取:
从消息中获取附件比发送它们棘手些,因为 MIME 没有简单的关于附件的概念。当消息包含附件时,消息的内容是个Multipart对象。接着,您需要处理每个Part,获取主要内容和附件。标有从part.getDisposition()获得的Part.ATTACHMENT配置(disposition)的部件(Part)无疑就是附件。但是,没有配置(以及一个非文本 MIME 类型)和带Part.INLINE配置的部件也可能是附件。当配置要么是Part.ATTACHMENT,要么是Part.INLINE时,这个消息部件的内容就能被保存。只要用getFileName()和getInputStream()就能分别得到原始文件名和输入流。
Multipart mp = (Multipart)message.getContent();
for (int i=0, n=multipart.getCount(); i<n; i++) {
Part part = multipart.getBodyPart(i));
String disposition = part.getDisposition();
if ((disposition != null) &&
((disposition.equals(Part.ATTACHMENT) ||
(disposition.equals(Part.INLINE))) {
saveFile(part.getFileName(), part.getInputStream());
}
}
saveFile()方法仅依据文件名创建了一个File,它从输入流中将字节读出,然后写入到文件中。万一文件已经存在,就在文件名后添加一个数字作为新文件名,如果这个文件名仍存在,则继续添,直到找不到这样的文件名为止。
// from saveFile()
File file = new File(filename);
for (int i=0; file.exists(); i++) {
file = new File(filename+i);
}
上面的代码涵盖了最简单的情况—消息中各部件恰当的标记了。要涵盖所有情况,还要在配置为空时进行处理,并且获取部件的 MIME 类型来进行相应处理。
if (disposition == null) {
// Check if plain
MimeBodyPart mbp = (MimeBodyPart)part;
if (mbp.isMimeType("text/plain")) {
// Handle plain
} else {
// Special non-attachment cases here of image/gif, text/html, ...
}
...
}
练习 5. 如何发送附件
在这个练习中,我们创建了一个程序,它能够发送含附件的消息。
需要更多练习的帮助,请参阅关于练习。
先决条件:
· 练习 2. 如何发送第一条消息
框架代码:
任务 1:
skeleton code 中已经包含代码以获取初始邮件 session。
任务 2:
根据 session 获取一个Message并设置它的头部域:to、from 和 subject(主题)。
任务 3:
为消息正文内容创建BodyPart,并以消息正文为内容。
任务 3 的帮助:
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText("Here's the file");
任务 4:
创建一个Multipart从而将正文内容和附件结合。将正文内容添加到 multipart 中。
任务 4 的帮助:
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);
任务 5:
为附件创建第二个BodyPart。
任务 6:
获取作为DataSource的附件。
任务 6 的帮助:
DataSource source = new FileDataSource(filename);
任务 7:
?将数据源设置为消息部分的DataHandler。同时设置原始文件名。
任务 7 的帮助:
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(filename);
任务 8:
将消息的第二部件添加到 multipart。
任务 9:
将消息内容设置成 multipart。
任务 9 的帮助:
message.setContent(multipart);
任务 10:
发送消息。
任务 11:
在命令行上编译并运行程序—传递 SMTP 服务器、from地址、to地址和文件名。这就以附件形式发送了您的文件。
任务 11 的帮助:
java AttachExample SMTP.Server from@address to@address filename
任务 12:
请用您的标准邮件阅读程序(Eudora、Outlook Express、pine...)检查,确保消息已收到。
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
public class AttachExample {
public static void main (String args[]) throws Exception {
String host = args[0];
String from = args[1];
String to = args[2];
String filename = args[3];
// Get system properties
Properties props = System.getProperties();
// Setup mail server
props.put("mail.smtp.host", host);
// Get session
Session session = Session.getInstance(props, null);
// Define message
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
message.setSubject("Hello JavaMail Attachment");
// Create the message part
BodyPart messageBodyPart = new MimeBodyPart();
// Fill the message
messageBodyPart.setText("Here's the file");
// Create a Multipart
Multipart multipart = new MimeMultipart();
// Add part one
multipart.addBodyPart(messageBodyPart);
//
// Part two is attachment
//
// Create second body part
messageBodyPart = new MimeBodyPart();
// Get the attachment
DataSource source = new FileDataSource(filename);
// Set the data handler to the attachment
messageBodyPart.setDataHandler(new DataHandler(source));
// Set the filename
messageBodyPart.setFileName(filename);
// Add part two
multipart.addBodyPart(messageBodyPart);
// Put parts in message
message.setContent(multipart);
// Send the message
Transport.send(message);
}
}
对 HTML 消息的处理
发送基于 HTML 文件格式消息的工作量比发送纯文本消息多,虽然不一定非要这些多余的工作量。如何选择完全取决于给定的请求。
HTML 消息的发送:
若您所要做的全部事情是发送一份 HTML 文件的等价物作为消息,但让邮件阅读者为不能提取任何内嵌图像或相关片段而担心的话,可以使用Message的setContent()方法,把内容当作一个String传入,并将内容类型设置成text/html。
String htmlText = "<H1>Hello</H1>" +
"<img src=/"http://www.jguru.com/images/logo.gif/">";
message.setContent(htmlText, "text/html"));
在接收端,如果您用 JavaMail API 提取消息,API 中没有内建的显示 HTML 消息的东西。 JavaMail API 只把它看成一串字节流。要显示 HTML 文件格式的消息,您必需使用 Swing JEditorPane或其它第三方 HTML 格式查看器组件。
if (message.getContentType().equals("text/html")) {
String content = (String)message.getContent();
JFrame frame = new JFrame();
JEditorPane text = new JEditorPane("text/html", content);
text.setEditable(false);
JScrollPane pane = new JScrollPane(text);
frame.getContentPane().add(pane);
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.show();
}
在消息中引入图像:
另一方面,如果您想让 HTML 文件格式内容的消息完整(内嵌的图像作为消息的一部分),您必需把图像作为附件,并且用一个给定的cid URL 引用图像,其中cid是图像附件Content-ID头的引用。
嵌入图像的过程与附加文件到消息的过程非常相似,唯一的区别在于您必需通过设置 MimeMultipart 构造器中的子类型(或者说用setSubType())告知MimeMultipart各个相关部件,并且将这个图像的Content-ID头设置成随机字符串,作为图像的src在img标记中使用。完整的演示如下。
String file = ...;
// Create the message
Message message = new MimeMessage(session);
// Fill its headers
message.setSubject("Embedded Image");
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
// Create your new message part
BodyPart messageBodyPart = new MimeBodyPart();
String htmlText = "<H1>Hello</H1>" +
"<img src=/"cid:memememe/">";
messageBodyPart.setContent(htmlText, "text/html");
// Create a related multi-part to combine the parts
MimeMultipart multipart = new MimeMultipart("related");
multipart.addBodyPart(messageBodyPart);
// Create part for the image
messageBodyPart = new MimeBodyPart();
// Fetch the image and associate to part
DataSource fds = new FileDataSource(file);
messageBodyPart.setDataHandler(new DataHandler(fds));
messageBodyPart.setHeader("Content-ID","memememe");
// Add part to multi-part
multipart.addBodyPart(messageBodyPart);
// Associate multi-part with message
message.setContent(multipart);
练习 6. 如何发送含图像的 HTML 消息
在这个练习中,我们创建了一个程序,它能够发送含图像附件(图像显示在 HTML 消息内部)的 HTML 消息。
需要更多练习的帮助,请参阅关于练习。
先决条件:
· 练习 5. 如何发送附件
框架代码:
· logo.gif
任务 1:
框架代码已经包含代码以获取初始邮件 session、创建消息正文以及填充消息头(to、from 和 subject)。
任务 2:
创建一个 HTML 文件格式消息内容的BodyPart。
任务 3:
创建一个 HTML 文件格式内容的文本字符串。在 HTML 文件中引入一个图像的引用(<img src="...">),它和邮件消息处于同一目录。
任务 3 的帮助:
使用一个cid URL。图像的 content-id 可以稍后再定。
String htmlText = "<H1>Hello</H1>" +
"<img src=/"cid:memememe/">";
任务 4:
设置消息部件的内容。确保将 MIME 的类型定为text/html。
任务 4 的帮助:
messageBodyPart.setContent(htmlText, "text/html");
任务 5:
创建一个Multipart从而将正文内容和附件结合。确保已指定相关部分。然后正文内容将添加到 multipart 中。
任务 5 的帮助:
MimeMultipart multipart = new MimeMultipart("related");
multipart.addBodyPart(messageBodyPart);
任务 6:
为附件创建第二个BodyPart。
任务 7:
获取作为DataSource的附件,并把消息部件的数据源设置为该DataHandler。
任务 8:
设置Content-ID头,使 HTML 文件中给定的部件匹配图像引用。
任务 8 的帮助:
messageBodyPart.setHeader("Content-ID","memememe");
任务 9:
将消息的第二部件添加到 multipart,并将消息的内容设置成 multipart。
任务 10:
发送消息。
任务 11:
在命令行上编译并运行程序—传递 SMTP 服务器、from地址、to地址和文件名。这样会把图片当作 HTML 文本中内嵌的图片发送。
任务 11 的帮助:
java HtmlImageExample SMTP.Server from@address to@address filename
任务 12:
请检查邮件阅读程序辨认出的消息是否为 HTML 格式文件,而且消息中显示的是图片,不是外部附件文件的链接。
任务 12 的帮助:
如果邮件阅读程序无法显示 HTML 格式文件的消息,可以考虑把它发给朋友(让他们试试)。
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
public class HtmlImageExample {
public static void main (String args[]) throws Exception {
String host = args[0];
String from = args[1];
String to = args[2];
String file = args[3];
// Get system properties
Properties props = System.getProperties();
// Setup mail server
props.put("mail.smtp.host", host);
// Get session
Session session = Session.getDefaultInstance(props, null);
// Create the message
Message message = new MimeMessage(session);
// Fill its headers
message.setSubject("Embedded Image");
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
// Create your new message part
BodyPart messageBodyPart = new MimeBodyPart();
// Set the HTML content, be sure it references the attachment
String htmlText = "<H1>Hello</H1>" +
"<img src=/"cid:memememe/">";
// Set the content of the body part
messageBodyPart.setContent(htmlText, "text/html");
// Create a related multi-part to combine the parts
MimeMultipart multipart = new MimeMultipart("related");
// Add body part to multipart
multipart.addBodyPart(messageBodyPart);
// Create part for the image
messageBodyPart = new MimeBodyPart();
// Fetch the image and associate to part
DataSource fds = new FileDataSource(file);
messageBodyPart.setDataHandler(new DataHandler(fds));
// Add a header to connect to the HTML
messageBodyPart.setHeader("Content-ID","<memememe>");
// Add part to multi-part
multipart.addBodyPart(messageBodyPart);
// Associate multi-part with message
message.setContent(multipart);
// Send message
Transport.send(message);
}
}
总结
JavaMail API 是一个 Java 包,用于阅读、编写及发送电子邮件消息(包括附件)。您能用它建立基于标准的电子邮件客户机,它配置了各种因特网邮件协议包括 SMTP、POP、IMAP 和 MIME,还包括相关的 NNTP、S/MIME 及其它协议。
此 API 自然的划分为两个部件。第一部件着眼于独立于使用协议的发送、接收及管理消息,而第二部件则着眼于协议的特定用途。本教程的目的在于展示如何利用 API 的第一部分,并不试图涉及协议供应商。
核心 JavaMail API 由七个类组成— Session、Message、Address、Authenticator、Transport、Store及Folder —它们都来自javax.mail、即 JavaMail API 顶级包。我们用这些类完成了大量常见的电子邮件任务,包括发送消息、检索消息、删除消息、认证、回复消息、转发消息、管理附件、处理基于 HTML 文件格式的消息以及搜索或过滤邮件列表。
最后,提供了大量按部就班的练习来协助说明提出的概念。希望会有助于您将电子邮件功能性添加到独立于平台的 Java 应用程序。
用 JavaMail API,您所能做的比这里多得多。以下资源是这个课程和练习的补充:
· 从 JavaMail API home page 下载 JavaMail 1.2 API。
· JavaMail API 版本 1.2 和 1.1.3 需要JavaBeans Activation Framework 的支持。
· JavaMail-interest mailing list是 Sun 的开发者论坛。
· Sun 的 JavaMail FAQ 用 applet 和 servlet 的形式提出了 JavaMail 的用途,还提出了特定于协议的问题。
· 教程作者 John Zukowski 对 jGuru's JavaMail FAQ 站点进行维护。
· 想知道其它用户怎么使用 JavaMail 吗?请查看 Sun 的 第三方产品。
· 如果您需要更多 JavaMail 的详细信息,请阅读 Rick Grehan 的"How JavaMail keeps it simple"(Lotus Developer Network,2000 年 6 月)。
· Benoit Marchal 提出如何用 Java 和 XML 实现纯文本及 HTML 文件格式的时事通讯,请参考这个由两部分组成的系列“Managing e-zines with JavaMail and XSLT”第一部分(developerWorks,2001 年 3 月)和 第 2 部分 (developerWorks,2001 年 4 月)。
· "Linking Applications with E-mail"(Lotus Developer Network,2000 年 5 月)论述了群件如何促进应用程序间的交流、协作及配合。