背景
近期需要實作一個郵件用戶端的項目,類似Foxmail,outlook用戶端。但項目隻做消息轉發,不需要完整實作。
郵件協定
常用的電子郵件協定有SMTP、POP3、IMAP4,它們都隸屬于TCP/IP協定簇,預設狀态下,分别通過TCP端口25、110和143建立連接配接。
SMTP即簡單郵件傳輸協定,SMTP郵件伺服器是遵循SMTP協定的發送郵件的伺服器。如QQ:smtp.qq.com、163:smtp.163.com
POP即郵局協定,可以查詢郵件,查詢是否有新郵件,可以删除郵件。POP3是POP協定的第三個版本。如QQ:pop.qq.com、163:pop.163.com。
IMAP即網際網路資訊通路協定,此協定優于POP協定,擁有POP協定的功能,克服了POP協定的缺點。這裡不做過多介紹。
MIME多用途網際網路郵件擴充類型,這個不是協定,也在這裡介紹,因為郵件消息需要遵循MIME擴充類型。具體的對照關系可以參看http://tool.oschina.net/commons
郵箱帳号的設定
在第三方用戶端登入,需要設定帳号授權,擷取授權碼,此授權碼将作為登入鑒權的密碼使用。具體設定以QQ郵箱為例:
登入QQ郵箱,點選設定,帳号界面,下拉直到出現POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服務的設定,在“IMAP/SMTP服務”選項處點選“開啟”。

Setting.png
出現了短信驗證的界面,如上操作,将一串數字發送指定的電話号碼後,點選“我已發送”。

Setting.png
開啟成功後,就可以根據自己的需求設定“收取選項”。之後“儲存設定”,到這裡我們就完成了IMAP服務的開啟,可以成功使用第三方郵件用戶端登陸了(可以看到如果你在第三方登入時忘記授權碼,你可以在這裡點選“生成授權碼”,重新發短信獲得新授權碼)。

Setting.png
Javamail簡介
JavaMail API提供了一種與平台無關和協定獨立的架構來建構郵件和消息應用程式。下載下傳位址:http://java.sun.com/products/javamail/。
解壓後将javax.mail.jar導入項目。
javamail主要的子產品:
Session對象
Session對象管理用戶端與郵件伺服器的連接配接會話
static Session getDefaultInstance(Properties props);
static Session getDefaultInstance(Properties props, Authenticator authenticator);
static Session getInstance(Properties props);
static Session getInstance(Properties props, Authenticator authenticator);
getDefaultInstance擷取預設初始對象,getInstance擷取的都是新對象。
Properties對象複用java.util.Properties,SMTP協定屬性參數設定如下:
Properties props = new Properties();
props.put("mail.debug", "true");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", "smtp.qq.com");
props.put("mail.smtp.port", "465");
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); props.put("mail.smtp.socketFactory.port", "465");
props.put("mail.smtp.socketFactory.fallback", "false");
POP3協定屬性參數設定如下:
Properties props = new Properties();
props.put("mail.debug", "true");
props.put("mail.pop3.host", "pop3.qq.com");
props.put("mail.pop3.port", "995");
props.put("mail.pop3.starttls.enable", "true");
Authenticator對象控制連接配接過程中的權限認證,設定代碼如下:
Authenticator authenticator = new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("user:XXXX", "password:XXXX");
}
user是郵箱登入帳号,password是第三方用戶端登入授權碼
Message對象
MimeMessage對象表示整封郵件,MimeBodyPart對象表示郵件的一個MIME消息,MimeMultipart對象表示一個由多個MIME消息組合而成的MIME消息。在具體組裝複雜MIME消息,可參考如下代碼:
Message message = new MimeMessage(session);
message.setSubject("subject");
message.setContent("test", "text/plain;charset=UTF-8");
Multipart multipart = new MimeMultipart();
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText("test");
multipart.addBodyPart(messageBodyPart);
message.setContent(multipart);
郵件位址設定以及附件參數的組裝
郵件位址由InternetAddress對象做轉換,郵件包含的位址類型如下:
Message.RecipientType.TO;
Message.RecipientType.Cc;
Message.RecipientType.Bcc;
設定發件人:
message.setFrom(new InternetAddress("[email protected]"));
設定收件人:
message.setRecipients(Message.RecipientType.TO,InternetAddress.parse("[email protected];[email protected]"));
message.addRecipients(Message.RecipientType.Cc,InternetAddress.parse("[email protected];[email protected]"));
message.addRecipients(Message.RecipientType.Bcc,InternetAddress.parse("[email protected];[email protected]"));
設定附件:
messageBodyPart = new MimeBodyPart();
messageBodyPart.setDataHandler(new DataHandler(new DataSource("xxxx")));
messageBodyPart.setFileName("filename");
multipart.addBodyPart(messageBodyPart);
附件對象構造依賴DataHandler對象,DataHandler對象構造依賴DataSource對象。DataSource就是通過讀取檔案二進制流生成。javax.mail.util.ByteArrayDataSource可以直接通過二進制流生成DataSource對象。
final ByteArrayDataSource dataSource = new ByteArrayDataSource(data, (type == null || "".equals(type)) ? "application/octet-stream" : type);
Transport對象
Transport對象控制郵件的發送,具體過程是:連接配接伺服器->發送郵件->關閉連接配接,代碼如下:
Transport transport = session.getTransport("smtps");
transport.connect("smtp.qq.com", 465, "[email protected]", "password:xxxxx");
transport.sendMessage(message, message.getAllRecipients());
transport.close();
Store對象
Store對象表示整個郵局,可以擷取檢視郵局的所有資訊,如檢視收件夾,需要擷取收件夾檔案目錄:
Folder emailFolder = store.getFolder("INBOX");
emailFolder.open(Folder.READ_ONLY);
//從folder中擷取這些郵件資訊并列印出來
Message[] messages = emailFolder.getMessages();
Folder 對象表示郵局的一個檔案目錄,一個Message對象就是一封郵件,拆解的過程與組裝過程相反。
當擷取整個Store對象以後,我們可以做更多的操作,如删除郵件,移動郵件等。但删除郵件與郵件帳号的設定相關,需要授權給第三方用戶端操作權限。具體設定,參看各郵件服務商的帳号設定選項。
注意
使用javamail在連接配接郵件伺服器時connect報錯:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
總結
到此,郵件收發基本功能完成。後續需要思考:
實作一個任務隊列,因為郵件發送過程是個比較耗時的過程,可以擴充實作多線程處理。
新郵件收取的通知
借此項目作為學習java的契機,如有不當之處,請指正。