多种方法使用Java发送电子邮件
第一件我们需要知道的事情是,通用电子邮件库实际上是包裹在JavaMail外层的API,所以无论我们选择哪种API,我们都需要JavaMail库。您可能还需要JavaBeans激活框架(JavaBeans Activation Framework (JAF)),该框架将负责处理关于邮件选项的更复杂的内容。最后一个在“购物列表”上的就是通用电子邮件库(Commons Email),您需要从JAF中将JavaMail库中的mail.jar、JAF中的activation.jar和通用电子邮件库中的commons-email-1.0.jar添加到classpath的设置中。
我们现在已经拥有了支持部件的工具包,让我们从使用JavaMail发送简单的电子邮件开始讲解,我们将提出所有的设置放到一个单独的静态类MailSettings当中,这样做将在比较代码的时候容易些。
在发送电子邮件之前,您还需要知道SMTP服务器的主机名,SMTP服务器是负责将您的邮件发送到外部世界去的机器。
JavaMail使用了Session类的概念来保存诸如SMTP主机和认证的信息,主要想法是基于会话(Sessions)在Java虚拟机中可以被隔离,这可以阻止恶意代码窃取其他用户在其他会话中的信息,这些信息可能包括用户名和密码等认证信息。“但是”,您可能会说,“在同一时间,我只在Java 虚拟机上运行一个应用程序,而且我相信我的代码。”JavaMail的目的是开发大型的邮件系统,它有一个具有复杂性的关联层,您可以绕过没有经验的用户,我们用以下的例子来说明:
Properties props=new Properties();
props.put("mail.smtp.host",MailSettings.smtpHost);
Session session=Session.getDefaultInstance(props,null);
此处,并没有创建新的会话,您只是从会话工厂(session factory)中得到并通过Properties的实例来进行设置,我们只对SMTP主机进行了设置和传送,在得到默认的实例的同时创建了一个共享的会话,现在我们可以使用这个会话来创建邮件消息了。
Message message=new MimeMessage(session);
JavaMail中有一个Message类,各种各样的消息都是它的子类,如果查看了JavaMail的API,您会发现它只有一个子类: MimeMessage。JavaMail是被设计为通用的电子邮件框架的,所以显然存在冗余的抽象。总之,我们已经使用会话创建了一个 MimeMessage,现在我们需要来填充这个消息。
message.setFrom(new InternetAddress (MailSettings.fromAddress, MailSettings.fromName));
再次利用抽象,JavaMail有一个Address(地址)类,其他所有的地址类型皆源于此,但是我们现在只关心发送国际互联网的电子邮件,所以我们制造一个InternetAddress,这个地址用来表示电子邮件的来源和一个用于显示的“个人”名字。现自我们来设置邮件将发向何方。
message.setRecipient(Message.RecipientType.TO, new InternetAddress(MailSettings.toAddress));
地址的抽象将再次出现,我们设定接收器和接收器的类型,从此处开始,至少纯文本的电子邮件可以稳定地传送了,我们只需要设定邮件的标题、信文并打上时间戳。
message.setSubject(MailSettings.messageSubject);
message.setText(MailSettings.messageBody1);
message.setSentDate(new Date());
此时,我们已经准备好发送消息了。
Transport.send(message);
这个对Transport类的调用将会去查找适当的会话,并找出如何发送消息,尽管这样做看上去有些不直观。当我们完成这一步的时候,我们的邮件就已经发送出去了。此时,我们还需要添加代码来捕获三种JavaMail可能抛出的异常,它们是AddressException、 MessagingException和UnsupportedEncodingException. 但这就是最基本的使用JavaMail发送邮件的方法。
现在,让我们来看看如何使用Apache的通用电子邮件软件包(参见MailCommons.java)来完成同样的工作,通用电子邮件库是一系列类的集合,它们基于您所要发送的邮件类型,其中最简单的是SimpleEmail类,不需要建立任何会话或属性列表:
SimpleEmail email=new SimpleEmail();
email.setHostName(MailSettings.smtpHost);
以上代码创建了我们的邮件并指定它通过我们选定的SMTP服务器发送,因为通用电子邮件库只处理国际互联网的电子邮件,所以就不必在创建InternetAddress实例上浪费时间了,我们可以简单地设定来源地址:
email.setFrom(MailSettings.fromAddress,MailSettings.fromName);
我们只需要添加一个地址到收信人列表当中,而不是发送接收器的类型:
email.addTo(MailSettings.toAddress);
同样,设定邮件的标题和信文与发送邮件都很简单。
email.setSubject(MailSettings.messageSubject);
email.setMsg(MailSettings.messageBody1);
email.send();
我们所需要去捕获的异常只有EmailException这一种,所以,您应该可以看出通过隐藏所有的框架和会话管理,事情变得非常简单并且代码更容易阅读。
当然,这是我们所能发送的最简单的电子邮件,我们假设SMTP服务器不需要认证,我们发送的邮件只有一个收件人,并没有发送邮件副本。让我们先来看看认证,SMTP认证(SMTP AUTH)需要用户名和密码来发送邮件,在JavaMail中(参见MailJavaMail2.java),需要创建一个认证者(Authenticator)来返回所需的认证证书:
class ForcedAuthenticator extends Authenticator {
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(MailSettings.smtpUsername,
MailSettings.smtpPassword);
}
}
当我们创建会话的时候,就给出这个认证者的一个实例。
Session session=Session.getDefaultInstance(props,new ForcedAuthenticator());
然而,这里有一点要注意,这种做法将默认的会话绑定到了该认证者,因此您需要一个指向它的引用(reference)以再次取得这个会话。当然,您也可以转而使用Session.getInstance(),来创建一个唯一的不共享的实例,但是这将依靠您自己来管理会话的实例。
现在让我们来看看如果使用通用电子邮件库的方法来进行认证(参见MailCommons2.java),您将只看到一行新的语句:
email.setAuthentication(MailSettings.smtpUsername,MailSettings.smtpPassword);这就解决了认证过程的所有痛苦。
还有另外一种认证方式,那就是使用在邮件服务中的POP-before-SMTP,它并没有实现SMTP AUTH,其实SMTP AUTH是SMTP之后的一种扩展,而POP-before-SMTP的工作方式则是将受密码控制的邮件接收者和发送邮件的能力进行绑定,只有当用户经过 POP协议访问某个受密码保护的POP邮件服务器一段时间之后,此用户才能够发送邮件。在JavaMail中,为了发送邮件,你需要编写打开POP信箱的代码,这将涉及JavaMail接收端的工作,我们将在下一篇文章中讲解相关内容;而对于通用电子邮件库来讲,事情就容易得多了:
email.setPopBeforeSMTP(true,popHost,popUsername,popPassword);
这使您可以操作与POP-before-SMTP相关的所有功能,而根本不需要考虑处理与POP相关的工作。
让我们再来看看如何设定邮件的抄送收件人,现在JavaMail的Message类有一个setRecipients方法,该方法可以接受一个 InternetAddress的数组,但这样做并不精巧,如果您已经有了一个储存电子邮件地址的字符串数组,那就可以结束这种复杂方法的考验。
ArrayList ccs=new ArrayList();
for(String s:MailSettings.ccAddresses) ccs.add(new InternetAddress(s));
message.setRecipients(Message.RecipientType.CC,
(InternetAddress[]) ccs.toArray(new InternetAddress[ccs.size()]));
现在让我们来看看通用电子邮件库,我们发现了两件事情:首先,setTo、setCc和setBcc方法都使用了InternetAddress的集合作为它们的参数,这样做更符合Java的现行实践;其次,addTo、addCc和addBcc方法将创建和添加收件人列表变得轻松。(参见 MailCommons3.java)
for(String s:MailSettings.ccAddresses) email.addCc(s);
这样的代码更加清晰。
最后,让我们来看看如何发送包含内嵌图片的HTML格式的电子邮件,在此,JavaBeans激活框架将与JavaMail将协同工作,来协助图片的编码。在JavaMail的API中,我们首先创建一个MimeMultipart的实例,然后创建MimeBodyParts的实例,并将它们组装到MimeMultipart的实例中,所以,嵌入单个图片,比如uk-builder-com.gif这个图片文件,并将它显示在HTML消息中,我们需要完成以下步骤(参见HtmlJavaMail.java)
MimeMultipart multipart=new MimeMultipart();
BodyPart msgBodyPart=new MimeBodyPart();
msgBodyPart.setContent("<H1>Hi! From HtmlJavaMail</H1>
<img src=\"cid:logo\">","text/html");
BodyPart embedImage=new MimeBodyPart();
DataSource ds=new URLDataSource(new URL(MailSettings.inlineImage));
embedImage.setDataHandler(new DataHandler(ds));
embedImage.setHeader("Content-ID","");
multipart.addBodyPart(msgBodyPart);
multipart.addBodyPart(embedImage);
message.setContent(multipart);
第一个BodyPart将它的内容设置为一个字符串和“text/html”的内容类型,注意,在我们设置的HTML代码中,IMG标签指向了一个内容标识符(content-id),这就是MIME(多用途互联网邮件扩充协议)消息部分的名称,该消息将含有GIF格式的标识图文件,为了嵌入这个图片,我们创建了另一个BodyPart和一个JAF的数据源(data source),JAF类库将负责管理内容的工作;然后我们将使用这个DataSource在BodyPart上添加一个DataHandler(数据处理器),这样就可以读出我们的URL所指向的内容了。最后,我们还要在BodyPart的头部设定Content-ID,这样它就可以作为内嵌图片被访问了。我们创建的那些BodyParts将被添加到MimeMultipart的实例中,为此,我们使用setContent()方法而不是调用 setText()方法。最棘手的部分莫过于管理这些内容标识符了,我们在此只完成了嵌入一个图片的工作,但今后每添加一个图片,您都需要在HTML中加入一个BodyPart和一个内容标识符。
现在让我们来看看使用通用电子邮件库方法的版本(参见HtmlMailCommons.java):
HtmlEmail email=new HtmlEmail();…
String cid=email.embed(new URL(MailSettings.inlineImage),"Builder AU Logo");
email.setHtmlMsg("<H1>Hi! From MailJavaMail3</H1><img src=\"cid:"+cid+"\">");
email.setTextMsg("Your email client does not support HTML messages, sorry");
这种方法简短了很多,而且这一版本拥有更多的功能:当电子邮件客户端不能处理HTML时,它将显示备用的文字。我们从创建一个SimpleEmail实例改变为创建一个HtmlEmail实例。为了嵌入一个图片,我们只需要简单地调用HtmlEmail的嵌入方法,这将返回一个字符串,该字符串含有被生成和管理的内嵌图片内容标识符的信息,我们可以直接使用这些信息来生成HTML内容。我们只需要简单地对适当的字符串调用setHtmlMsg方法来进行设定,setTextMsg将设定不支持HTML的客户端所显示的文字,然后我们就可以发送邮件了。
但是,到目前为止,您可能正在想“直接使用JavaMail的意义是什么呢?”答案是,如果您的目的是发送电子邮件,那么对于大多数情况,通用电子邮件库仅仅需要少量的复杂代码就可以完成您的需求,但是它连一点控制邮件会话和接收邮件的功能都没有,如果您的应用软件不需要大规模地进行邮件转换或邮件阅读的操作,那么应该不会造成不便。当然,如果您需要进行上述操作,那您就已经在使用JavaMail库了。这里唯一的一个警告就是,在我写这篇文章之时,通用电子邮件库发布的是1.0版本,其API中可能有一些缺陷和漏洞,但它确实可以工作。现在您应该没有借口不让您的应用软件在工作的时候发送状态邮件了。