1. 程式人生 > >JavaMail傳送和接收郵件API

JavaMail傳送和接收郵件API

一、JavaMail概述:

    JavaMail是由Sun定義的一套收發電子郵件的API,不同的廠商可以提供自己的實現類。但它並沒有包含在JDK中,而是作為JavaEE的一部分。

    廠商所提供的JavaMail服務程式可以有選擇地實現某些郵件協議,常見的郵件協議包括:

  • SMTP:簡單郵件傳輸協議,用於傳送電子郵件的傳輸協議;

  • POP3:用於接收電子郵件的標準協議;

  • IMAP:網際網路訊息協議,是POP3的替代協議。

    這三種協議都有對應SSL加密傳輸的協議,分別是SMTPS,POP3S和IMAPS。

    除JavaMail服務提供程式之外,JavaMail還需要JAF(JavaBeans Activation Framework)來處理不是純文字的郵件內容,這包括MIME(多用途網際網路郵件擴充套件)、URL頁面和檔案附件等內容。

    mail.jar:此JAR檔案包含JavaMail API和Sun提供的SMTP、IMAP和POP3服務提供程式;

    activation.jar:此JAR檔案包含JAF API和Sun的實現。

二、對相關協議的回顧:

    1、介紹

    在研究 JavaMail API 的細則之前,讓我們回顧用於 API 的協議。基本上,您會逐漸熟悉並喜愛的協議有四個:

    * SMTP

    * POP

    * IMAP

    * MIME

    您還將碰到 NNTP 和其它協議。理解所有協議的基本知識將有助於您理解如何使用 JavaMail API。雖然不瞭解這些協議您照樣可以用這個 API,卻不能夠克服那些基礎協議的侷限性。如果我們精選的協議不能支援某種效能,JavaMail API 決不能魔術般的將這種效能新增上去。(您很快就會看到,在處理 POP 時這將成為一個難題。)

    2、SMTP

    簡單郵件傳輸協議(Simple Mail Transfer Protocol,SMTP)由 RFC 821 定義。它定義了傳送電子郵件的機制。在 JavaMail API 環境中,您基於 JavaMail 的程式將和您的公司或因特網服務供應商的(Internet Service Provider's,ISP's)SMTP 伺服器通訊。SMTP 伺服器會中轉訊息給接收方 SMTP 伺服器以便最終讓使用者經由 POP 或 IMAP 獲得。這不是要求 SMTP 伺服器成為開放的中繼,儘管 SMTP 伺服器支援身份驗證,不過還是得確保它的配置正確。像配置伺服器來中繼訊息或新增刪除郵件賬號這類任務的實現,JavaMail API 中並不支援。

    3、POP

    POP 代表郵局協議(Post Office Protocol)。目前用的是版本 3,也稱 POP3,RFC 1939 定義了這個協議。POP 是一種機制,因特網上大多數人用它得到郵件。它規定每個使用者一個郵箱的支援。這就是它所能做的,而這也造成了許多混淆。使用POP 時,使用者熟悉的許多效能並不是由 POP 協議支援的,如檢視有幾封新郵件訊息這一效能。這些效能內建於如 Eudora 或Microsoft Outlook 之類的程式中,它們能記住一些事,諸如最近一次收到的郵件,還能計算出有多少是新的。所以當使用JavaMail API 時,如果您想要這類資訊,您就必須自己算。

    4、IMAP

    IMAP 是更高階的用於接收訊息的協議。在 RFC 2060 中被定義,IMAP 代表因特網訊息訪問協議(Internet Message Access Protocol),目前用的是版本 4,也稱 IMAP4。在用到 IMAP 時,郵件伺服器必需支援這個協議。不能僅僅把使用 POP 的程式用於 IMAP,並指望它支援 IMAP 所有效能。假設郵件伺服器支援 IMAP,基於 JavaMail 的程式可以利用這種情況 — 使用者在伺服器上有多個資料夾(folder),並且這些資料夾可以被多個使用者共享。

    因為有這一更高階的效能,您也許會認為所有使用者都會使用 IMAP。事實並不是這樣。要求伺服器接收新訊息,在使用者請求時傳送到使用者手中,還要在每個使用者的多個資料夾中維護訊息。這樣雖然能將訊息集中備份,但隨著使用者長期的郵件夾越來越大,到磁碟空間耗盡時,每個使用者都會受到損失。使用 POP,就能解除安裝郵件伺服器上儲存的訊息了。

    5、MIME

    MIME 代表多用途因特網郵件擴充套件標準(Multipurpose Internet Mail Extensions)。它不是郵件傳輸協議。但對傳輸內容的訊息、附件及其它的內容定義了格式。這裡有很多不同的有效文件:RFC 822、RFC 2045、RFC 2046 和 RFC 2047。作為一個JavaMail API 的使用者,您通常不必對這些格式操心。無論如何,一定存在這些格式而且程式會用到它。

    6、NNTP及其他

    因為 JavaMail API 將供應商和所有其它的東西分開了,您就能輕鬆新增額外的協議支援。Sun 保留了一張第三方供應商列表,他們利用了 Sun 不提供超出(out-of-the-box)支援範圍的協議。您會找到 NNTP(網路新聞傳輸協議)[新聞組]、S/MIME(安全多用途因特網郵件擴充套件)及其它支援。

三、JavaMail的關鍵物件:

    JavaMail對收發郵件進行了高階的抽象,形成了一些關鍵的的介面和類,它們構成了程式的基礎,下面我們分別來了解一下這些最常見的物件。

    1、Properties:屬性物件

    由於JavaMail需要和郵件伺服器進行通訊,這就要求程式提供許多諸如伺服器地址、埠、使用者名稱、密碼等資訊,JavaMail通過Properties物件封裝這些屬性西資訊。如下面的程式碼封裝了兩個屬性資訊:

?
1 2 3 Properties props = new Properties(); props.put("mail.smtp.host""smtp.sina.com.cn"); props.put("mail.smtp.auth""true");

    針對不同的的郵件協議,JavaMail規定了服務提供者必須支援一系列屬性,下表是針對SMTP協議的一些常見屬性(屬性值都以String型別進行設定,屬性型別欄僅表示屬性是如何被解析的):

屬性名 屬性型別 說明
mail.stmp.host String SMTP伺服器地址,如smtp.sina.com.cn
mail.stmp.port

int

SMTP伺服器埠號,預設為25
mail.stmp.auth boolean SMTP伺服器是否需要使用者認證,預設為false
mail.stmp.user String SMTP預設的登陸使用者名稱
mail.stmp.from String 預設的郵件傳送源地址
mail.stmp.socketFactory.class String socket工廠類類名,通過設定該屬性可以覆蓋提供者預設的實現,必須實現javax.net.SocketFactory介面
mail.stmp.socketFactory.port int 指定socket工廠類所用的埠號,如果沒有規定,則使用預設的埠號
mail.smtp.socketFactory.fallback boolean 設定為true時,當使用指定的socket類建立socket失敗後,將使用java.net.Socket建立socket,預設為true
mail.stmp.timeout int I/O連線超時時間,單位為毫秒,預設為永不超時

    其他幾個協議也有類似的一系列屬性,如POP3的mail.pop3.host、mail.pop3.port以及IMAP的mail.imap.host、mail.imap.port等。更詳細的資訊請檢視com.sun.mail.smtp、com.sun.mail.pop3和com.sun.mail.imap這三個包的Javadoc:http://java.sun.com/products/javamail/javadocs/index.html

    2、Session:會話物件

    Session是一個很容易被誤解的類,這歸咎於混淆視聽的類名。千萬不要以為這裡的Session像HttpSession一樣代表真實的互動會話,但建立Session物件時,並沒有對應的物理連線,它只不過是一對配置資訊的集合。Session的主要作用包括兩個方面:

    1)接收各種配置屬性資訊:通過Properties物件設定的屬性資訊;

    2)初始化JavaMail環境:根據JavaMail的配置檔案,初始化JavaMail環境,以便通過Session物件建立其他重要類的例項。

    所以,如果把Session更名為Configure也許更容易理解一些。JavaMail提供者在Jar包的META-INF目錄下,通過以下檔案提供了基本配置資訊,以便session能夠根據這個配置檔案載入提供者的實現類:

    javamail.providersjavamail.default.providers

    javamail.address.mapjavamail.default.address.map

    下面是Sun提供者java.mail.default.providers檔案的配置資訊(位於mail.jar中):

?
1 2 3 4 5 6 7 8 9 # JavaMail IMAP provider Sun Microsystems, Inc protocol=imap; type=store; sun.mail.imap.IMAPStore; vendor=Sun Microsystems, Inc; protocol=imaps; type=store; sun.mail.imap.IMAPSSLStore; vendor=Sun Microsystems, Inc; # JavaMail SMTP provider Sun Microsystems, Inc protocol=smtp; type=transport; sun.mail.smtp.SMTPTransport; vendor=Sun Microsystems, Inc; protocol=smtps; type=transport; class=com.sun.mail.smtp.SMTPSSLTransport; vendor=Sun Microsystems, Inc; # JavaMail POP3 provider Sun Microsystems, Inc protocol=pop3; type=store; sun.mail.pop3.POP3Store; vendor=Sun Microsystems, Inc; protocol=pop3s; type=store; sun.mail.pop3.POP3SSLStore; vendor=Sun Microsystems, Inc;

    這個配置檔案提供了以下四個方面的資訊:

       protocol:協議名稱;

       type:協議型別;

       class:對應該操作型別的實現類;

       vendor:廠商名稱。

    Session在載入配置檔案時會按照以下優先順序順序進行:

       1)首先使用<JAVA_HOME>/lib中的javamail.providers;

       2)如果1)不存在相應的配置檔案,使用類路徑下mail.jar中META-INF目錄下的javamail.providers;

       3)如果2)不存在相應的配置檔案,使用類路徑下的mail.jar中META-INF目錄下的javamail.default.providers;

       所以開發者可以在<JAVA_HOME>/lib目錄下提供配置檔案覆蓋mail.jar/META-INF目錄中廠商的配置。但是,一般情況下,我們無須這樣做。

       Session通過JavaMail配置檔案以及程式中設定的Properties物件構建一個郵件處理環境,後續的處理將在Session基礎上進行。Session擁有多個靜態工廠方法用於建立Session例項。

    • static Session getDefaultInstance(Properties props, Authenticator authenticator):當JVM中已經存在預設的Session例項中,直接返回這個例項,否則建立一個新的Session例項,並將其作為JVM中預設Session例項。這個API很詭異,我們將對它進行詳細的講解。由於這個預設Session例項可以被同一個JVM所有的程式碼訪問到,而Session中本身又可能包括密碼、使用者名稱等敏感資訊在內的所有屬性資訊,所以後續呼叫也必須傳入和第一次相同的Authenticator例項,否則將丟擲java.lang.SecurityException異常。如果第一次呼叫時Authenticator入參為null,則後續呼叫通過null的Authenticator入參或直接使用getDefaultInstance(Properties props)即可返回這個預設的Session例項。值得一提的是,雖然後續呼叫也會傳入Properties,但新屬性並不會起作用,如果希望採用新的屬性值,則可以通過getDefaultInstance(Properties props)建立一個新的Session例項達到目的。Authenticator在這裡承當了兩個功能:首先,對JVM中預設Session例項進行認證保護,後續呼叫執行getDefaultInstance(Properties props, Authenticator authenticator)方法時必須和第一次一樣;其次,在具體和郵件伺服器互動時,又作為認證的資訊;

    • static Session getDefaultInstance(Properties props):返回JVM中預設的Session例項,如果第一次建立Session未指定Authenticator入參,後續呼叫可以使用該訪問獲取Session;

    • static Session getInstance(Properties props, Authenticator authenticator):建立一個新的Session例項,它不會在JVM中被作為預設例項共享;

    • static Session getInstance(Properties props):根據相關屬性建立一個新的Session例項,未使用安全認證資訊;

    Session是JavaMail提供者配置檔案以及設定屬性資訊的“容器”,Session本身不會和郵件伺服器進行任何的通訊。所以在一般情況下,我們僅需要通過getDefaultInstance()獲取一個共享的Session例項就可以了,下面的程式碼建立了一個Session例項:

?
1 2 3 Properties props = System.getProperties(); props.setProperty("mail.transport.protocol""smtp");              … Session session = Session.getDefaultInstance(props);

    3、Transport和Store:傳輸和儲存

    郵件操作只有傳送或接收兩種處理方式,JavaMail將這兩種不同操作描述為傳輸(javax.mail.Transport)和儲存(javax.mail.Store),傳輸對應郵件的傳送,而儲存對應郵件的接收。

    Session提供了幾個用於建立Transport和Store例項的方法,在具體講解這些方法之前,我們事先了解一下Session建立Transport和Store的內部機制。我們知道提供者在javamail.providers配置檔案中為每一種支援的郵件協議定義了實現類,Session根據協議型別(stmp、pop3等)和郵件操作方式(傳輸和儲存)這兩個資訊就可以定位到一個例項類上。比如,指定stmp協議和transport型別後,Session就會使用com.sun.mail.smtp.SMTPTransport實現類建立一個Transport例項,而指定pop3協議和store型別時,則會使用com.sun.mail.pop3.POP3Store例項類建立一個Store例項。Session提供了多個過載的getTransport()和getStore()方法,這些方法將根據Session中Properties屬性設定情況進行工作,影響這兩套方法工作的屬性包括:

屬性名 說明
mail.transport.protocol 預設的郵件傳輸協議,例如,smtp
mail.store.protocol 預設的儲存郵件協議,例如:pop3
mail.host 預設的郵件服務地址,例如:192.168.67.1
mail.user 預設的登陸使用者名稱,例如:zapldy

    下面,我們再回頭來了解Session的getTransport()和getStore()的過載方法。

    • Transport getTransport():當Session例項設定了mail.transport.protocol屬性時,該方法返回對應的Transport例項,否則丟擲javax.mail.NoSuchProviderException。

    • Transport getTransport(String protocol):如果Session沒有設定mail.transport.protocol屬性,可以通過該方法返回指定型別的Transport,如transport = session.getTransport(“smtp”)。

    如果Session中未包含Authenticator,以上兩方法建立的Transport例項和郵件伺服器互動時必須顯示提供使用者名稱/密碼的認證資訊。如果Authenticator非空,則可以在和郵件伺服器互動時被作為認證資訊使用。除了以上兩種提供認證資訊的方式外,Session還可以使用以下的方法為Transport提供認證資訊。

    • Transport getTransport(URLName url):使用者可以通過URLName入參指定郵件協議、郵件伺服器、埠、使用者名稱和密碼資訊,請看下面的程式碼:

?
1 2 3 URLName urln = new URLName(“smtp”, “smtp.sina.com.cn”, 25null “masterspring2”, “spring”); Transport transport = session.getTransport(urln);

    這裡,指定了郵件協議為smtp,郵件伺服器是smtp.sina.com.cn,埠為25,使用者名稱/密碼為masterspring2/spring。

    訊息傳送的最後一部分是使用  Transport 類。這個類用協議指定的語言傳送訊息(通常是 SMTP)。它是抽象類,它的工作方式與 Session 有些類似。僅呼叫靜態 send() 方法,就能使用類的 預設 版本:

?
1 Transport.send(message);

    或者,您也可以從針對您的協議的會話中獲得一個特定的例項,傳遞使用者名稱和密碼(如果不必要就不傳),傳送訊息,然後關閉連線。

?
1 2 3 4 5 message.saveChanges(); // implicit with send() Transport transport = session.getTransport("smtp"); transport.connect(host, username, password); transport.sendMessage(message, message.getAllRecipients()); transport.close();

    後面這種方法在您要傳送多條訊息時最好,因為它能保持郵件伺服器在訊息間的活動狀態。基本 send() 機制為每個方法的呼叫設定與伺服器獨立的連線。

    注意:要觀察傳到郵件伺服器上的郵件命令,請用 session.setDebug(true) 設定除錯標誌。

    用 Session 獲取訊息與傳送訊息開始很相似。但是,在 session 得到後,很可能使用使用者名稱和密碼或使用 Authenticator 連線到一個 Store。類似於 Transport ,您告知 Store 使用什麼協議:

?
1 2 3 // Store store = session.getStore("imap"); Store store = session.getStore("pop3"); store.connect(host, username, password);

    連線到 Store 之後,接下來,您就可以獲取一個 Folder,您必需先開啟它,然後才能讀裡面的訊息。

?
1 2 3 Folder folder = store.getFolder("INBOX"); folder.open(Folder.READ_ONLY); Message message[] = folder.getMessages();

    POP3 唯一可以用的資料夾是 INBOX。如果使用 IMAP,還可以用其它資料夾。

    注意:Sun 的供應商有意變得聰明。雖然 Message message[] = folder.getMessages(); 看上去是個很慢的操作,它從伺服器上讀取每一條訊息,但僅在你實際需要訊息的一部分時,訊息的內容才會被檢索。

    一旦有了要讀的 Message,您可以用 getContent() 來獲取其內容,或者用 writeTo() 將內容寫入流。getContent() 方法只能得到訊息內容,而 writeTo() 的輸出卻包含訊息頭。

?
1 System.out.println(((MimeMessage)message).getContent());

    同樣的,你還可以通過相應的API獲取郵件的發信人、收信人、標題等內容:

?
1 2 3 4 5 6 7 String from = InternetAddress.toString(msg.getFrom()); String replyTo = InternetAddress.toString(msg.getReplyTo()); String to = InternetAddress.toString(msg.getRecipients(Message.RecipientType.TO)); String subject = msg.getSubject(); Date sentDate = msg.getSentDate(); Date receDate = msg.getReceivedDate(); Enumeration headers = msg.getAllHeaders();

    注意:此種方式獲取到的from、to、subject以及附件名字如果包含非英文字元,則可能為亂碼,因此需要進行轉碼後才能正確顯示,詳細情況請參見4. 8亂碼處理章節。

    一旦讀完郵件,要關閉與 folder 和 store 的連線。

?
1 2 folder.close(aBoolean); store.close();

    傳遞給 folder 的 close() 方法的 boolean 表示是否清除已刪除的訊息從而更新 folder。

    4、Message:訊息物件

    一旦獲得 Session 物件,就可以繼續建立要傳送的訊息。這由 Message 類來完成。因為 Message 是個抽象類,您必需用一個子類,多數情況下為 javax.mail.internet.MimeMessage。MimeMessage 是個能理解 MIME 型別和頭的電子郵件訊息,正如不同 RFC 中所定義的。雖然在某些頭部域非 ASCII 字元也能被譯碼,但 Message 頭只能被限制為用 US-ASCII 字元。

    要建立一個 Message,請將 Session 物件傳遞給 MimeMessage 構造器:

?
1 MimeMessage message = new MimeMessage(session);

    注意:還存在其它構造器,如用按 RFC822 格式的輸入流來建立訊息。

     一旦獲得訊息,您就可以設定各個部分,因為 Message 實現 Part 介面(且 MimeMessage 實現 MimePart )。設定內容的基本機制是 setContent() 方法,同時使用引數,分別代表內容和 mime 型別:

?
1 message.setContent("Hello""text/plain");

     但如果,您知道您在使用 MimeMessage,而且訊息是純文字格式,您就可以用 setText() 方法,它只需要代表實際內容的引數,( MIME 型別預設為 text/plain):

?
1 message.setText("Hello");

    後一種格式是設定純文字訊息內容的首選機制。至於傳送其它型別的訊息,如 HTML 檔案格式的訊息,我們首選前者。

    用 setSubject() 方法設定 subject(主題):

?
1 message.setSubject("First");

    下面的程式碼演示了建立一個簡單郵件資訊的過程:

?
1 2 3 4 Message msg = new MimeMessage(session); msg.setSubject("Test Title"); msg.setText("How are you!"); msg.setSentDate(new Date());

    5、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");

     需要為訊息的 from 域和 to 域建立地址物件。除非郵件伺服器阻止,沒什麼能阻止你傳送一段看上去是來自任何人的訊息。

    一旦建立了 address(地址),將它們與訊息連線的方法有兩種。如果要識別發件人,您可以用 setFrom() 和 setReplyTo() 方法。

?
1 message.setFrom(address)

    需要訊息顯示多個 from 地址,可以使用 addFrom() 方法:

?
1 2 Address address[] = ...; message.addFrom(address);

    若要識別訊息 recipient(收件人),您可以使用 addRecipient() 方法。除 address(地址)外,這一方法還請求一個 Message.RecipientType。