JavaMail(JAVA郵件服務) API詳解

工程師WWW發表於2015-10-29
一、JavaMail API簡介
JavaMail API
是讀取、撰寫、傳送電子資訊的可選包。我們可用它來建立如EudoraFoxmailMS Outlook Express一般的郵件使用者代理程式(Mail User Agent,簡稱MUA)。而不是像sendmail或者其它的郵件傳輸代理(Mail Transfer Agent,簡稱MTA)程式那樣可以傳送、遞送、轉發郵件。從另外一個角度來看,我們這些電子郵件使用者日常用MUA程式來讀寫郵件,而MUA依賴著MTA處理郵件的遞送。
在清楚了到MUAMTA之間的關係後,讓我們看看JavaMail API是如何提供資訊訪問功能的吧!JavaMail API被設計用於以不依賴協議的方式去傳送和接收電子資訊,這個API被分為兩大部分:

基本功能:如何以不依賴於協議的方式傳送接收電子資訊,這也是本文所要描述的,不過在下文中,大家將看到這只是一廂情願而已。
第二個部分則是依賴特定協議的,比如SMTPPOPIMAPNNTP協議。在這部分的JavaMail API是為了和伺服器通訊,並不在本文的內容中。

二、相關協議一覽
在我們步入JavaMail API之前,先看一下API所涉及的協議。以下便是大家日常所知、所樂於使用的4大資訊傳輸協議:
SMTP
POP
IMAP
MIME
當然,上面的4個協議,並不是全部,還有NNTP和其它一些協議可用於傳輸資訊,但是由於不常用到,所以本文便不提及了。理解這4個基本的協議有助於我們更好的使用JavaMail API。然而JavaMail API是被設計為與協議無關的,目前我們並不能克服這些協議的束縛。確切的說,如果我們使用的功能並不被我們選擇的協議支援,那麼JavaMail API並不可能如魔術師一樣神奇的賦予我們這種能力。

1
SMTP
簡單郵件傳輸協議定義了遞送郵件的機制。在下文中,我們將使用基於Java-Mail的程式與公司或者ISPSMTP伺服器進行通訊。這個SMTP伺服器將郵件轉發到接收者的SMTP伺服器,直至最後被接收者通過POP或者IMAP協議獲取。這並不需要SMTP伺服器使用支援授權的郵件轉發,但是卻的確要注意SMTP伺服器的正確設定(SMTP伺服器的設定與JavaMail API無關)。

2
POP
POP
是一種郵局協議,目前為第3個版本,即眾所周知的POP3POP定義了一種使用者如何獲得郵件的機制。它規定了每個使用者使用一個單獨的郵箱。大多數人在使用POP時所熟悉的功能並非都被支援,例如檢視郵箱中的新郵件數量。而這個功能是微軟的Outlook內建的,那麼就說明微軟Outlook之類的郵件客戶端軟體是通過查詢最近收到的郵件來計算新郵件的數量來實現前面所說的功能。因此在我們使用JavaMail API時需要注意,當需要獲得如前面所講的新郵件數量之類的資訊時,我們不得不自己進行計算。

3
IMAP
IMAP
使用在接收資訊的高階協議,目前版本為第4版,所以也被稱為IMAP4。需要注意的是在使用IMAP時,郵件伺服器必須支援該協議。從這個方面講,我們並不能完全使用IMAP來替代POP,不能期待IMAP在任何地方都被支援。假如郵件伺服器支援IMAP,那麼我們的郵件程式將能夠具有以下被IMAP所支援的特性:每個使用者在伺服器上可具有多個目錄,這些目錄能在多個使用者之間共享。
其與POP相比高階之處顯而易見,但是在嘗試採取IMAP時,我們認識到它並不是十分完美的:由於IMAP需要從其它伺服器上接收新資訊,將這些資訊遞送給使用者,維護每個使用者的多個目錄,這都為郵件伺服器帶來了高負載。並且IMAPPOP的一個不同之處是POP使用者在接收郵件時將從郵件伺服器上下載郵件,而IMAP允許使用者直接訪問郵件目錄,所以在郵件伺服器進行備份作業時,由於每個長期使用此郵件系統的使用者所用的郵件目錄會佔有很大的空間,這將直接導致郵件伺服器上磁碟空間暴漲。

4
MIME
MIME
並不是用於傳送郵件的協議,它作為多用途郵件的擴充套件定義了郵件內容的格式:資訊格式、附件格式等等。一些RFC標準都涉及了MIMERFC 822, RFC 2045, RFC 2046, RFC 2047,有興趣的Matrixer可以閱讀一下。而作為JavaMail API的開發者,我們並不需關心這些格式定義,但是這些格式被用在了程式中。

5
NNTP和其它的第三方協議
正因為JavaMail API在設計時考慮到與第三方協議實現提供商之間的分離,故我們可以很容易的新增一些第三方協議。SUN維護著一個第三方協議實現提供商的列表:http://java.sun.com/products/javamail/Third_Party.html,通過此列表我們可以找到所需要的而又不被SUN提供支援的第三方協議:比如NNTP這個新聞組協議和S/MIME這個安全的MIME協議。

三、安裝
1
.安裝JavaMail
為了使用JavaMail API,需要從http://java.sun.com/products/javamail/downloads/index.html下載檔名格式為javamail-[version].zip的檔案(這個檔案中包括了JavaMail實現),並將其中的mail.jar檔案新增到CLASSPATH中。這個實現提供了對SMTPIMAP4POP3的支援。
注意:在安裝JavaMail實現之後,我們將在demo目錄中發現許多有趣的簡單例項程式。
在安裝了JavaMail之後,我們還需要安裝JavaBeans Activation Framework,因為這個框架是JavaMail API所需要的。如果我們使用J2EE的話,那麼我們並無需單獨下載JavaMail,因為它存在於J2EE.jar中,只需將J2EE.jar加入到CLASSPATH即可。

2
.安裝JavaBeans Activation Framework
http://java.sun.com/products/javabeans/glasgow/jaf.html下載JavaBeans Activation Framework,並將其新增到CLASSPATH中。此框架增加了對任何資料塊的分類、以及對它們的處理的特性。這些特性是JavaMail API需要的。雖然聽起來這些特性非常模糊,但是它對於我們的JavaMail API來說只是提供了基本的MIME型別支援。
到此為止,我們應當把mail.jaractivation.jar都新增到了CLASSPATH中。
當然如果從方便的角度講,直接把這兩個Jar檔案複製到JRE目錄的lib/ext目錄中也可以。

四、初次認識JavaMail API
1
.瞭解我們的JavaMail環境
A
.縱覽JavaMail核心類結構
開啟JavaMail.jar檔案,我們將發現在javax.mail的包下面存在著一些核心類:SessionMessageAddressAuthenticatorTransportStoreFolder。而且在javax.mail.internet包中還有一些常用的子類。
B
Session
Session
類定義了基本的郵件會話。就像Http會話那樣,我們進行收發郵件的工作都是基於這個會話的。Session物件利用了java.util.Properties物件獲得了郵件伺服器、使用者名稱、密碼資訊和整個應用程式都要使用到的共享資訊。
Session
類的構造方法是私有的,所以我們可以使用Session類提供的getDefaultInstance()這個靜態工廠方法獲得一個預設的Session物件:
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.getInstance(props, null);
從上面的兩個例子中不難發現,getDefaultInstance()getInstance()方法的第二個引數都是null,這是因為在上面的例子中並沒有使用到郵件授權,下文中將對授權進行詳細介紹。
從很多的例項看,在對mail server進行訪問的過程中使用共享的Session是足夠的,即使是工作在多個使用者郵箱的模式下也不例外。

C
Message
當我們建立了Session物件後,便可以被髮送的構造資訊體了。在這裡SUN提供了Message型別來幫助開發者完成這項工作。由於Message是一個抽象類,大多數情況下,我們使用javax.mail.internet.MimeMessage這個子類,該類是使用MIME型別、MIME資訊頭的郵箱資訊。資訊頭只能使用US-ASCII字元,而非ASCII字元將通過編碼轉換為ASCII的方式使用。
為了建立一個MimeMessage物件,我們必須將Session物件作為MimeMessage構造方法的引數傳入:
MimeMessage message = new MimeMessage(session);
注意:對於MimeMessage類來講存在著多種構造方法,比如使用輸入流作為引數的構造方法。

在建立了MimeMessage物件後,我們需要設定它的各個part,對於MimeMessage類來說,這些part就是MimePart介面。最基本的設定資訊內容的方法就是通過表示資訊內容和米麼型別的引數呼叫setContent()方法:
message.setContent("Hello", "text/plain");
然而,如果我們所使用的MimeMessage中資訊內容是文字的話,我們便可以直接使用setText()方法來方便的設定文字內容。
message.setText("Hello");
前面所講的兩種方法,對於文字資訊,後者更為合適。而對於其它的一些資訊型別,比如HTML資訊,則要使用前者。
別忘記了,使用setSubject()方法對郵件設定郵件主題:
message.setSubject("First");

D
Address
到這裡,我們已經建立了SessionMessage,下面將介紹如何使用郵件地址類:Address。像Message一樣,Address類也是一個抽象類,所以我們將使用javax.mail.internet.InternetAddress這個子類。
通過傳入代表郵件地址的字串,我們可以建立一個郵件地址類:
Address address = new InternetAddress("president@whitehouse.gov"); 
如果要在郵件地址後面增加名字的話,可以通過傳遞兩個引數:代表郵件地址和名字的字串來建立一個具有郵件地址和名字的郵件地址類:
Address address = new InternetAddress("president@whitehouse.gov", "George Bush"); 
本文在這裡所講的郵件地址類是為了設定郵件資訊的發信人和收信人而準備的,在建立了郵件地址類後,我們通過messagesetFrom()setReplyTo()兩種方法設定郵件的發信人:
message.setFrom(address);
message.setReplyTo(address);
若在郵件中存在多個發信人地址,我們可用addForm()方法增加發信人:
Address address[ ] = ...;message.addFrom(address);
為了設定收信人,我們使用addRecipient()方法增加收信人,此方法需要使用Message.RecipientType的常量來區分收信人的型別:
message.addRecipient(type, address)
下面是Message.RecipientType的三個常量:
Message.RecipientType.TO
Message.RecipientType.CC
Message.RecipientType.BCC
因此,如果我們要傳送郵件給總統,併發用一個副本給第一夫人的話,下面的方法將被用到:
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
並沒有提供檢查郵件地址有效性的機制。當然我們可以自己完成這個功能:驗證郵件地址的字元是否按照RFC822規定的格式書寫或者通過DNS伺服器上的MX記錄驗證等。

E
Authenticator
java.net類那樣,JavaMail API通過使用授權者類(Authenticator)以使用者名稱、密碼的方式訪問那些受到保護的資源,在這裡資源就是指郵件伺服器。在javax.mail包中可以找到這個JavaMail的授權者類(Authenticator)。
在使用Authenticator這個抽象類時,我們必須採用繼承該抽象類的方式,並且該繼承類必須具有返回PasswordAuthentication物件(用於儲存認證時要用到的使用者名稱、密碼)getPasswordAuthentication()方法。並且要在Session中進行註冊,使Session能夠了解在認證時該使用哪個類。
下面程式碼片斷中的MyAuthenticator就是一個Authenticator的子類。
Properties props = new Properties();
// fill props with any information
Authenticator auth = new MyAuthenticator();
Session session = Session.getDefaultInstance(props, auth);
FTransport
在傳送資訊時,Transport類將被用到。這個類實現了傳送資訊的協議(通稱為SMTP),此類是一個抽象類,我們可以使用這個類的靜態方法send()來傳送訊息:
Transport.send(message);
當然,方法是多樣的。我們也可由Session獲得相應協議對應的Transport例項。並通過傳遞使用者名稱、密碼、郵件伺服器主機名等引數建立與郵件伺服器的連線,並使用sendMessage()方法將資訊傳送,最後關閉連線:
message.saveChanges();
// implicit with send()
Transport transport = session.getTransport("smtp");
transport.connect(host, username, password);
transport.sendMessage(message, message.getAllRecipients());
transport.close();
評論:上面的方法是一個很好的方法,尤其是在我們在同一個郵件伺服器上傳送多個郵件時。因為這時我們將在連線郵件伺服器後連續傳送郵件,然後再關閉掉連線。send()這個基本的方法是在每次呼叫時進行與郵件伺服器的連線的,對於在同一個郵件伺服器上傳送多個郵件來講可謂低效的方式。
注意:如果需要在傳送郵件過程中監控mail命令的話,可以在傳送前設定debug標誌:
session.setDebug(true)


G
StoreFolder
接收郵件和傳送郵件很類似都要用到Session。但是在獲得Session後,我們需要從Session中獲取特定型別的Store,然後連線到Store,這裡的Store代表了儲存郵件的郵件伺服器。在連線Store的過程中,極有可能需要用到使用者名稱、密碼或者Authenticator
// Store store = session.getStore("imap");
Store store = session.getStore("pop3");
store.connect(host, username, password);
在連線到Store後,一個Folder物件即目錄物件將通過StoregetFolder()方法被返回,我們可從這個Folder中讀取郵件資訊:
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
Message message[] = folder.getMessages();
上面的例子首先從Store中獲得INBOX這個Folder(對於POP3協議只有一個名為INBOXFolder有效),然後以只讀(Folder.READ_ONLY)的方式開啟Folder,最後呼叫FoldergetMessages()方法得到目錄中所有Message的陣列。

注意:對於POP3協議只有一個名為INBOXFolder有效,而對於IMAP協議,我們可以訪問多個Folder(想想前面講的IMAP協議)。而且SUN在設計FoldergetMessages()方法時採取了很智慧的方式:首先接收新郵件列表,然後再需要的時候(比如讀取郵件內容)才從郵件伺服器讀取郵件內容。
在讀取郵件時,我們可以用Message類的getContent()方法接收郵件或是writeTo()方法將郵件儲存,getContent()方法只接收郵件內容(不包含郵件頭),而writeTo()方法將包括郵件頭。
System.out.println(((MimeMessage)message).getContent());
在讀取郵件內容後,別忘記了關閉FolderStore
folder.close(aBoolean);
store.close();
傳遞給Folder.close()方法的boolean 型別參數列示是否在刪除操作郵件後更新Folder 

H
.繼續向前進!
在講解了以上的七個Java Mail核心類定義和理解了簡單的程式碼片斷後,下文將詳細講解怎樣使用這些類實現JavaMail API所要完成的高階功能。

五、使用JavaMail API
在明確了JavaMail API的核心部分如何工作後,本人將帶領大家學習一些使用Java Mail API任務案例。
1
.傳送郵件
在獲得了Session後,建立並填入郵件資訊,然後傳送它到郵件伺服器。這便是使用Java Mail API傳送郵件的過程,在傳送郵件之前,我們需要設定SMTP伺服器:通過設定Propertiesmail.smtp.host屬性。
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
.接收郵件
為了在讀取郵件,我們獲得了session,並且連線到了郵箱的相應store,開啟相應的Folder,然後得到我們想要的郵件,當然別忘記了在結束時關閉連線。
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();
上面的程式碼所作的是從郵箱中讀取每個郵件,並且顯示郵件的發信人地址和主題。從技術角度講,這裡存在著一個異常的可能:當發信人地址為空時,getFrom()[0]將丟擲異常。

下面的程式碼片斷有效的說明了如何讀取郵件內容,在顯示每個郵件發信人和主題後,將出現使用者提示從而得到使用者是否讀取該郵件的確認,如果輸入YES的話,我們可用Message.writeTo(java.io.OutputStream os)方法將郵件內容輸出到控制檯上,關於Message.writeTo()的具體用法請看JavaMail API
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
.刪除郵件和標誌
設定與message相關的Flags是刪除郵件的常用方法。這些Flags表示了一些系統定義和使用者定義的不同狀態。在Flags類的內部類Flag中預定義了一些標誌:
Flags.Flag.ANSWERED
Flags.Flag.DELETED
Flags.Flag.DRAFT
Flags.Flag.FLAGGED
Flags.Flag.RECENT
Flags.Flag.SEEN
Flags.Flag.USER
但需要在使用時注意的:標誌存在並非意味著這個標誌被所有的郵件伺服器所支援。例如,對於刪除郵件的操作,POP協議不支援上面的任何一個。所以要確定哪些標誌是被支援的??通過訪問一個已經開啟的Folder物件的getPermanetFlags()方法,它將返回當前被支援的Flags類物件。
刪除郵件時,我們可以設定郵件的DELETED標誌: 
message.setFlag(Flags.Flag.DELETED, true);
但是首先要採用READ_WRITE的方式開啟Folder
folder.open(Folder.READ_WRITE);
在對郵件進行刪除操作後關閉Folder時,需要傳遞一個true作為對刪除郵件的擦除確認。
folder.close(true);
Folder
類中另一種用於刪除郵件的方法expunge()也同樣可刪除郵件,但是它並不為sun提供的POP3實現支援,而其它第三方提供的POP3實現支援或者並不支援這種方法。
另外,介紹一種檢查某個標誌是否被設定的方法:Message.isSet(Flags.Flag flag)方法,其中引數為被檢查的標誌。

4
.郵件認證
我們在前面已經學會了如何使用Authenticator類來代替直接使用使用者名稱和密碼這兩字串作為Session.getDefaultInstance()或者Session.getInstance()方法的引數。在前面的小試牛刀後,現在我們將瞭解到全面認識一下郵件認證。
我們在此取代了直接使用郵件伺服器主機名、使用者名稱、密碼這三個字串作為連線到POP3 Store的方式,使用儲存了郵件伺服器主機名資訊的屬性檔案,並在獲得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();

PopupAuthenticator
類繼承了抽象類Authenticator,並且通過過載Authenticator類的getPasswordAuthentication()方法返回PasswordAuthentication類物件。而getPasswordAuthentication()方法的引數param是以逗號分割的使用者名稱、密碼組成的字串。
import javax.mail.*;
import java.util.*;
public class PopupAuthenticator extends Authenticator {  
public PasswordAuthentication getPasswordAuthentication(String param) {
String username, password;    
StringTokenizer st = new StringTokenizer(param, ","); 
username = st.nextToken();
password = st.nextToken(); 
return new PasswordAuthentication(username, password);  
}
}
5.回覆郵件
回覆郵件的方法很簡單:使用Message類的reply()方法,通過配置回覆郵件的收件人地址和主題(如果沒有提供主題的話,系統將預設將“Re作為郵件的主體),這裡不需要設定任何的郵件內容,只要複製發信人或者reply-to到新的收件人。而reply()方法中的boolean參數列示是否將郵件回覆給傳送者(引數值為false),或是恢復給所有人(引數值為true)。
補充一下,reply-to地址需要在發信時使用setReplyTo()方法設定。
MimeMessage reply = (MimeMessage)message.reply(false); reply.setFrom(new InternetAddress("president@whitehouse.gov")); reply.setText("Thanks");Transport.send(reply); 
6
.轉發郵件
轉發郵件的過程不如前面的回覆郵件那樣簡單,它將建立一個轉發郵件,這並非一個方法就能做到。
每個郵件是由多個部分組成,每個部分稱為一個郵件體部分,是一個BodyPart類物件,對於MIME型別郵件來講就是MimeBodyPart類物件。這些郵件體包含在成為Multipart的容器中對於MIME型別郵件來講就是MimeMultiPart類物件。在轉發郵件時,我們建立一個文字郵件體部分和一個被轉發的文字郵件體部分,然後將這兩個郵件體放到一個Multipart中。說明一下,複製一個郵件內容到另一個郵件的方法是僅複製它的DataHandler(資料處理者)即可。這是由JavaBeans Activation Framework定義的一個類,它提供了對郵件內容的操作命令的訪問、管理了郵件內容操作,是不同的資料來源和資料格式之間的一致性介面。
// Create the message to forward 
Message forward = new MimeMessage(session); 
// Fill in header
forward.setSubject("Fwd: " + message.getSubject());
forward.setFrom(new InternetAddress(from)); 
forward.addRecipient(Message.RecipientType.TO,   new InternetAddress(to)); 
// Create your new message part
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(  "Here you go with the original message:/n/n");
// Create a multi-part to combine the parts
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart); 
// Create and fill part for the forwarded contentmessage
BodyPart = new MimeBodyPart();
messageBodyPart.setDataHandler(message.getDataHandler());
// Add part to multi part
multipart.addBodyPart(messageBodyPart); 
// Associate multi-part with messageforward. 
setContent(multipart); 
// Send message
Transport.send(forward);

7
.使用附件
附件作為與郵件相關的資源經常以文字、表格、圖片等格式出現,如流行的郵件客戶端一樣,我們可以用JavaMail API從郵件中獲取附件或是傳送帶有附件的郵件。

A
.傳送帶有附件的郵件
傳送帶有附件的郵件的過程有些類似轉發郵件,我們需要建立一個完整郵件的各個郵件體部分,在第一個部分(即我們的郵件內容文字)後,增加一個具有DataHandler的附件而不是在轉發郵件時那樣複製第一個部分的DataHandler

如果我們將檔案作為附件傳送,那麼要建立FileDataSource型別的物件作為附件資料來源;如果從URL讀取資料作為附件傳送,那麼將要建立URLDataSource型別的物件作為附件資料來源。

然後將這個資料來源(FileDataSource或是URLDataSource)物件作為DataHandler類構造方法的引數傳入,從而建立一個DataHandler物件作為資料來源的DataHandler

接著將這個DataHandler設定為郵件體部分的DataHandler。這樣就完成了郵件體與附件之間的關聯工作,下面的工作就是BodyPartsetFileName()方法設定附件名為原檔名。

最後將兩個郵件體放入到Multipart中,設定郵件內容為這個容器Multipart,傳送郵件。
// 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 messagemessage
BodyPart.setText("Pardon Ideas");
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart); 
// Part two is attachmentmessageBodyPart = 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實現傳送帶有附件的郵件,則必須上傳附件給servlet,這時需要注意提交頁面form中對編碼型別的設定應為multipart/form-data
<form enctype="multipartform-data"     method='post action="/myservlet"'>
<input type="file"bane="thefile">
<input type= "submit" value="upload">

B.讀取郵件中的附件
讀取郵件中的附件的過程要比傳送它的過程複雜一點。因為帶有附件的郵件是多部分組成的,我們必須處理每一個部分獲得郵件的內容和附件。
但是如何辨別郵件資訊內容和附件呢?SunPart類(BodyPart類實現的介面類)中提供了getDisposition()方法讓開發者獲得郵件體部分的部署型別,當該部分是附件時,其返回之將是Part.ATTACHMENT。但附件也可以沒有部署型別的方式存在或者部署型別為Part.INLINE,無論部署型別為Part.ATTACHMENT還是Part.INLINE,我們都能把該郵件體部分匯出儲存。
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方法是自定義的方法,它根據附件的檔名建立一個檔案,如果本地磁碟上存在名為附件的檔案,那麼將在檔名後增加數字表示區別。然後從郵件體中讀取資料寫入到本地檔案中(程式碼省略)。
// from saveFile()
File file = new File(filename);
for (int i=0; file.exists(); i++) {
  file = new File(filename+i);
}
以上是郵件體部分被正確設定的簡單例子,如果郵件體部分的部署型別為null,那麼我們通過獲得郵件體部分的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, ...  }
...}

8.處理HTML郵件
前面的例子中傳送的郵件都是以文字為內容的(除了附件),下面將介紹如何接收和傳送基於HTML的郵件。
A.傳送HTML郵件
假如我們需要傳送一個HTML檔案作為郵件內容,並使郵件客戶端在讀取郵件時獲取相關的圖片或者文字的話,只要設定郵件內容為html程式碼,並設定內容型別為text/html即可:
String htmlText = " Hello " +message.setContent(htmlText, "text/html"));
請注意:這裡的圖片並不是在郵件中內嵌的,而是在URL中定義的。郵件接收者只有線上時才能看到。
在接收郵件時,如果我們使用JavaMail API接收郵件的話是無法實現以HTML方式顯示郵件內容的。因為JavaMail API郵件內容視為二進位制流。所以要顯示HTML內容的郵件,我們必須使用JEditorPane或者第三方HTML展現元件。

以下程式碼顯示瞭如何使用JEditorPane顯示郵件內容:
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();}

B.在郵件中包含圖片
如果我們在郵件中使用HTML作為內容,那麼最好將HTML中使用的圖片作為郵件的一部分,這樣無論是否線上都會正確的顯示HTML中的圖片。處理方法就是將HTML中用到的圖片作為郵件附件並使用特殊的cid URL作為圖片的引用,這個cid就是對圖片附件的Content-ID頭的引用。
處理內嵌圖片就像向郵件中新增附件一樣,不同之處在於我們必須通過設定圖片附件所在的郵件體部分的headerContent-ID為一個隨機字串,並在HTMLimgsrc標記中設定為該字串。這樣就完成了圖片附件與HTML的關聯。
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 = " Hello" + "<CCID_FILE VALUES="/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 imagemessageBodyPart = new MimeBodyPart();
// Fetch the image and associate to par
tDataSource fds = new FileDataSource(file);
messageBodyPart.setDataHandler(new DataHandler(fds));
messageBodyPart.setHeader("Content-ID","");
// Add part to multi-part
multipart.addBodyPart(messageBodyPart);
// Associate multi-part with message
message.setContent(multipart);
9.在郵件中搜尋短語
JavaMail API
提供了過濾器機制,它被用來建立搜尋短語。這個短語由javax.mail.search包中的SearchTerm抽象類來定義,在定義後我們便可以使用FolderSearch()方法在Folder中查詢郵件:
SearchTerm st = ...;Message[] msgs = folder.search(st);
下面有22個不同的類(繼承了SearchTerm類)供我們使用:
AND terms (class AndTerm)
OR terms (class OrTerm)
NOT terms (class NotTerm)
SENT DATE terms (class SentDateTerm)
CONTENT terms (class BodyTerm)
HEADER terms (FromTerm / FromStringTerm, RecipientTerm / RecipientStringTerm, SubjectTerm, etc.)
使用這些類定義的斷語集合,我們可以構造一個邏輯表示式,並在Folder中進行搜尋。下面是一個例項:在Folder中搜尋郵件主題含有“ADV”字串或者發信人地址為friend@public.com的郵件。
SearchTerm st =   new OrTerm(    new SubjectTerm("ADV:"), 
new FromStringTerm("friend@public.com"));
Message[] msgs = folder.search(st);

六、參考資源
JavaMail API Home
Sun’s JavaMail API
基礎
JavaBeans Activation Framework Home
javamail-interest mailing list
Sun's JavaMail FAQ
jGuru's JavaMail FAQ
Third Party Products List
七、程式碼下載
http://java.sun.com/developer/onlineTraining/JavaMail/exercises.html 

相關文章