天天看點

JavaMail介紹及相關類介紹

    JavaMail是SUN提供給開發人員在應用程式中實作“郵件發送和接收功能”的一套标準開發類庫,支援常用的郵件協定,如SMTP、POP3、IMAP。

  • SMTP:簡單郵件傳輸協定,用于發送電子郵件的傳輸協定;
  • POP3:郵局協定,用于接收電子郵件的标準協定;
  • IMAP:網際網路消息通路協定,是POP3的替代協定。

    (這三種協定都有對應SSL加密傳輸的協定,分别是SMTPS,POP3S和IMAP3)。

  • MIME:多用途網際網路郵件擴充别準,它不是郵件傳輸消息,但對傳輸内容的消息、附件及其他的内容定義了格式。    

    開發人員使用JavaMail編寫郵件程式時,無需考慮底層的通信細節(Socket),JavaMail也提供了能夠建立出各種複雜MIME格式的郵件内容的API。使用JavaMail,我們可以實作類似OutLook、FoxMail的軟體。雖然JavaMail(僅支援JDK4及以上)也是Java的API之一,但是卻沒有直接加入到JDK中,是以我們需要另行下載下傳。另外,JavaMail依賴JAF(JavaBeans Activation Framework)來處理不是純文字的郵件内容(如MIME多用途網際網路郵件擴充、URL頁面和檔案附件等内容),JAF在Java6之後已經合并到JDK中,而JDK5之前需要另外下載下傳JAF的類庫。下載下傳位址如下:

       JavaMail:http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-eeplat-419426.html#javamail-1.4.5-oth-JPR

       JavaMail spec:http://www.oracle.com/technetwork/java/javamail-1-149769.pdf

       JAF:http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-java-plat-419418.html#jaf-1.1.1-fcs-oth-JPR

       JavaMail下載下傳好後,我們來看一下其主要内容:            

        README.txt:整體介紹JavaMail,需要看一下
        docs/javadocs:The JavaMail API javadocs,需要看一下
        mail.jar:包括JavaMail API和所有service providers,大部分使用者隻需要該jar包
        lib/mailapi.jar	:隻有JavaMail API
        lib/imap.jar:The IMAP service provider
        lib/smtp.jar:The SMTP service provider
        lib/pop3.jar:The POP3 service provider
        lib/dsn.jar:multipart/report DSN message support
        demo:demo示例,簡單了解,有需要再看      

    JavaMail包含兩部分内容,一部分是JavaMail API,定義了一組平台無關、獨立于通訊協定的郵件程式架構,該部分稱為應用級接口,也就是供我們調用的部分,另一部分是service provider,該部分使用特定的協定語言來實作第一部分定義的抽象類和接口,這些協定包括:SMTP、NNTP、POP3、IMAP,如果讓JavaMail與郵件伺服器通信,就需要相應的協定支援,該部分稱為服務提供者接口,也就是JavaMail自身需要的協定支援。在使用JavaMail時,通常我們隻需将mail.jar放在classpath下使用,它包含了JavaMail API部分和SUN自己實作的service provider部分。可能也有特殊的時候,我們應用程式中需要自己實作service provider部分,那我們隻需要mailapi.jar。下面通過幾個類來簡單認識下JavaMail API:

    javax.mail.Session:上下文環境資訊,如伺服器的主機名、端口号、協定名稱等
    javax.mail.Message:郵件模型,發送郵件和接收郵件的媒介,封裝了郵件的資訊,如發件人、收件人、郵件标題、郵件内容等
    javax.mail.Transport:連接配接郵件SMTP伺服器,發送郵件
    javax.mail.Store:連接配接郵件POP3、IMAP伺服器,收取郵件      

通過這些類,最終就可以實作收發郵件,一個發送郵件的簡單示例:

public class JavaMailTest1 {
	public static void main(String[] args) throws MessagingException {
		Properties props = new Properties();
		// 開啟debug調試
		props.setProperty("mail.debug", "true");
		// 發送伺服器需要身份驗證
		props.setProperty("mail.smtp.auth", "true");
		// 設定郵件伺服器主機名
		props.setProperty("mail.host", "smtp.163.com");
		// 發送郵件協定名稱
		props.setProperty("mail.transport.protocol", "smtp");
		
		// 設定環境資訊
		Session session = Session.getInstance(props);
		
		// 建立郵件對象
		Message msg = new MimeMessage(session);
		msg.setSubject("JavaMail測試");
		// 設定郵件内容
		msg.setText("這是一封由JavaMail發送的郵件!");
		// 設定發件人
		msg.setFrom(new InternetAddress("[email protected]"));
		
		Transport transport = session.getTransport();
		// 連接配接郵件伺服器
		transport.connect("java_mail_001", "javamail");
		// 發送郵件
		transport.sendMessage(msg, new Address[] {new InternetAddress("[email protected]")});
		// 關閉連接配接
		transport.close();
	}
}      

最終運作後,郵件發送成功。由于我們開啟了debug調試,在控制台可以看到JavaMail和伺服器之間的互動資訊記錄。

       PS:文中示例以及以後的示例中所用的郵箱賬戶均為在163申請的測試賬戶,分别為java_mail_001至java_mail_004,密碼均為javamail。

        JavaMail API使用上非常靈活。它對收發郵件進行了進階的抽象,形成了一些關鍵的接口和類,它們構成了程式的基礎,下面分别介紹這些對象:

  • Properties

        屬性對象。由于JavaMail需要和郵件伺服器進行通信,這就要求程式提供許多諸如伺服器位址、端口、使用者名、密碼等資訊,JavaMail通過Properties對象封裝這些屬性資訊。例如

    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 SMTP預設的登陸使用者名
mail.stmp.from 預設的郵件發送源位址
mail.stmp.socketFactory.class socket工廠類類名,通過設定該屬性可以覆寫提供者預設的實作,必須實作javax.net.SocketFactory接口
mail.stmp.socketFactory.port 指定socket工廠類所用的端口号,如果沒有規定,則使用預設的端口号
mail.smtp.socketFactory.fallback 設定為true時,當使用指定的socket類建立socket失敗後,将使用java.net.Socket建立socket,預設為true
mail.stmp.timeout 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。

  • Session

    會話對象。千萬别以為這裡的Session向HTTPSession一樣代表真實的互動會話,建立Session對象時,并沒有對應的實體連接配接,它隻不過是一對配置資訊的集合。主要作用包括兩個方面:

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

    2)初始化JavaMail環境:根據JavaMail的配置檔案,初始化JavaMail環境,以便通過Session對象建立其他重要類的執行個體。    

    JavaMail分為API和service provider兩部分,API定義了相關接口(eg.:Transport and Store),service provider中實作了這些接口。JavaMail提供的mail.jar/smtp.jar/pop3.jar/imap.jar等jar包的META-INF目錄下,通過javamail.providers或javamail.default.provider、javamail.address.map和javamail.default.address.map檔案提供了基本的配置資訊,以便session能夠根據這個配置檔案家在提供者的實作類。javamail.default.provider檔案的配置資訊如:

# JavaMail IMAP provider Sun Microsystems, Inc
protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Sun Microsystems, Inc;
protocol=imaps; type=store; class=com.sun.mail.imap.IMAPSSLStore; vendor=Sun Microsystems, Inc;
# JavaMail SMTP provider Sun Microsystems, Inc
protocol=smtp; type=transport; class=com.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; class=com.sun.mail.pop3.POP3Store; vendor=Sun Microsystems, Inc;
protocol=pop3s; type=store; class=com.sun.mail.pop3.POP3SSLStore; vendor=Sun Microsystems, Inc;      

    每一行聲明了協定名稱、協定類型、實作類、供應商、版本等資訊,如果需要自己實作相應的協定,必須按照該格式配置好,Java Mail API中才能正确的調用到。

 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運作過程中的環境資訊,它可以建立一個單例的對象,也可以每次建立新的對象,Session沒有構造器,但擁有多個靜态工廠方法用于創造執行個體:

static Session

getDefaultInstance(Properties props)

          Get the default Session object.

static Session

getDefaultInstance(Properties props,Authenticator authenticator)

static Session

getInstance(Properties props)

          Get a new Session object.

static Session

getInstance(Properties props,Authenticator authenticator)

l         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)方法時必須和第一次一樣;其次,在具體和郵件伺服器互動時,又作為認證的資訊;

l         static Session getDefaultInstance(Properties props):傳回JVM中預設的Session執行個體,如果第一次建立Session未指定Authenticator入參,後續調用可以使用該通路擷取Session;

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

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

    getDefaultInstance得到的始終是該方法初次建立的預設的對象,而getInstance得到的始終是新的對象,Authenticator的使用後面會說到。Session是JavaMail提供者配置檔案以及設定屬性資訊的“容器”,其本身不會和郵件伺服器進行任何的通信。是以,一般情況下,我們僅需要通過getDefaultInstance擷取一個共享的Session執行個體就可以了。例如:

    Properties props = System.getProperties();
    props.setProperty("mail.transport.protocol", "smtp");              …
    Session session = Session.getDefaultInstance(props);      

Message

    消息對象。Message是郵件的載體,用于封裝郵件的所有資訊,Message是一個抽象類,已知的實作類有MimeMessage。

    MimeMessage message=new MimeMessage(session)      

     ps:Address類:位址。一旦你建立了Session和Message,并将内容填入消息後,就可以用Address确定信件位址了。和Message一樣,Address也是一個抽象類,常用InternetAddress類執行個體。

    若建立的位址隻包含電子郵件位址,隻要傳遞電子郵件位址到構造器就行了。
        Address address = new InternetAddress("[email protected]");
    若希望名字緊挨着電子郵件顯示,也可以把它傳遞給構造器:
        Address address = new InternetAddress("[email protected]", "George Bush");      

    下面來看下Message中設定郵件資訊的一些方法。

發件人

abstract  void

setFrom()

          Set the "From" attribute in this Message.

abstract  void

setFrom(Address address)

    現在大多數SMTP伺服器要求發件人與連接配接賬戶必須一緻,否則會抛出驗證失敗的異常,這樣是防止使用者僞裝成其它人的賬戶惡意發送郵件。

    如果需要消息顯示多個 from 位址,可以使用 addFrom() 方法:

                 Address address[] = ...;

                message.addFrom(address);

收件人/抄送人/暗送人

void

setRecipient(Message.RecipientType type,Address address)

          Set the recipient address.

abstract  void

setRecipients(Message.RecipientType type,Address[] addresses)

          Set the recipient addresses.

    第一個方法設定單個人,第二個方法設定多個人。

    方法中第一個參數涉及到另一個類RecipientType(預定義位址),該類是Message的靜态内部類,期内有三個常量值:

static Message.RecipientType

BCC

          The "Bcc" (blind carbon copy) recipients.

static Message.RecipientType

CC

          The "Cc" (carbon copy) recipients.

static Message.RecipientType

TO

          The "To" (primary) recipients.

       TO - 收件人;CC - 抄送人;BCC - 暗送人。

回複人

 void

setReplyTo(Address[] addresses)

          Set the addresses to which replies should be directed.

    設定收件人收到郵件後的回複位址。

标題

abstract  void

setSubject(String subject)

          Set the subject of this message.

    設定郵件标題/主題。

内容

 void

setContent(Multipart mp)

          This method sets the given Multipart object as this message's content.

 void

setContent(Object obj,String type)

          A convenience method for setting this part's content.

 void

setText(String text)

          A convenience method that sets the given String as this part's content with a MIME type of "text/plain".

    設定郵件文本内容、HTML内容、附件内容。

示例 

    如果消息是發給副總統的,同時發送一個副本(carbon copy)給總統夫人,以下做法比較恰當: 

    Address toAddress = new InternetAddress("[email protected]");
    Address ccAddress = new InternetAddress("[email protected]");
    message.addRecipient(Message.RecipientType.TO, toAddress);
    message.addRecipient(Message.RecipientType.CC, ccAddress);      

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中提供的建立Trasnsport和Store的方法如下(當Session執行個體設定了mail.transport.protocol屬性時,該方法傳回對應的Transport執行個體,否則抛出javax.mail.NoSuchProviderException。如果Session沒有設定mail.transport.protocol屬性,可以通過該方法傳回指定類型的Transport,如transport = session.getTransport(“smtp”)。):

 Store

getStore()

          Get a Store object that implements this user's desired Store protocol.

 Store

getStore(Provider provider)

          Get an instance of the store specified by Provider.

 Store

getStore(String protocol)

          Get a Store object that implements the specified protocol.

 Store

getStore(URLName url)

          Get a Store object for the given URLName.

 Transport

getTransport()

          Get a Transport object that implements this user's desired Transport protcol.

 Transport

getTransport(Address address)

          Get a Transport object that can transport a Message of the specified address type.

 Transport

getTransport(Provider provider)

          Get an instance of the transport specified in the Provider.

 Transport

getTransport(String protocol)

          Get a Transport object that implements the specified protocol.

 Transport

getTransport(URLName url)

          Get a Transport object for the given URLName.

    可以看到,重構了很多,這些方法最終都會去解析上文提到的配置檔案,找到對應配置資訊。       

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

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

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

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

消息發送:使用  Transport 類。這個類用協定指定的語言發送消息(通常是 SMTP)。它是抽象類,它的工作方式與 Session 有些類似,僅調用靜态 send() 方法,就能使用類的預設版本:

    Transport.send(message);      

    或者,您也可以從針對您的協定的會話中獲得一個特定的執行個體,傳遞使用者名和密碼(如果不必要就不傳),發送消息,然後關閉連接配接。

    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 使用什麼協定:

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

    連接配接到 Store 之後,接下來,您就可以擷取一個 Folder,您必需先打開它,然後才能讀裡面的消息。

    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() 的輸出卻包含消息頭。

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

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

    folder.close(aBoolean);
    store.close();      

    傳遞給 folder 的 close() 方法的 boolean 表示是否清除已删除的消息進而更新 folder。

示例

       下面來看一個稍複雜點的示例:

public class JavaMailTest2 {

	public static void main(String[] args) throws MessagingException {
		Properties props = new Properties();
		// 開啟debug調試
		props.setProperty("mail.debug", "true");
		// 發送伺服器需要身份驗證
		props.setProperty("mail.smtp.auth", "true");
		// 設定郵件伺服器主機名
		props.setProperty("mail.host", "smtp.163.com");
		// 發送郵件協定名稱
		props.setProperty("mail.transport.protocol", "smtp");
		
		// 設定環境資訊
		Session session = Session.getInstance(props, new Authenticator() {
			// 在session中設定賬戶資訊,Transport發送郵件時會使用
			protected PasswordAuthentication getPasswordAuthentication() {
				return new PasswordAuthentication("java_mail_001", "javamail");
			}
		});
		
		// 建立郵件對象
		Message msg = new MimeMessage(session);
		// 發件人
		msg.setFrom(new InternetAddress("[email protected]"));
		// 多個收件人
		msg.setRecipients(RecipientType.TO, InternetAddress.parse("[email protected],[email protected]"));
		// 抄送人
		msg.setRecipient(RecipientType.CC, new InternetAddress("[email protected]"));
		// 暗送人
		msg.setRecipient(RecipientType.BCC, new InternetAddress("[email protected]"));
		
		// 主題
		msg.setSubject("中文主題");
		// HTML内容
		msg.setContent("<div align=\"center\">你好啊</div>", "text/html;charset=utf-8");
		
		// 連接配接郵件伺服器、發送郵件、關閉連接配接,全幹了
		Transport.send(msg);
	}

}      
JavaMail介紹及相關類介紹