JavaMail学习6 发送邮件
1.JavaMail API中提供了一些專門的類來實現郵件發送功能,JavaMail應用程序開發人員只需要使用JavaMailAPI中的少數幾個類就可以完成郵件的發送,sun公司也提供了一個用于查詢DNS信息的JNDI服務程序,我們只要通過使用JNDIapi調用這個DNS查詢的JNDI服務器程序,就可以或得某個域中的郵件服務器。
?
2.郵件發送API的體系結構
JavaMail API中定義了一個java.mail.Transport類,它專門用來執行郵件發送任務,這個類的實例對象封裝了某種郵件發送協議的底層實施細節,應用程序調用這個類中的方法就可以把Message對象中封裝的郵件數據對象發送指定的SMTP服務器,使用JavaMail發送郵件時涉及到的API之間的工作關系如下:
個各類的功能及相互關系如下:
1.從Session對象中獲得實現了某種郵件發送協議的Transport對象;
2.使用Session對象創建Message對象,并調用Message對象的方法封裝郵件數據。
3.連接指定的SMTP服務器,調用Transport對象中的郵件發送方法發送Message對象中封裝的郵件數據
?
3.Session類
mail.jar包中的javax.mail.Session類用于定義整個JavaMail應用程序所需的環境信息,以及收集客戶端與郵件服務器建立網絡連接的會話信息,如郵件服務器的主機名,端口號,采用的郵件發送和接收協議等。Session對象根據這些信息構建用于郵件收發的Transport和Store對象,以及為客戶端創建Message對象時提供信息支持,下面是Session類中定義的常用方法:
?3.1 getInstane與getDefaultInstance方法:
getInstane與getDefaultInstance方法是Session類中的靜態方法,它們都可用于獲得Session類的實例對象,由于Session類的構造函數是私有的,所以,應用程序必須調用getInstane或getDefaultInstance靜態方法獲得Session實例對象,getInstane與getDefaultInstance方法個有兩種重載形式,它們的語法定義如下:
~~public static Session getInstance(java.util.Properties props);
~~public static Session getInstance (java.util.Properties props,Authenticator authenticator);
~~public static Session getDefaultInstance(java.util.Properties props);
~~public static Session getDefaultInstance(java.util.Properties props,Authenticator authenticator);
?getInstane與getDefaultInstance方法的區別在于getDefaultInstance返回一個Session對象后,先把這個Session對象安裝成一個默認的Session對象,以后每次調用getDefaultInstance方法都將返回這個默認的Session對象,而getInstane方法則是每次調用后都將返回一個新的Session對象,兩個方法都將接收一個Properties對象作為參數,
Properties對象中保存了實例化Session對象所需的應用程序環境信息,以及客戶端與郵件服務器建立連接所必須的會話信息,這些信息被?稱為JavaMail屬性,他們的屬性名作為Properties對象的關鍵字進行保存。下面介紹了一些常用JavaMail的屬性:
mail.smtp.host:指定連接的服務器主機名。
mail.transport.protocol:指定采用的郵件發送協議。
mail.store.protocol:指定接收的郵件協議
mail.smtp.auth:指定客戶端是否向郵件服務器提交認證
第二個方法除了接收一個Properties對象作為參數外,還接收一個Authenticator對象作為參數,Authenticator主要用于提供用戶認證信息,調用第二個方法創建Session對象時,將把作為第二個參數傳入的Authenticator對象注冊到該Session對象中,以后使用這個Session對象的JavaMail客戶端程序要向郵件服務器提交認證信息時,將調用該Session對象中注冊的Authenticator對象,從中獲得用戶認證信息后傳遞給郵件服務器
?3.2 getTransport方法:
getTransport方法用于返回實現了某種具體郵件服務器協議的Transport對象,Transport對象對象可以完成底層的郵件發送細節,getTransport方法有多種重載形式,其中常用的兩個語法定義如下:
~~public Transport getTransport();
~~public Transport getTransport(java.lang.String propocol);
第一個方法返回的Transport對象,將根據Session對象中的mail.transport.protocol屬性指定郵件協議進行創建,第二個方法返回的Transport對象,將根據參數protocol指定的郵件協議進行創建,Transport類是一個抽象類,兩個方法返回的Transport對象實際上都是實現了某種郵件傳輸協議的Transport子類的實例對象。
3.3 getStore方法:
getStore方法用于返回實現了某種具體郵件接收協議的Store對象,Store對象可以完成底層的郵件接收協議,getStore方法有多重重載形式,它們的語法定義如下:
~~public Store getStore();
~~public Store getStore(java.lang.String propocol);
?第一個方法返回的Store對象將根據mail.transport.protocol屬性指定的郵件協議進行創建,第二個方法返回的Store對象,將根據參數propocol指定的郵件協議進行創建,與Transport對象一樣,Store類也是一個抽象類,這兩個方法返回的Store對象實際上都是實現了某種郵件服務器協議的Store對象。
3.4 setDebug方法:
setDebug方法用于打開JavaMail API的調式功能,它們的語法定義如下:
~~public void setDebug(boolean debug);
當調用setDebug方法并將其參數設置為true時,JavaMail API將把其運行過程和郵件服務器的交互命令信息輸入到運行窗口,這個功能對JavaMail的調試功能非常有用。
?4.Transport類:
javax.mail.Transport類繼承了java.mail.Service類,它用于連接SMTP服務器,并把包含在Message對象中的郵件數據發送到SMTP服務器。Transport類是一個抽象類,在程序中運行的實際是其具體的實現之類的實例對象,不同的實現之類實現不同的郵件發送協議,Sun公司在mail.jar包提供了一個com.sun.mail.smtp.SMTPTransport類,這個類就是實現了SMTP協議的底層細節的Transport的子類,其實我們在編寫郵件發送協議的時候通常并不需要這個Transport類的子類名稱,調用Session類中的getTransport方法就可以獲得實現了某種傳輸協議的Transport子類的實例對象,例如,將mail.transport.protocol屬性設置為SMTP時,getTransport方法將創建并返回SMTPTransport類的實例對象,下面是Transport類的常用方法:
4.1? connect方法:
connect方法用于建立與郵件服務器的連接,它有三中重載形式,只要語法定義如下:
~~public void connect();
~~public void connect(String host,String user,String password);
~~public void connect(String host,int port,String user,String password);
這些方法實際上都是從java.mail.Service類中繼承的,第一個方法用于保存在Session對象中的與網絡相關的JavaMail屬性連接郵件服務器,第三個方法使用指定的郵件主機名,用戶名,和密碼連接郵件服務器,連接的端口號選擇郵件的默認端口號
?4.2 sendMessage方法:
sendMessage方法用于向指定的郵件地址發送郵件,它的語法定義如下:
~~public abstract void sendMessage(Message msg , Address[] address);
?參數msg指定代表郵件內容的Message對象,參數address指定了一個代表郵件地址的Address類型的數組,這個數組中可以指定一個或多個發件人的地址,因此sendMessage可以用來向多個郵件地址同時發送一封相同的郵件,Transport對象與郵件服務器建立連接后,可以在同一個連接上多次調用sendMessage方法,這樣就可以在一個連接上向郵件服務器發送多次郵件。
sendMessage是一個非靜態方法,它必須得到Transport實例對象后才可以調用,sendMessage方法在發送郵件前不會自動調用Message.saveChanges()方法,JavaMail必須在調用這個方法前,調用代表被發送郵件的Message對象的saveChanges()方法。
4.3 close()方法:
close()方法用于斷開與郵件服務器的連接,它的語法定義如下:
~~public void close();
close()方法也是從java.mail.Service類中繼承的
4.4 send方法:
?send方法提供了一種發送郵件的簡單方式,它是Transport類中的靜態方法,有兩種重載方式,它們的語法定義如下:
~~public static void send(Message msg);
~~public static void send(Message msg,Address[] address);
?應用程序可以直接調用Transport.send()方法發送郵件,send()方法執行郵件發送任務時,它首先從參數Message對象中獲得Session對象(創建MimeMessage對象時為其構造方法傳入Session對象)
?,然后調用Session.getTransport方法獲得用于發送郵件的Transport實例對象,接著在使用保存在Session對象中的與網絡連接相關的JavaMail屬性,調用Transport對象的connect方法連接方法連接郵件服務器,然后調用Transport對象的sendMesssage方法發送郵件,最后調用close方法斷開與郵件服務器的連接。可見,send方法內部一次調用了getTransport,connect,sendMessage,和close方法,它可以作為發送郵件的一種簡單方法。
由于第一個send方法中沒有指定收件人,所以,它將調用傳入的Message參數對象的getAllRecipients()方法,從中獲得郵件消息內定義的所有收件人,然后把郵件發送給這些收件人;第二個send方法把郵件發送給參數address中指定的郵件列表,而不會考慮郵件消息內定義的收件人地址。
send方法是一個靜態方法,它可以直接通過Transport類進行調用,send方法在發送郵件前都會調用Message.saveChanges()方法將保存在Message對象中的數據生成MIME郵件消息內容,send方法的缺點是每調用一次,都會與郵件服務器建立建立和斷開連接即再一次網絡連接上只發送一封郵件,如果要向同一個郵件服務器連續發送多封郵件,發送每封郵件時都要與郵件服務器建立和斷開網絡連接。
?4.5 郵件發送程序的編程實例:
使用JavaMail API發送郵件只需要執行如下3步:
1.創建包含郵件服務器的網絡連接信息的Session對象
2.創建代表郵件內容的Message對象
3.從Session對象中獲得Transport對象,并調用它的方法發送Message對象
?
下面來編寫一個客戶端郵件發送程序:
?
package com.jt.mail;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
import javax.mail.internet.MimeMultipart;
/**
?* @author jt
?*2016-1-19 下午9:04:41
?*郵件發送程序
?*/
public class HtmlMessageSender {
?//定義建立連接的信息
?String protocol="smtp";
?//String server="smtp.sina.com";
?String from="郵件地址";
?String to="郵件地址";
?String subject="Mail test";
?String body="<a href='http://www.baidu.com'>歡迎大家訪問我的網站</a><br/>" +
???"<img src='E:\\我的照片\\桂林\\aff\\jt.jpg'>";
?
?
?
?
?/**
? * @param args
? * @throws MessagingException
? */
?public static void main(String[] args) throws MessagingException {
??String server="smtp.sina.com";
??String user="郵箱用戶名";
??String password="密碼";
??
??HtmlMessageSender htmlSender=new HtmlMessageSender();
??Session session=htmlSender.createSession();
??MimeMessage message=htmlSender.createMimeMessage(session);
??
??//獲得Transport對象,并連接郵件服務器發送郵件
??Transport trnsport=session.getTransport();
??trnsport.connect(server,user,password);
??trnsport.sendMessage(message, message.getRecipients(Message.RecipientType.TO));
??trnsport.close();
?}
?//創建Session對象
?public Session createSession(){
??Properties props=new Properties();
??props.setProperty("mail.transport.protocol", protocol);
??//props.setProperty("mail.smtp.host", server);
??/**
?? * 必須將mail.smtp.auth的屬性設置為true,SMTPTransport對象才會
?? * 向SMTP服務器提交用戶認證信息,這個信息可以從JavaMail的javadocs
?? * 文檔中的com.sum.mail.smtp包的幫助頁面中可以查到
?? */
??props.setProperty("mail.smtp.auth", "true");
??Session session=Session.getDefaultInstance(props);
??session.setDebug(true);
??return session;
?}
?
?//創建郵件的Mimessage對象
?public MimeMessage createMimeMessage(Session session) throws MessagingException{
??MimeMessage msg=new MimeMessage(session);
??//設置郵件頭部的發件人信息
??msg.setFrom(new InternetAddress(from));
??//設置郵件頭部的收件人信息
??msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
??//設置郵件的主題
??msg.setSubject(subject);
??
??//創建MIME消息體對象,消息體中的內容是關聯的
??MimeMultipart multipart=new MimeMultipart("related");
??//創建消息體的body部分
??MimeBodyPart htmlBody=new MimeBodyPart();
??htmlBody.setContent(body,"text/html;charset=gb2312");
??multipart.addBodyPart(htmlBody);
??
??//創建圖片附件信息
??MimeBodyPart gitBody=new MimeBodyPart();
??FileDataSource ds=new FileDataSource("E:\\我的照片\\桂林\\aff\\1.jpg");
??gitBody.setDataHandler(new DataHandler(ds));
??gitBody.setContentID("1.jpg");
??multipart.addBodyPart(gitBody);
??
??msg.setContent(multipart);
??msg.saveChanges();
??return msg;
?}
?
}
?4.5 Authenticator類的應用:
在JavaMail中除了可以通過Transport.connect(host,user,password)方法在連接SMTP服務器時直接傳遞用戶認證信息,還可以借助Authenticator類來獲得用戶認證信息。
?4.5.1 Authenticator類:
Authenticator類用于代表一個可以對外提供用戶認證信息的對象,它提供的用戶認證信息封裝在一個PasswordAuthenticator類型的對象中,調用getInstance(java.util.Properties props,Authenticator authenticator)方法創建Session對象時,將把第二個參數傳入的Authenticator對象注冊到該Session對象中,以后,使用這個Session對象的JavaMail客戶端程序要向郵件服務器提交認證信息時,將調用Session對象中注冊的Authenticator對象,從中獲得用戶認證信息后傳遞給郵件服務器。
Authenticator類最常用的一個方法定義如下:
~~protected PasswordAuthenticator getPasswordAuthenticator();
getPasswordAuthenticator方法用于對外返回一個PasswordAuthenticator對象,這個PasswordAuthenticator對象中封裝了用戶用戶認證信息(用戶名和密碼)。Authenticator類是一個抽象類,傳遞給getInstance方法的Authenticator對象只能是其實現之類的實例對象。Authenticator類中定義的getPasswordAuthenticator方法返回值為null,Authenticator之類必須覆蓋這個方法,實現之類在覆蓋的getPasswordAuthenticator方法中,需要將用戶認證信息封裝在一個getPasswordAuthenticator對象中并作為返回值返回。Authenticator類的實現之類通常留給JavaMail應用程序開發人員去編寫,以便最終可以由應用開發人員來決定具體如何獲取和提供用戶認證信息。Authenticator類只是提供了獲取用戶認證信息的方法,即Authenticator類本身不包含用戶認證消息,它只是提供了創建用戶認證信息的方法。這就好比你要吃面包,我不是直接給你面包,而是給你安排一位會做面包的師傅,按照這種方式設計的程序將具有低耦合和高內聚的靈活性,例如,如果你想吃歐洲面包,那么,我給你安排一位歐洲面包的面包師,如果你想吃亞洲面包,我則給你安排一位亞洲面包師。關于如何制作亞洲面包和歐洲面包的過程,則封裝在面包師的大腦中和由面包師直接去完成,你我都不用去考慮。
擴展:(上面提到了抽象類,那么我們就來復習一下抽象和接口的區別:
面向對象設計的重點在抽象,那么Java接口和Java抽象類就有他必然的存在性了。Java接口(interface)和Java抽象類(abstract class)代表的就是抽象類型,就是我們需要提出的抽象層的具體表現形式。OOP面向對象編程就是,如果要提高程序的復用性,增加程序的可維護性,可擴展性,就是面向接口的編程,面向抽象的編程,正確的使用接口,抽象類這些有用的抽象類型作為你結構層次的頂層。
區別:java接口和java抽象類的最大一個區別就是Java抽象類可以提供某些方法的部分實現,而Java接口不可以(接口中只能定義方法,不能寫具體的實現,而abstract calss則是可以只定義方法,又可以有具體的實現方法,這也是Java抽象類的優點,這個優點非常有用,如果向一個抽象類里加入一個新的具體的方法時,那么它所有的之類都一下子都得到了這個新方法,而java接口做不到這一點,如果想一個java接口加入一個新方法,所有實現這個接口的之類就必須實現這個方法,否則無法編譯通過,這顯然是java接口的缺點。一個抽象類的實現只能由這個抽象類的之類給出,也就是說,這個實現處在抽象類所定義出的繼承的等級結構中,而由于java語言的單繼承性,所以抽象類作為類型定義的工具效率就大打折扣了,這一點java接口的優勢就體現出來了,任何一個實現java接口的所有方法的之類都可以具有這個接口類型,而一個類可以任意實現多個接口,從而這個類就具有了多種類型,(使用抽象類,那么繼承這個抽象類的之類的類型就比較單一,因為之類只能是單繼承抽象類,而之類能同時實現多個接口,所以類型比較多,java接口和java抽象類都可以定義對象,但是只能用他們的具體實現類來進行實例化,可以總結得出java接口是混合類型的理想工具,混合類型表名一個類不僅僅具有某個主類型的行為,而且具有其他的次要行為)
結合上面java接口和java抽象類的各自有點,經典的設計模式就出來了:聲明類型的工作仍然有java接口來做,但同時給出一個java抽象類且實現了這個接口,而其他屬于這個抽象類型的具體實現類可以選擇實現這個java接口,也可以選擇繼承這個抽象類,也就是說在層次結構中,java接口在最上面,下來就是java抽象類,這下兩個的優點都發揮最大極致了,這個模式就是缺省適配模式,在java語言 API中運用了這種模式,而且全部遵循一定的命名規范:Abstarct+接口名(A extends AbstractB implements interfaceC
那么A可以選擇實現(@Override)抽象類B中的方法,也可以選擇不實現)
java接口和java抽象類的存在就是為了具體類的實現和繼承,如果你準備寫一個類去繼承另一個類的話,那么你的程序設計就有很大問題,java抽象類就是為了繼承而存在的,它的抽象方法就是為了強制之類必須去實現的,使用java抽象類和java接口進行變量的類型聲明,參數的類型聲明,方法的返還類型說明,以及數據類型的轉換,而不要用java具體的類進行變量的聲明,參數的類型聲明,方法的返還說明,數據類型的轉換等。
)
4.5.2? PasswordAuthentication類:
PasswordAuthentication類用于封裝用戶認證信息(用戶名和密碼),其中定義了一些如下的方法:
~~public PasswordAuthentication(String username ,String password)唯一的公有構造方法,根據指定的用戶名和密碼創建PasswordAuthentication實例對象
~~public String getUserName();getUserName()用于返回PasswordAuthentication對象中保存的用戶名。
~~public String getPassword();getPassword方法用于返回PasswordAuthentication對象中保存的密碼。
?
?4.5.3 Authenticator類的編程實例:
如果調用Transport類中的無參數connect方法連接郵件服務器,或則調用Transport.send靜態方法直接發送郵件,當郵件服務器需要認證信息時,這兩個方法都不能直接向郵件服務器提供用戶認證信息時,在這種情況下,就需要在創建Session對象時向其中注冊一個Authenticator之類的實例對象,以便connect方法和send方法從中獲得用戶認證信息后傳遞給郵件服務器。
如果應用程序希望通過Authenticator類的方式向郵件服務器提交認證信息,可以按照以下步驟和思路進行編寫:
1.編寫抽象類Authenticator類的實現之類,在之類中覆蓋父類的getPasswordAuthenticator方法,,并返回用戶封裝的用戶名和密碼的PasswordAuthentication對象
2.調用Session.getInstance(Properties props,Authenticator authenticator)方法獲得Session類的實例對象,并把Authenticator對象注冊到Session對象中。
3.使用Session對象創建代表郵件消息內容的Message對象。
4.調用Transport.send靜態方法發送Message對象中的郵件消息內容。send方法將從Message對象中獲得Session對象的引用,然后調用該Session對象中注冊的Authenticator對象,從中獲得用戶的認證信息之后傳遞給郵件服務器。在這里,也可以不用調用Transport.send靜態方法直接發送郵件,而是先調用Session對象的getTransport方法獲得一個Transport實例對象,接著調用Transport對象的sendMessage方法發送郵件,最后調用Transport對象的close方法關閉與郵件服務器的連接,Transport對象的無參connect方法也調用Session對象中注冊的Authenticator對象,從中獲得用戶認證消息并傳遞給郵件服務器。
?
下面是Authenicator的編程實例:
package com.jt.mail;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
/**
?* @author jt
?*2016-1-22 下午8:52:04
?*編寫MyAuthenticator實現Authenticator類
?*/
public class MyAuthenticator extends Authenticator{
?
?String username=null;
?String password=null;
?
?//通過MyAuthenticator類的構造方法接收外部傳遞的用戶信息
?public MyAuthenticator(String username ,String password){
??this.username=username;
??this.password=password;
?}
?//覆蓋父類的getPasswordAuthenticator方法
?@Override
?protected PasswordAuthentication getPasswordAuthentication() {
??//使用外部傳入的用戶名和密碼創建PasswordAuthentication對象實例
??return new PasswordAuthentication(username,password);
?}
}
?
package com.jt.mail;
import java.util.Date;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
/**
?* @author jt
?*2016-1-22 下午8:59:12
?*測試Authenticator類
?*/
public class AuthenticatorDemo {
?/**
? * @param args
? * @throws MessagingException
? * @throws AddressException
? */
?public static void main(String[] args) throws AddressException, MessagingException {
??String smtpServe="smtp.sina.com";
??String protocol="smtp";
??String username="jiangtao7913";
??String password="13714834509yi";
??String from="jiangtao7913@sina.com";
??String to="651101060@qq.com";
??String subject="Authenticator Demo";
??String body="authenticator Demo";
??
??//創建Session對象
??Properties props=new Properties();
??props.setProperty("mail.host", smtpServe);
??props.setProperty("mail.transport.protocol", protocol);
??props.setProperty("mail.smtp.auth", "true");
??
??MyAuthenticator myAutnenticator=new MyAuthenticator(username,password);
??Session session=Session.getDefaultInstance(props,myAutnenticator);
??//創建代表郵件的MimeMessage對象
??MimeMessage msg=new MimeMessage(session);
??msg.setFrom(new InternetAddress(from));
??msg.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
??msg.setSentDate(new Date());
??msg.setSubject(subject);
??msg.setText(body);
??//保存并生成郵件內容
??msg.saveChanges();
??
??/**
?? * 由于Session對象中注冊了Authenticator
?? * 可以從Authenticator對象中獲得用戶認證信息,
?? * 所以這里直接調用Transport.send類的靜態方法發送郵件
?? */
??Transport.send(msg);
??/**
?? * 也可以使用下面的代碼先創建Transport對象,
?? * 在使用無參的connect方法連接郵件服務器和發送郵件
?? * Transport transport=session.getTransport();
?? *?transport.sendMessage(msg,
?? *?msg.getRecipients(Message.RecipientType.TO));
?? *?transport.close();
?? */
?}
}
擴張:在郵件處理系統中使用Authenicator類向郵件服務器提交認證信息時可以提高程序的可擴展性,使程序的認證方式變得更加靈活,應用程序可以通過查詢數據庫,或則彈出對話框的方式,甚至從一個加密文件中讀取用戶認證信息。當應用程序的開發者想改變程序的認證信息獲取方式時,只需要重新編寫一個Authenicator類的之類,并將這個之類的實例對象注冊到Session對象中即可,這實際上是設計模式的策略模式,主程序將獲取用戶認證信息的方式委托給了另外一個對象(策略對象來完成),當獲取用戶認證信息的方式發生改變時,不用修改主程序,只需要編寫一個新的策略對象。例如,對于以上程序,我們想要彈出一個輸入對話框來收集用戶的認證信息,只需要按上例子修改MyAuthenicator類,然后將它的實例對象注冊到Session對象中就可以了。
package com.jt.mail;
import java.util.StringTokenizer;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.swing.JOptionPane;
/**
?* @author jt
?*2016-1-22 下午9:37:49
?*策略方式實現MyAuthenicator
?*/
public class MyAuthenticator1 extends Authenticator{
?@Override
?protected PasswordAuthentication getPasswordAuthentication() {
??String username,password;
??String result=
????JOptionPane.showInputDialog("請輸入用戶名和密碼,中間用','分隔");
??StringTokenizer st=new StringTokenizer(result,",");
??username=st.nextToken();
??password=st.nextToken();
??return new PasswordAuthentication(username,password);
?}
?
}
4.6 為郵件發送程序配置代理:
?有時候一些網絡環境限定用戶的計算機只能通過代理服務器訪問Internet,代理服務器的工作機制如下:
?
?在使用代理服務器的情況下,用戶使用網絡客戶端軟件訪問Internet上的服務器時,用戶提交的請求都不會直接發送給目標主機,而是先發給代理服務器,代理服務器接收了用戶的請求之后,在向目標主機發出這一請求。代理服務器接收到目標主機返回的數據后,將數據發送給最初發出這一條請求的主機,并將這些數據保存到代理服務器的緩存中,用戶使用代理服務器的好處在于:
1.提高訪問速度
2.代理服務器可以起到防火墻的作用
3.通過代理服務器訪問一些不能直接訪問的網站。
4.具有一定的隱身效果
隨著每一種網絡應用協議的工作方式不同,它們的代理實現機制也會不同,例如HTTP協議和FTP協議的工作機制有很大區別,它們的代理實現程序也有很大區別。現在常見的代理服務器同時支持HTTP 協議和FTP協議的代理,同一個代理服務器在不同的端口號上代理不同協議的請求,HTTP代理和FTP代理分別針對HTTP請求和FTP請求的專用代理,它們可以分別正常代理HTTP請求和FTP請求,如果將其他協議的請求也交給HTTP代理或FTP代理,代理服務器則不一定完成代理的功能。提示:某些網絡應用協議與HTTP協議的工作機制非常相似,都是由客戶端主動發送請求信息,服務器被動響應數據,這種網絡應用協議使用HTTP代理服務器時也能正常工作。人們后來設計了一種通用的代理服務程序,稱之為SOCKS代理,SOCKS代理可以處理各種網絡協議的代理請求。
java虛擬機本身提供了網絡代理的方面的支持,只要配置了socksProxyHost這個java虛擬機系統屬性,這個虛擬機發出的所有Socket網絡連接就不在直接連接到目標計算機,而是連接到socksProxyHost屬性指定的代理服務器的默認代理端口1080,這就是說,一個java網絡應用程序的代碼不需要進行任何修改,只要在啟動java虛擬機時設置了socksProxyHost系統屬性,這個java網絡應用程序就會使用socksProxyHost系統屬性指定的代理服務器進行連接和通信,否則,這個網絡程序按照通常的方式與目標計算機進行連接和通信。
如果網絡環境限定我們只能通過代理服務器上網,那么我們在前面編寫的javaMail程序也必須進行正確的代理設置后才能向外成功發送郵件,為JavaMail程序配置代理不需要修改程序,只需要在啟動javaMail程序時將socksProxyHost系統屬性設置為代理服務器的IP地址即可,如果代理服務器使用的端口號不是默認的1080,那么在啟動javaMail程序還需要設置一個socksProxyPort系統屬性,讓socksProxyPort系統屬性等于代理服務器監聽的端口號。
?
4.7 SMTP服務器功能的郵件發送程序:
所謂的SMTP服務器功能的郵件發送程序,就是指郵件發送程序本身就像一臺SMTP服務器那樣直接對外發送郵件,不需要依賴其他的SMTP服務器對外發送程序,其工作過程如下:
從上圖可以看出,具有SMTP服務器功能的郵件發送程序會根據收件人的地址域名,直接連接到該域的SMTP服務器和進行郵件發送,由于眾多收件人地址通常都屬于多個不同的域,所以具有SMTP服務器功能的郵件發送程序需要與多個域的SMTP服務器進行通信,由于在程序開發和安裝時根本就無法預測眾多收件人的地址,因此,具有SMTP服務器功能的郵件發送程序可能需要與哪些SMTP服務器進行通信也是不可預知的,這就導致了它在功能與純碎的郵件客戶端發送程序有兩個重要的區別:
1.不能手工預先設置它所要連接的SMTP服務器,只能在發送郵件時根據收件人地址的域名臨時向DNS服務器查詢所要連接的SMTP服務器
2.在向其他接收郵件的SMTP服務器發送郵件時,其他的SMTP服務器不需要用戶認證信息,道理很簡單,一個SMTP服務器不可能在其他眾多的SMTP服務器上開設賬號,如果一個SMTP服務器需要其他SMTP服務器傳遞認證信息的話,那么其他SMTP服務器根本就無法與之通信。
從具有SMTP服務器功能的郵件發送程序與客戶端郵件發送程序的功能區別上可以看到,前者與后者在編程上的最大區別就是要通過程序來自動獲得收件人地址所在域的SMTP服務器和不用向其連接的SMTP服務器提交用戶認證信息,要在程序中獲得收件人地址所在域的SMTP服務器,這需要通過程序代碼去查詢DNS服務器并獲得收件所在域的MX記錄,Sun公司開發了一個用于查詢DNS信息的JNDI服務程序,我們只要通過使用JNDI API調用這個用于DNS查詢的JNDI服務程序,就可以獲得某個域中的所有DNS信息,在編寫JNDI API程序之前,我們必須對JNDI有個基本的認識。
4.7.2? JNDI的基本應用:
JNDI是JAVA Naming and Directory interface(java命名和目錄接口)的縮寫,它是應用程序提供命名和目錄訪問服務的API(Application Programming Interface 應用程序編程接口)
1.命名的概念和應用:
JNDI中的命名,就是將java對象以某個名稱的形式綁定到一個容器環境中,然后調用容器環境(Context)的查找(lookup)方法又可以查找出某個名稱所綁定的java對象。
也許會有點奇怪,自己創建java對象,為什么又把它查詢出來?
在真實的項目應用中,通常由系統程序或框架程序先將資源對象綁定JNDI環境中,以后在該系統或框架中運行的模塊程序就可以從JNDI環境中查找這些資源對象了。例如,Tomcat服務器在啟動時可以創建一個連接到某種數據庫系統的數據源對象(DataSource),并將該數據源(DataSource)對象綁定到JNDI環境中,以后在這個Tomcat環境中運行的servlet或JSP程序就可以直接JNDI環境中查詢出這個數據源對象(DataSource)進行使用,而不用關心數據源對象是如何創建出來的,這種方式大大增加了系統的可維護性,當數據庫系統的連接參數發生改變時,這只是Tomcat系統管理人員一個人要關心的事情,而與所有的應用程序開發人員無關。
容器環境(Context)本身就是一個java對象,它可以通過一個名稱綁定到另外一個容器環境(Context)中,將一個Context對象綁定到另外一個Context對象中,這就形成了一個父子級聯的關系,多個Context對象最終可以級聯成一個樹狀結構,樹中的每個Context對象中都可以綁定若干個Java對象,如下圖所示:
?
?
?
圖中的每個方框分別代表一個Context對象,他們綁定的名稱分別為a,b,c,d,e,b和c是a的子Context,d是b的子Context,e又是d的Context,圖中方框內的每個橢圓代表一個java對象,他們也都有綁定一個名稱,這些名稱分別為dog,pig,sheep等。在同一個Context內不能綁定兩個相同名稱的java對象,在不同的Context中可以出現同名的綁定對象,可見,Context樹的級聯結構與文件系統中的目錄結構非常類似,Context與其中綁定的java對象的關系也非常類似于文件系統中的目錄與文件的關系。
從上圖可以看出,要想得到Context樹中的java對象,必須先得到其所在的Context對象,只要得到Context對象,就可以調用它的查詢(lookup)方法來獲得綁定對象。另外,調用某個Context對象的lookup方法也可以獲得Context樹中的任意一個Context對象,這只需要在lookup方法中指定相應的Context路徑即可,在JNDI不存在“根”Context的概念,也就是說執行JNDI操作不是從一個“根”Context對象開始,而是可以從Context樹中的任意一個Context開始,無論如何,程序必須獲得一個作為操作入口的Context對象后才能執行各種JNDI命名操作。,為此,JNDI API中提供了一個InitialContext類創建用作JNDI命名操作的入口Context對象,Context是一個接口,Context對象實際上是Context某個實現類的實例對象,選擇這個具體的Context實現類來創建其實例對象的過程由一Context工廠類來實現,這個工廠類的類名可以通過JNDI的環境屬性java.naming.factory.initial指定,也可以根據Context的操作方法的url參數的Scheme來選擇。
2?.? 目錄的概念與應用:
JNDI的目錄(Directory)與文件系統中的目錄在概念上有很大不同,JNDI中的目錄(Directory)是指將一個對象的所有屬性信息保存到一個容器環境中,JNDI的目錄原理JNDI的命名原理非常相似,主要的區別在于目錄容器環境中保存的對象的屬性信息,而不是對象本身。所以,目錄提供的是對屬性的各種操作,事實上,JNDI的目錄與命名往往是結合一起使用,JNDI API中提供的代表目錄容器環境的類為DirContext,DirContext是Context的子類,顯然它除了能完成目錄相關的操作外,也能完成所有命名操作,DirContext是對Context的擴展,它在Context的基礎上增加了對目錄屬性的操作功能,可以在其中綁定綁定對象的屬性信息和查找對象的屬性信息,JNDI中的目錄(Directory)
的結構示意圖如下所示:
上圖中的每個最外層的方框分別代表一個DirContext對象,他們綁定的名稱分別為a,b,b是a的子DirContext對象,上圖中的各個最外層的方框內的小橢圓分別代表一個java對象,各個里層的方框分別代表一個對象的屬性,從名稱為a的DirContext中的內容可以看到,一個DirContext容器環境中既可以綁定對象本身,也可以綁定對象的屬性信息,綁定的對象和綁定的對象屬性完全是兩個獨立的事物,即使他們的綁定名稱相同,他們操作也是完全獨立的,另外,一個屬性可以有多個屬性值,例如,dog對象的category屬性就是設置兩個屬性值:meat和pet,從名稱為b的DirContext中的內容可以看到,一個DirContext容器環境中也可以只綁定對象的屬性信息,而不綁定任何對象本身,與Context操作原理類似,JNDIAPI中提供了一個InitialDirContext類來創建用作JNDI命名與目錄屬性操作的入口。
?
3.用于DNS查詢的JNDI服務程序:
JNDI API是面向應用程序開發人員的編程接口,它在運行時需要調用某個具體的JNDI服務器,JNDI API與JNDI服務器之間的關系猶如JDBC與JDBC驅動程序之間的關系,從JDK1.3開始,JDK就集中了JNDI API,從JDK1.4以及更高的JDK版本來開發DNS信息查詢程序時,不需要下載和安裝JNDI API和用于DNS查詢的JNDI服務程序,Sun公司提供的用于查詢DNS 信息的JNDI 服務程序,將某個域名的DNS信息以屬性的形式綁定到代表該域名的DirContext對象上,下面是JNDI程序的例子:
package com.jt.mail;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
/**
?* @author jt
?*2016-1-23 下午6:43:28
?*使用JNDI api獲取DNS信息
?*/
public class DNSQuery {
?/**
? * @param args
? * @throws NamingException
? */
?public static void main(String[] args) throws NamingException {
??/**
?? * 第一個參數指定要查詢 的域或主機名,第二個參數指定查詢的DNS服務器
?? * 為了程序的簡單易讀性省略了嚴格的參數錯誤檢查
?? */
??String domain=args[0];
??String dnsServer=args.length<2?" ":("//"+args[1]);
??
??//通過環境屬性來指定Context的工廠類
??Hashtable ht=new Hashtable();
??ht.put(Context.INITIAL_CONTEXT_FACTORY,
????"com.sun.jndi.dns.DnsContextFactory");
??ht.put(Context.PROVIDER_URL, "dns:"+dnsServer);
??DirContext ctx=new InitialDirContext(ht);
??//分別獲取包含所有屬性和只包含MX屬性的Attributes對象
??Attributes attrAll=ctx.getAttributes(domain);
??Attributes attrMx=ctx.getAttributes(domain,new String[]{"MX"} );
??
??/**
?? * 上面的整段代碼也可以用下面這段程序代碼來替代,
?? * 下面這段程序代碼通過查詢URL中的Scheme信息來自動選擇Context的工廠類
?? */
//??DirContext ctx1=new InitialDirContext();
//??Attributes attrAll1=ctx1.getAttributes("dns:"+dnsServer+"/"+domain);
//??Attributes attrMx1=ctx1.getAttributes("dns:"+dnsServer+"/"+domain,
//????new String[]{"MX"});
??System.out.println("打印出域:"+domain+"的Attributes對象中的信息:");
??System.out.println(attrAll);
??System.out.println("-------------");
??System.out.println("*打印只檢索域*"+domain+"的MX記錄的Attributes對象:");
??System.out.println(attrMx);
??System.out.println("---------");
??System.out.println("逐一打印出Attributes對象中的各個屬性:");
??NamingEnumeration attributes=attrAll.getAll();
??while(attributes.hasMore())
??{
???System.out.println(attributes.next());
??}
??System.out.println("-----------");
??//直接調用get方法從attrMx集合檢索MX屬性
??Attribute attrMx1=attrAll.get("MX");
??System.out.println(attrMx1);
??
??System.out.println("----------");
??//獲取MX屬性的第一個值
??System.out.println("獲取MX屬性的第一個值:");
??String recordMX=(String)attrMx1.get();
??System.out.println(recordMX);
??//從MX屬性的第一個值中提取郵件服務器地址
??System.out.println("從MX屬性的第一個值中提取郵件服務器地址");
??String smtpServer=recordMX.substring(recordMX.indexOf(" ")+1);
??System.out.println(smtpServer);
?}
}
結果為:
4.編寫具有SMTP服務器功能的郵件發送程序:
package com.jt.mail;
import java.util.Date;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
/**
?* @author jt
?*2016-1-23 下午7:53:28
?*
?*/
public class SMTPSender {
?/**
? * @param args
? * @throws MessagingException
? * @throws AddressException
? * @throws NamingException
? */
?public static void main(String[] args) throws AddressException, MessagingException, NamingException {
??//下面是郵件要群發給的多個收件人地址
??String[] to={"",""};
??//創建Session對象
??Properties props=new Properties();
??//
??props.setProperty("mail.smtp.localhost", "mail.itcast.cn");
??Session session=Session.getInstance(props);
??session.setDebug(true);
??Message msg=createMessage(session);
??for(int i=0;i<to.length;i++)
??{
???sendMessage(session,msg,to[i]);
??}
?}
?public static Message createMessage(Session session) throws AddressException, MessagingException{
??String from="";
??String subject="test";
??String body="test!!";
??//創建代表郵件的MimeMessage對象,不包含收件人地址
??MimeMessage msg=new MimeMessage(session);
??msg.setFrom(new InternetAddress(from));
??msg.setSentDate(new Date());
??msg.setSubject(subject);
??msg.setText(body);
??return msg;?
?}
?
?public static void sendMessage(Session session,Message msg,String to) throws AddressException, MessagingException, NamingException{
??//設置郵件內容的收件人并生成郵件消息內容
??msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
??msg.saveChanges();
??
??//連接收件人地址所在的SMTP服務器
??Transport transport=session.getTransport();
??String domian=to.substring(to.indexOf("@")+1);
??String smtpServer=getSmtpServer(domian,null);
??transport.connect(smtpServer, null, null);
??transport.sendMessage(msg, msg.getRecipients(Message.RecipientType.TO));
??transport.close();
?}
?
?public static String getSmtpServer(String domian,String dns) throws NamingException{
??DirContext cts=new InitialDirContext();
??Attributes attrMx=null;
??if(dns!=null){
???attrMx=cts.getAttributes("dns:"+"//"+dns+"/"+domian,new String[]{"MX"});
??}else{
???attrMx=cts.getAttributes("dns:"+"/"+domian,new String[]{"MX"});
??}
??String recordeMX=(String)attrMx.get("MX").get();
??String smtpServer=recordeMX.substring(recordeMX.indexOf(" ")+1);
??return smtpServer;
??
?}
}
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的JavaMail学习6 发送邮件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 百度、谷歌高德等网络地图经纬度偏差纠正以
- 下一篇: 一位acm过来人的心得