从一个应用程序发送邮件是很容易的操作,然而你肯定希望从服务器收取邮件也这样简单。这里将为你演示使用javaMail收取邮件的过程。
以前,我们介绍过使用javaMail和Apache Commons Email发送邮件的方法。其中,Commons Email没有包含收取邮件功能,只是集中在发送邮件功能上。对于邮件的收取,我们必须使用到javaMail功能。
发送邮件只需要协议(SMTP),其过程相对比较简单。而接收邮件则包含两个协议,即POP3和IMAP。POP3是老协议,它提供一个单一收信箱,以存放一定顺序的邮件信息。IMAP相对比较新,它为邮件提供连接到一个层次关系的文件入口,其中一个入口即为收信箱。当然还有其它可以使用的协议,POP3和IMAP只是其中一种安全性很好的协议。javaMail将这些协议提炼为一种邮件仓库(Store)的概念,这一仓库为文件等级的集合。这种提炼意味着仓库包含很多内容,但我们只需要弄清楚在一个服务文件夹中浏览与导航邮件信息的过程,而实际处理邮件协议的任务则通过javaMail调用Provider来完成。
javaMail的Provider操作服务于POP3和IMAP,这就确保了POP3S和IMAPS的安全性。值得提醒的是,你可以将另外的Providers嵌入到javaMail API以处理其它诸如NNTP或本地存储邮件的协议。在Sun主页上列出这方面的第三方Providers。
让我们看看一个实际的例子:MailPage。我们的任务是建立一个最新发送邮件的网页,该页面允许远程用户无需直接访问web服务器的情况下更新邮件,而这一过程只需要通过发送一个邮件到一个特定的邮件帐号。
实现这一过程的关键是使用MailRetriever。在操作之前需要4种数据:邮件用户帐号名称,帐号密码,服务器的主机名,以及Provider名称。“pop3”,“pop3s”,“map”,“maps”就是标准的数据。现在我们可以用getMail方法获得邮件。首先我们需要一个Session来发送邮件。我们获得一个Session:
Properties props=System.getProperties();
session=Session.getInstance(props,null);
从这一Session,我们可以得到适合于provider的一个Store:
store=session.getStore(emailprovider);
使用服务器名称、用户名以及密码,通过调用Store的connect 方法来连接到Session:
store.connect(emailserver,emailuser,emailpassword);
现在,我们可以浏览Folder的层次。为了得到层次的顶部,我们要求查看一个默认的Folder。
folder=store.getDefaultFolder();
每一Folder 可以包含很多其它的文件夹。例如,可以通过getFolder方法浏览收信箱。
inboxfolder=folder.getFolder("INBOX");
此时,可以查看IMAP树中的文件。而对于POP3,它以单一队列将文件映射到一个收信箱。为了操作文件中的信息,需要打开文件夹:
inboxfolder.open(Folder.READ_ONLY);
我们以READ_ONLY模式打开文件夹,因为我们不想改变文件的性质和内容。而使用READ_WRITE将会改变文件。现在,我们可以获得文件夹内的任何一组信息:
Message[] msgs=inboxfolder.getMessages();
可以反复迭代这一过程,但这一方法效率很低。我们采用的方法是指定API,以得到信息标题感兴趣的部分,由此可以获得这些信息的标题。输入FetchProfile类,这一类允许指定一个API:
FetchProfile fp=new FetchProfile();
fp.add("Subject");
我们只对Subject的标题感兴趣,所以将Subject标题加入到FetchProfile。你可以使用两个技巧:FetchProfile.Item.ENVELOPE (针对于普通From, To, CC等)或者 FetchProfile.Item.CONTENT_INFO (针对于有关信息内容大小和类型的信息)。现在我们请求文件在整个域内接收信息:
inboxfolder.fetch(msgs,fp);
我们将反复操作整个Subject标题。对于MailPage,以“MailPage”的特定字符串作为查找对象。以数组的顶部开始查找,直到找到合适的信息:
for(int j=msgs.length-1;j>=0;j--) {
if(msgs[j].getSubject().startsWith("MailPage:")) {
setLatestMessage(msgs[j]);<
break;
}
}
回到setLatestMessage,现在我们只需要关闭和保持文件:
inboxfolder.close(false);
store.close();
根据判断邮件是否已被删除,文件即关闭或保留收件箱。这里我们给出false(没有被删除),因为我们不想改变邮件箱的状态。
这样就可以完成邮件的收取过程。现在我们可以通过例子中的setLatestMessage方法排列邮件。你所需要注意的第一件事为邮件的不同格式。邮件的基本格式是纯文本。如何才能识别这种格式?第一步就是检查信息的内容类型,可通过“text/plain”识别一个纯文本信息:
void setLatestMessage(Message message) {
…
if(message.getContentType().startsWith("text/plain")) {
latestMessage=new RenderablePlainText(message);
} else {
latestMessage=new RenderableMessage(message);
}
在程序段使用startsWith比较邮件类型。虽然信息内容的类型字符串包含很多信息,但我们只关心的是起始部分。例如,我们已经建立一个名为Renderable的接口,这一接口可查看被解码的邮件,它包括邮件主题,邮件文本以及附件。我们使用RenderablePlainText类来解码文本信息,而附件可以被忽略。如果查看RenderablePlainText构造器,可以看到读取内容的过程:
public RenderablePlainText(Message message) throws MessagingException,
IOException {
subject=message.getSubject().substring("MailPage:".length());
bodytext=(String)message.getContent();
}
邮件主题很容易获得。请注意Part接口是来自于前面的文章,即通过这一接口我们将BodyPart和MimeBodyPart构建一个信息。当解码邮件时,必须使用Message方法处理Parts的层次。
处理MultiPart信息的过程相对比较复杂。MultiPart意味着Part由多个部分组成,每一部分的内容必须接收到。例如,我们使用RenderableMessage类解析HTML文本和附件信息。
public RenderableMessage(Message m) throws MessagingException,IOException {
subject=m.getSubject().substring("MailPage:".length());
attachments=new ArrayList();
extractPart(m);
}
RenderableMessage是一个简单的handler,它能够反复调用Parts以检查我们是否使用extractPart方法。首先RenderableMessage检查给定的Part是否是一个MultiPart,如果是,则运行这些子部分并调用extractPart:
private void extractPart(final Part part) throws MessagingException,
IOException {
if(part.getContent() instanceof Multipart) {
Multipart mp=(Multipart)part.getContent();
for(int i=0;i
然后,我们采用前面处理纯文本的方法来处理text/html的内容。
if(part.getContentType().startsWith("text/html")) {
if(bodytext==null) {
bodytext=(String)part.getContent();
} else {
bodytext=bodytext+"<HR/>"+(String)part.getContent();
}
}
这里没有什么特别。对于多个text/html部分,其原始文本将被连成一个长字符串。现在可以得到这一过程的关键之处:
else if(!part.getContentType().startsWith("text/plain")) {
Attachment attachment=new Attachment();
attachment.setContenttype(part.getContentType());
attachment.setFilename(part.getFileName());
如果内容不是纯文本,将其当成附件,并建立我们的Attachment instance(即包含文件名、内容类型以及内容的字节),并导入内容类型和文件名。我们将建立一个ByteArrayOutputStream:
InputStream in=part.getInputStream();
ByteArrayOutputStream bos=new ByteArrayOutputStream();
然后复制内容:
byte[] buffer=new byte[8192];
int count=0;
while((count=in.read(buffer))>=0) bos.write(buffer,0,count);
in.close();
最后,从ByteArrayOutputStream 设置我们的Attachment instance 的内容,并将Attachment添加到附件的ArrayList:
attachment.setContent(bos.toByteArray());
attachments.add(attachment);
}
}
以上就是获得附件的过程。当你运行MailRetriever并获得最新信息时,MailRetriever的main方法运行MailRetriever,即提供用户名、密码、服务器名以及provider类型作为参数。以下为运行MailRetriever后的输出:
Subject:Mail with attachments
Body Text:<HTML>
<BODY>Here's some images of the <B>Space Shuttle</B>.
<DIV><BR class="khtml-block-placeholder"></DIV>
<DIV><IMG src="cid:A30781A5-E965-4A36-96FB-70206218303D@local"></DIV>
<DIV><IMG src="cid:0A960B93-A970-4F56-8B02-820969A26643@local"></DIV>
</BODY></HTML>
2 attachments
9603752.jpg 81312 bytes of (image/jpeg; x-unix-mode=0644; name=9603752.jpg)
1976_04380L.jpg 32089 bytes of (image/jpeg; x-unix-mode=0644;
name=1976_04380L.jpg)
此时,附件为两张图片。请注意在信息的HMTL文本顶部,<IMG>并非指的是图片文件名,而是cid: URLs。在下一次我们将介绍包含MailRetriever代码来处理这一方面的内容。
运行以上例子程序之后,会出现一个警告,即告诉邮件服务器javaMail有漏洞,可以使用FAQ处理这一漏洞。当我开始编写这篇文章时,我决定使用Gmail,Google邮件服务以及POP3支持。然而出乎我的意料,Google的POP3服务与RFC不兼容,即只要读取一个来自RFC的信息,无论如何操作,这一信息马上消失。对于其它邮件服务器,邮件服务器版本在解析RFC标准时总会提示一个逻辑错误,如何处理这一问题?其中一个有效方法就是使用javaMail调试,即当进入到Session时,调用setDebug方法来调试,可获得所有的邮件信息。当收取附件时,如果附件太大,在调试时可以将系统的属性“mail.debug”设置为true。
javaMail包含影响provider行为的所有属性。例如,使用POP3,标准provider的属性包括超时设定、认证以及如何与邮件服务器相互通讯。由此可解决以上提及的兼容问题。然而使用标准POP3,有些POP3 providers会自动地删除已有信息,然而POP3 REST命令可以将邮件箱处于最初的读信状态。将属性mail.pop3.rsetbeforequit设置为true可激活POP3 provider的行为。属性名的“pop3s”部分代表provider名称,所以你可将名称改为“pop3s”。以下为代码示例:
Properties props=System.getProperties();
props.setProperty("mail.pop3s.rsetbeforequit","true");
props.setProperty("mail.pop3.rsetbeforequit","true");
session=Session.getInstance(props,null);
session.setDebug(true);
下个月,我们将介绍使用MailRetriever来完成收取附件的过程,以及处理其它类型的邮件,比如大容量的附件。
你可以在此下载本文的源代码。
DJ Walker-Morgan是一名开发人员咨询师,专长是java和用户对用户的消息发送和远程会议技术。
责任编辑:张琎