我們現(xiàn)在已經(jīng)擁有了支持部件的工具包,讓我們從使用JavaMail發(fā)送簡(jiǎn)單的電子郵件開始講解,我們將提出所有的設(shè)置放到一個(gè)單獨(dú)的靜態(tài)類MailSettings當(dāng)中,這樣做將在比較代碼的時(shí)候容易些。
在發(fā)送電子郵件之前,您還需要知道SMTP服務(wù)器的主機(jī)名,SMTP服務(wù)器是負(fù)責(zé)將您的郵件發(fā)送到外部世界去的機(jī)器。
JavaMail使用了Session類的概念來(lái)保存諸如SMTP主機(jī)和認(rèn)證的信息,主要想法是基于會(huì)話(Sessions)在Java虛擬機(jī)中可以被隔離,這可以阻止惡意代碼竊取其他用戶在其他會(huì)話中的信息,這些信息可能包括用戶名和密碼等認(rèn)證信息。“但是”,您可能會(huì)說(shuō),“在同一時(shí)間,我只在Java虛擬機(jī)上運(yùn)行一個(gè)應(yīng)用程序,而且我相信我的代碼。”JavaMail的目的是開發(fā)大型的郵件系統(tǒng),它有一個(gè)具有復(fù)雜性的關(guān)聯(lián)層,您可以繞過(guò)沒有經(jīng)驗(yàn)的用戶,我們用以下的例子來(lái)說(shuō)明:
Properties props=new Properties();
props.put("mail.smtp.host",MailSettings.smtpHost);
Session session=Session.getDefaultInstance(props,null);
此處,并沒有創(chuàng)建新的會(huì)話,您只是從會(huì)話工廠(session factory)中得到并通過(guò)Properties的實(shí)例來(lái)進(jìn)行設(shè)置,我們只對(duì)SMTP主機(jī)進(jìn)行了設(shè)置和傳送,在得到默認(rèn)的實(shí)例的同時(shí)創(chuàng)建了一個(gè)共享的會(huì)話,現(xiàn)在我們可以使用這個(gè)會(huì)話來(lái)創(chuàng)建郵件消息了。
Message message=new MimeMessage(session);
JavaMail中有一個(gè)Message類,各種各樣的消息都是它的子類,如果查看了JavaMail的API,您會(huì)發(fā)現(xiàn)它只有一個(gè)子類:MimeMessage。JavaMail是被設(shè)計(jì)為通用的電子郵件框架的,所以顯然存在冗余的抽象。總之,我們已經(jīng)使用會(huì)話創(chuàng)建了一個(gè)MimeMessage,現(xiàn)在我們需要來(lái)填充這個(gè)消息。
message.setFrom(new InternetAddress (MailSettings.fromAddress, MailSettings.fromName));
再次利用抽象,JavaMail有一個(gè)Address(地址)類,其他所有的地址類型皆源于此,但是我們現(xiàn)在只關(guān)心發(fā)送國(guó)際互聯(lián)網(wǎng)的電子郵件,所以我們制造一個(gè)InternetAddress,這個(gè)地址用來(lái)表示電子郵件的來(lái)源和一個(gè)用于顯示的“個(gè)人”名字。現(xiàn)自我們來(lái)設(shè)置郵件將發(fā)向何方。
message.setRecipient(Message.RecipientType.TO, new InternetAddress(MailSettings.toAddress));
地址的抽象將再次出現(xiàn),我們?cè)O(shè)定接收器和接收器的類型,從此處開始,至少純文本的電子郵件可以穩(wěn)定地傳送了,我們只需要設(shè)定郵件的標(biāo)題、信文并打上時(shí)間戳。
message.setSubject(MailSettings.messageSubject);
message.setText(MailSettings.messageBody1);
message.setSentDate(new Date());
此時(shí),我們已經(jīng)準(zhǔn)備好發(fā)送消息了。
Transport.send(message);
這個(gè)對(duì)Transport類的調(diào)用將會(huì)去查找適當(dāng)?shù)臅?huì)話,并找出如何發(fā)送消息,盡管這樣做看上去有些不直觀。當(dāng)我們完成這一步的時(shí)候,我們的郵件就已經(jīng)發(fā)送出去了。此時(shí),我們還需要添加代碼來(lái)捕獲三種JavaMail可能拋出的異常,它們是AddressException、MessagingException和UnsupportedEncodingException. 但這就是最基本的使用JavaMail發(fā)送郵件的方法。
現(xiàn)在,讓我們來(lái)看看如何使用Apache的通用電子郵件軟件包(參見MailCommons.java)來(lái)完成同樣的工作,通用電子郵件庫(kù)是一系列類的集合,它們基于您所要發(fā)送的郵件類型,其中最簡(jiǎn)單的是SimpleEmail類,不需要建立任何會(huì)話或?qū)傩粤斜恚?/P>
SimpleEmail email=new SimpleEmail();
email.setHostName(MailSettings.smtpHost);
以上代碼創(chuàng)建了我們的郵件并指定它通過(guò)我們選定的SMTP服務(wù)器發(fā)送,因?yàn)橥ㄓ秒娮余]件庫(kù)只處理國(guó)際互聯(lián)網(wǎng)的電子郵件,所以就不必在創(chuàng)建InternetAddress實(shí)例上浪費(fèi)時(shí)間了,我們可以簡(jiǎn)單地設(shè)定來(lái)源地址:
email.setFrom(MailSettings.fromAddress,MailSettings.fromName);
我們只需要添加一個(gè)地址到收信人列表當(dāng)中,而不是發(fā)送接收器的類型:
email.addTo(MailSettings.toAddress);
同樣,設(shè)定郵件的標(biāo)題和信文與發(fā)送郵件都很簡(jiǎn)單。
email.setSubject(MailSettings.messageSubject);
email.setMsg(MailSettings.messageBody1);
email.send();
我們所需要去捕獲的異常只有EmailException這一種,所以,您應(yīng)該可以看出通過(guò)隱藏所有的框架和會(huì)話管理,事情變得非常簡(jiǎn)單并且代碼更容易閱讀。
當(dāng)然,這是我們所能發(fā)送的最簡(jiǎn)單的電子郵件,我們假設(shè)SMTP服務(wù)器不需要認(rèn)證,我們發(fā)送的郵件只有一個(gè)收件人,并沒有發(fā)送郵件副本。讓我們先來(lái)看看認(rèn)證,SMTP認(rèn)證(SMTP AUTH)需要用戶名和密碼來(lái)發(fā)送郵件,在JavaMail中(參見MailJavaMail2.java),需要?jiǎng)?chuàng)建一個(gè)認(rèn)證者(Authenticator)來(lái)返回所需的認(rèn)證證書:
class ForcedAuthenticator extends Authenticator {
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(MailSettings.smtpUsername,
MailSettings.smtpPassword);
}
}
當(dāng)我們創(chuàng)建會(huì)話的時(shí)候,就給出這個(gè)認(rèn)證者的一個(gè)實(shí)例。
Session session=Session.getDefaultInstance(props,new ForcedAuthenticator());
然而,這里有一點(diǎn)要注意,這種做法將默認(rèn)的會(huì)話綁定到了該認(rèn)證者,因此您需要一個(gè)指向它的引用(reference)以再次取得這個(gè)會(huì)話。當(dāng)然,您也可以轉(zhuǎn)而使用Session.getInstance(),來(lái)創(chuàng)建一個(gè)唯一的不共享的實(shí)例,但是這將依靠您自己來(lái)管理會(huì)話的實(shí)例。
現(xiàn)在讓我們來(lái)看看如果使用通用電子郵件庫(kù)的方法來(lái)進(jìn)行認(rèn)證(參見MailCommons2.java),您將只看到一行新的語(yǔ)句:
email.setAuthentication(MailSettings.smtpUsername,MailSettings.smtpPassword);這就解決了認(rèn)證過(guò)程的所有痛苦。
還有另外一種認(rèn)證方式,那就是使用在郵件服務(wù)中的POP-before-SMTP,它并沒有實(shí)現(xiàn)SMTP AUTH,其實(shí)SMTP AUTH是SMTP之后的一種擴(kuò)展,而POP-before-SMTP的工作方式則是將受密碼控制的郵件接收者和發(fā)送郵件的能力進(jìn)行綁定,只有當(dāng)用戶經(jīng)過(guò)POP協(xié)議訪問(wèn)某個(gè)受密碼保護(hù)的POP郵件服務(wù)器一段時(shí)間之后,此用戶才能夠發(fā)送郵件。在JavaMail中,為了發(fā)送郵件,你需要編寫打開POP信箱的代碼,這將涉及JavaMail接收端的工作,我們將在下一篇文章中講解相關(guān)內(nèi)容;而對(duì)于通用電子郵件庫(kù)來(lái)講,事情就容易得多了:
email.setPopBeforeSMTP(true,popHost,popUsername,popPassword);
這使您可以操作與POP-before-SMTP相關(guān)的所有功能,而根本不需要考慮處理與POP相關(guān)的工作。
讓我們?cè)賮?lái)看看如何設(shè)定郵件的抄送收件人,現(xiàn)在JavaMail的Message類有一個(gè)setRecipients方法,該方法可以接受一個(gè)InternetAddress的數(shù)組,但這樣做并不精巧,如果您已經(jīng)有了一個(gè)儲(chǔ)存電子郵件地址的字符串?dāng)?shù)組,那就可以結(jié)束這種復(fù)雜方法的考驗(yàn)。
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()]));
現(xiàn)在讓我們來(lái)看看通用電子郵件庫(kù),我們發(fā)現(xiàn)了兩件事情:首先,setTo、setCc和setBcc方法都使用了InternetAddress的集合作為它們的參數(shù),這樣做更符合Java的現(xiàn)行實(shí)踐;其次,addTo、addCc和addBcc方法將創(chuàng)建和添加收件人列表變得輕松。(參見MailCommons3.java)
for(String s:MailSettings.ccAddresses) email.addCc(s);
這樣的代碼更加清晰。
最后,讓我們來(lái)看看如何發(fā)送包含內(nèi)嵌圖片的HTML格式的電子郵件,在此,JavaBeans激活框架將與JavaMail將協(xié)同工作,來(lái)協(xié)助圖片的編碼。在JavaMail的API中,我們首先創(chuàng)建一個(gè)MimeMultipart的實(shí)例,然后創(chuàng)建MimeBodyParts的實(shí)例,并將它們組裝到MimeMultipart的實(shí)例中,所以,嵌入單個(gè)圖片,比如uk-builder-com.gif這個(gè)圖片文件,并將它顯示在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);
第一個(gè)BodyPart將它的內(nèi)容設(shè)置為一個(gè)字符串和“text/html”的內(nèi)容類型,注意,在我們?cè)O(shè)置的HTML代碼中,IMG標(biāo)簽指向了一個(gè)內(nèi)容標(biāo)識(shí)符(content-id),這就是MIME(多用途互聯(lián)網(wǎng)郵件擴(kuò)充協(xié)議)消息部分的名稱,該消息將含有GIF格式的標(biāo)識(shí)圖文件,為了嵌入這個(gè)圖片,我們創(chuàng)建了另一個(gè)BodyPart和一個(gè)JAF的數(shù)據(jù)源(data source),JAF類庫(kù)將負(fù)責(zé)管理內(nèi)容的工作;然后我們將使用這個(gè)DataSource在BodyPart上添加一個(gè)DataHandler(數(shù)據(jù)處理器),這樣就可以讀出我們的URL所指向的內(nèi)容了。最后,我們還要在BodyPart的頭部設(shè)定Content-ID,這樣它就可以作為內(nèi)嵌圖片被訪問(wèn)了。我們創(chuàng)建的那些BodyParts將被添加到MimeMultipart的實(shí)例中,為此,我們使用setContent()方法而不是調(diào)用setText()方法。最棘手的部分莫過(guò)于管理這些內(nèi)容標(biāo)識(shí)符了,我們?cè)诖酥煌瓿闪饲度胍粋€(gè)圖片的工作,但今后每添加一個(gè)圖片,您都需要在HTML中加入一個(gè)BodyPart和一個(gè)內(nèi)容標(biāo)識(shí)符。
現(xiàn)在讓我們來(lái)看看使用通用電子郵件庫(kù)方法的版本(參見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");
這種方法簡(jiǎn)短了很多,而且這一版本擁有更多的功能:當(dāng)電子郵件客戶端不能處理HTML時(shí),它將顯示備用的文字。我們從創(chuàng)建一個(gè)SimpleEmail實(shí)例改變?yōu)閯?chuàng)建一個(gè)HtmlEmail實(shí)例。為了嵌入一個(gè)圖片,我們只需要簡(jiǎn)單地調(diào)用HtmlEmail的嵌入方法,這將返回一個(gè)字符串,該字符串含有被生成和管理的內(nèi)嵌圖片內(nèi)容標(biāo)識(shí)符的信息,我們可以直接使用這些信息來(lái)生成HTML內(nèi)容。我們只需要簡(jiǎn)單地對(duì)適當(dāng)?shù)淖址{(diào)用setHtmlMsg方法來(lái)進(jìn)行設(shè)定,setTextMsg將設(shè)定不支持HTML的客戶端所顯示的文字,然后我們就可以發(fā)送郵件了。
但是,到目前為止,您可能正在想“直接使用JavaMail的意義是什么呢?”答案是,如果您的目的是發(fā)送電子郵件,那么對(duì)于大多數(shù)情況,通用電子郵件庫(kù)僅僅需要少量的復(fù)雜代碼就可以完成您的需求,但是它連一點(diǎn)控制郵件會(huì)話和接收郵件的功能都沒有,如果您的應(yīng)用軟件不需要大規(guī)模地進(jìn)行郵件轉(zhuǎn)換或郵件閱讀的操作,那么應(yīng)該不會(huì)造成不便。當(dāng)然,如果您需要進(jìn)行上述操作,那您就已經(jīng)在使用JavaMail庫(kù)了。這里唯一的一個(gè)警告就是,在我寫這篇文章之時(shí),通用電子郵件庫(kù)發(fā)布的是1.0版本,其API中可能有一些缺陷和漏洞,但它確實(shí)可以工作。現(xiàn)在您應(yīng)該沒有借口不讓您的應(yīng)用軟件在工作的時(shí)候發(fā)送狀態(tài)郵件了。