找了好久,終于見到某位仁兄給出了javax.mail運作機制的詳細分析,感激感激,收藏了,以防找不到。
++++++++++++++++++++++++++++++++++++++++++++
打開JavaMail.jar檔案,我們将發現在javax.mail的包下面存在着一些核心類:Session、Message、Address、Authenticator、Transport、Store、Folder。而且在 javax.mail.internet包中還有一些常用的子類。
A.Session
Session類定義了基本的郵件會話。就像Http會話那樣,我們進行收發郵件的工作都是基于這個會話的。Session對象利用了java.util.Properties對象獲得了郵件伺服器、使用者名、密碼資訊和整個應用程式都要使用到的共享資訊。
Session類的構造方法是私有的,是以我們可以使用Session類提供的getDefaultInstance()這個靜态工廠方法獲得一個預設的Session對象:
1 Properties props = new Properties();// fill props with any information
2 Session session = Session.getDefaultInstance(props, null);
或者使用getInstance()這個靜态工廠方法獲得自定義的Session:
1 Properties props = new Properties();// fill props with any information
2 Session session = Session.getInstance(props, null);
從上面的兩個例子中不難發現,getDefaultInstance()和getInstance()方法的第二個參數都是null,這是因為在上面的例子中并沒有使用到郵件授權,下文中将對授權進行詳細介紹。
從很多的執行個體看,在對mail server進行通路的過程中使用共享的Session是足夠的,即使是工作在多個使用者郵箱的模式下也不例外。
B.Message
當我們建立了Session對象後,便可以構造被 發送的資訊體了。在這裡SUN提供了Message類型來幫助開發者完成這項工作。由于Message是一個抽象類,大多數情況下,我們使用 javax.mail.internet.MimeMessage這個子類,該類是使用MIME類型、MIME資訊頭的郵箱資訊。資訊頭隻能使用US- ASCII字元,而非ASCII字元将通過編碼轉換為ASCII的方式使用。
為了建立一個MimeMessage對象,我們必須将Session對象作為MimeMessage構造方法的參數傳入:
1 MimeMessage message = new MimeMessage(session);
注意:對于MimeMessage類來講存在着多種構造方法,比如使用輸入流作為參數的構造方法。
在建立了MimeMessage對象後,我們需要設定它的各個part,對于MimeMessage類來說,這些part就是MimePart接口。最基本的設定資訊内容的方法就是通過表示資訊内容和米麼類型的參數調用setContent()方法:
1 message.setContent(“Hello”, “text/plain”);
然而,如果我們所使用的MimeMessage中資訊内容是文本的話,我們便可以直接使用setText()方法來友善的設定文本内容:
1 message.setText(“Hello”);
前面所講的兩種方法,對于文本資訊,後者更為合适。而對于其它的一些資訊類型,比如HTML資訊,則要使用前者。
别忘記了,使用setSubject()方法對郵件設定郵件主題:
1 message.setSubject(“First”);
C.Address
到這裡,我們已經建立了Session和Message,下面将介紹如何使用郵件位址類:Address。像Message一樣,Address類也是一個抽象類,是以我們将使用javax.mail.internet.InternetAddress這個子類。
通過傳入代表郵件位址的字元串,我們可以建立一個郵件位址類:
1 Address address = new InternetAddress(“[email protected]”);
如果要在郵件位址後面增加名字的話,可以通過傳遞兩個參數:代表郵件位址和名字的字元串來建立一個具有郵件位址和名字的郵件位址類:
1 Address address = new InternetAddress(“[email protected]”, “George Bush”);
本文在這裡所講的郵件位址類是為了設定郵件資訊的發信人和收信人而準備的,在建立了郵件位址類後,我們通過message的setFrom()和setReplyTo()兩種方法設定郵件的發信人:
1 message.setFrom(address);message.setReplyTo(address);
若在郵件中存在多個發信人位址,我們可用addForm()方法增加發信人:
1 Address address[] = …;
2 message.addFrom(address);
為了設定收信人,我們使用addRecipient()方法增加收信人,此方法需要使用Message.RecipientType的常量來區分收信人的類型:
1 message.addRecipient(type, address)
下面是Message.RecipientType的三個常量:
1 Message.RecipientType.TO
2 Message.RecipientType.CC
3 Message.RecipientType.BCC
是以,如果我們要發送郵件給總統,并發用一個副本給第一夫人的話,下面的方法将被用到:
1 Address toAddress = new InternetAddress(“[email protected]”);
2 Address ccAddress = new InternetAddress(“[email protected]”);
3 message.addRecipient(Message.RecipientType.TO, toAddress);
4 message.addRecipient(Message.RecipientType.CC, ccAddress);
JavaMail API并沒有提供檢查郵件位址有效性的機制。當然我們可以自己完成這個功能:驗證郵件位址的字元是否按照RFC822規定的格式書寫或者通過DNS伺服器上的MX記錄驗證等。
D.Authenticator
像java.net類那樣,JavaMail API通過使用授權者類 (Authenticator)以使用者名、密碼的方式通路那些受到保護的資源,在這裡“資源”就是指郵件伺服器。在javax.mail包中可以找到這個 JavaMail的授權者類(Authenticator)。
在使用Authenticator這個抽象類時,我們必須采用繼承該抽象類的方式,并且該繼 承類必須具有傳回PasswordAuthentication對象(用于存儲認證時要用到的使用者名、密 碼)getPasswordAuthentication()方法。并且要在Session中進行注冊,使Session能夠了解在認證時該使用哪個類。
下面代碼片斷中的MyAuthenticator就是一個Authenticator的子類:
1 Properties props = new Properties();// fill props with any information
2 Authenticator auth = new MyAuthenticator();
3 Session session = Session.getDefaultInstance(props, auth);
E.Transport
在發送資訊時,Transport類将被用到。這個類實作了發送資訊的協定(通稱為SMTP),此類是一個抽象類,我們可以使用這個類的靜态方法send()來發送消息:
1 Transport.send(message);
當然,方法是多樣的。我們也可由Session獲得相應協定對應的Transport執行個體。并通過傳遞使用者名、密碼、郵件伺服器主機名等參數建立與郵件伺服器的連接配接,并使用sendMessage()方法将資訊發送,最後關閉連接配接:
1 message.saveChanges(); // implicit with send()
2 Transport transport = session.getTransport(“smtp”);
3 transport.connect(host, username, password);
4 transport.sendMessage(message, message.getAllRecipients());
5 transport.close();
評論:上面的方法是一個很好的方法,尤其是在我們在同一個郵件伺服器上發送多個郵件時。因為 這時我們将在連接配接郵件伺服器後連續發送郵件,然後再關閉掉連接配接。send()這個基本的方法是在每次調用時進行與郵件伺服器的連接配接的,對于在同一個郵件服 務器上發送多個郵件來講可謂低效的方式。
注意:如果需要在發送郵件過程中監控mail指令的話,可以在發送前設定debug标志:
1 session.setDebug(true);
F.Store和Folder
接 收郵件和發送郵件很類似都要用到Session。但是在獲得Session後,我們需要從Session中擷取特定類型的Store,然後連接配接到 Store,這裡的Store代表了存儲郵件的郵件伺服器。在連接配接Store的過程中,極有可能需要用到使用者名、密碼或者Authenticator。
1 Store store = session.getStore(“pop3″);
2 store.connect(host, username, password);
在連接配接到Store後,一個Folder對象即目錄對象将通過Store的getFolder()方法被傳回,我們可從這個Folder中讀取郵件資訊:
1 Folder folder = store.getFolder(“INBOX”);
2 folder.open(Folder.READ_ONLY);
3 Message message[] = folder.getMessages();
上面的例子首先從Store中獲得INBOX這個Folder(對于POP3協定隻有一個名為INBOX的Folder有效),然後以隻讀(Folder.READ_ONLY)的方式打開Folder,最後調用Folder的 getMessages()方法得到目錄中所有Message的數組。
注意:對于POP3協定隻 有一個名為INBOX的Folder有效,而對于IMAP協定,我們可以通路多個Folder(想想前面講的IMAP協定)。而且SUN在設計 Folder的getMessages()方法時采取了很智能的方式:首先接收新郵件清單,然後再需要的時候(比如讀取郵件内容)才從郵件伺服器讀取郵件 内容。
在讀取郵件時,我們可以用Message類的getContent()方法接收郵件或是writeTo()方法将郵件儲存,getContent()方法隻接收郵件内容(不包含郵件頭),而writeTo()方法将包括郵件頭。
1 System.out.println(((MimeMessage)message).getContent());
在讀取郵件内容後,别忘記了關閉Folder和Store。
1 folder.close(aBoolean);
2 store.close();
傳遞給Folder.close()方法的boolean類型參數表示是否在删除操作郵件後更新Folder。
1.發送郵件
在獲得了Session後,建立并填入郵件資訊,然後發送它到郵件伺服器。這便是使用Java Mail API發送郵件的過程,在發送郵件之前,我們需要設定SMTP伺服器:通過設定Properties的mail.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++)…
上面的代碼所作的是從郵箱中讀取每個郵件,并且顯示郵件的發信人位址和主題。從技術角度講,這裡存在着一個異常的可能:當發信人位址為空時,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…
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(“[email protected]”));
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 content
messageBodyPart = new MimeBodyPart();
messageBodyPart.setDataHandler(message.getDataHandler());
// Add part to multi part
multipart.addBodyPart(messageBodyPart);
// Associate multi-part with message
forward.setContent(multipart);
// Send message
Transport.send(forward);
7.使用附件
附件作為與郵件相關的資源經常以文本、表格、圖檔等格式出現,如流行的郵件用戶端一樣,我們可以用JavaMail API從郵件中擷取附件或是發送帶有附件的郵件。
A.發送帶有附件的郵件
發送帶有附件的郵件的過程有些類似轉發郵件,我們需要建立一個完整郵件的各個郵件體部分,在第一個部分(即我們的郵件内容文字)後,增加一個具有DataHandler的附件而不是在轉發郵件時那樣複制第一個部分的DataHandler。
如果我們将檔案作為附件發送,那麼要建立FileDataSource類型的對象作為附件資料源;如果從URL讀取資料作為附件發送,那麼将要建立URLDataSource類型的對象作為附件資料源。
然後将這個資料源(FileDataSource或是URLDataSource)對象作為DataHandler類構造方法的參數傳入,進而建立一個DataHandler對象作為資料源的DataHandler。
接着将這個DataHandler設定為郵件體部分的DataHandler。這樣就完成了郵件體與附件之間的關聯工作,下面的工作就是BodyPart的setFileName()方法設定附件名為原檔案名。
最後将兩個郵件體放入到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 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實作發送帶有附件的郵件,則必須上傳附件給servlet,這時需要注意送出頁面form中對編碼類型的設定應為multipart/form-data。
B.讀取郵件中的附件
讀取郵件中的附件的過程要比發送它的過程複雜一點。因為帶有附件的郵件是多部分組成的,我們必須處理每一個部分獲得郵件的内容和附件。
但 是如何辨識郵件資訊内容和附件呢?Sun在Part類(BodyPart類實作的接口類)中提供了getDisposition()方法讓開發者獲得郵件 體部分的部署類型,當該部分是附件時,其傳回之将是Part.ATTACHMENT。但附件也可以沒有部署類型的方式存在或者部署類型為 Part.INLINE,無論部署類型為Part.ATTACHMENT還是Part.INLINE,我們都能把該郵件體部分導出儲存。
Multipart mp = (Multipart)message.getContent();
for (int i=0, n=multipart.getCount(); i…
下列代碼中使用了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 = ”<h1>Hello</h1>” ;
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頭的引用。
處理内嵌圖檔就像向郵件中添加附件一樣,不同之處在于我們必須通過設定圖檔附件所在的郵件體部分的header中Content-ID為一個随機字元串,并在HTML中img的src标記中設定為該字元串。這樣就完成了圖檔附件與HTML的關聯。
String file = …;
// Create the messageMessage 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>” ;
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”,”");
// Add part to multi-part
multipart.addBodyPart(messageBodyPart);
// Associate multi-part with message
message.setContent(multipart);
9.在郵件中搜尋短語
JavaMail API提供了過濾器機制,它被用來建立搜尋短語。這個短語由javax.mail.search包中的SearchTerm抽象類來定義,在定義後我們便可以使用Folder的Search()方法在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”字元串或者發信人位址為[email protected]的郵件。
SearchTerm st = new OrTerm(new SubjectTerm(“ADV:”), new FromStringTerm(“[email protected]”));
Message[] msgs = folder.search(st);