一、背景
最近朋友的公司有用到這個功能,之前對這一塊也不是很熟悉,就和他一起解決出現的異常的同時,也初窺一下使用Apache Common Email元件進行郵件傳送。
二、Java傳送郵件的注意事項
1.不同的郵箱有不同的支援協議,比如有些只支援SSL協議,有些只支援TLS協議,還有些同時支援SSL和TLS協議。
2.支援不同協議的郵箱,在使用Java傳送郵件時要使用不同的方式傳送,下面我會介紹基於SSL和TLS的兩種實現方式。
三、程式碼實現
說明:本例採用Apache Common Email元件進行開發。
1.傳送郵件實體類
1 package com.hafiz.zhang.mail.entity; 2 3 import java.io.Serializable; 4 import java.util.List; 5 6 import org.apache.commons.mail.EmailAttachment; 7 8 /** 9 * @author hafiz.Zhang 10 * @Date 2016年5月23日 下午2:40:36 11 * @Description 郵件實體類 12 */ 13 public class MailEntity implements Serializable{ 14 private static final long serialVersionUID = 1589570366044890462L; 15 public static final String ENCODING = "UTF-8"; //郵件編碼 16 private String host; //伺服器地址 17 private String port; //傳送埠 18 private String sender; //發件人郵箱 19 private List<String> receiver; //收件人郵箱 20 private List<String> copier; //抄送人 21 private String senderName; //發件人暱稱 22 private String userName; //發件郵箱賬號 23 private String password; //發件郵箱密碼 24 private String subject; //郵件主題 25 private String content; //郵件內容(支援HTML) 26 private List<EmailAttachment> attachments; //郵件附件 27 public String getHost() { 28 return host; 29 } 30 public void setHost(String host) { 31 this.host = host; 32 } 33 public String getPort() { 34 return port; 35 } 36 public void setPort(String port) { 37 this.port = port; 38 } 39 public String getSender() { 40 return sender; 41 } 42 public void setSender(String sender) { 43 this.sender = sender; 44 } 45 public List<String> getReceiver() { 46 return receiver; 47 } 48 public void setReceiver(List<String> receiver) { 49 this.receiver = receiver; 50 } 51 public List<String> getCopier() { 52 return copier; 53 } 54 public void setCopier(List<String> copier) { 55 this.copier = copier; 56 } 57 public String getSenderName() { 58 return senderName; 59 } 60 public void setSenderName(String senderName) { 61 this.senderName = senderName; 62 } 63 public String getUserName() { 64 return userName; 65 } 66 public void setUserName(String userName) { 67 this.userName = userName; 68 } 69 public String getPassword() { 70 return password; 71 } 72 public void setPassword(String password) { 73 this.password = password; 74 } 75 public String getSubject() { 76 return subject; 77 } 78 public void setSubject(String subject) { 79 this.subject = subject; 80 } 81 public String getContent() { 82 return content; 83 } 84 public void setContent(String content) { 85 this.content = content; 86 } 87 public List<EmailAttachment> getAttachments() { 88 return attachments; 89 } 90 public void setAttachments(List<EmailAttachment> attachments) { 91 this.attachments = attachments; 92 } 93 @Override 94 public String toString() { 95 return "MailEntity [host=" + host + ", port=" + port + ", sender=" + sender + ", receiver=" + receiver 96 + ", copier=" + copier + ", senderName=" + senderName + ", userName=" + userName + ", password=" 97 + password + ", subject=" + subject + ", content=" + content + ", attachments=" + attachments + "]"; 98 } 99 }
注:實體類程式碼比較臃腫,建議使用lombok進行簡化,lombok的使用方式見我的另一篇部落格:Java簡化程式碼神器-Lombok
2.傳送郵件支援協議列舉類
1 package com.hafiz.zhang.mail.tools.factory; 2 3 /** 4 * @author hafiz.Zhang 5 * @Date 2016年5月25日 下午12:08:59 6 * @Description 列舉傳送郵件的可用協議 7 */ 8 public class Protocols { 9 //SSL協議 10 public static String SSL_MAIL_UTIL = "com.hafiz.zhang.mail.tools.SSLMailUtil"; 11 //TLS協議 12 public static String TLS_MAIL_UTIL = "com.hafiz.zhang.mail.tools.TLSMailUtil"; 13 }
3.傳送郵件工具介面類
1 package com.hafiz.zhang.mail.tools; 2 3 import com.hafiz.zhang.mail.entity.MailEntity; 4 5 /** 6 * @author hafiz.Zhang 7 * @Date 2016年5月23日 下午3:26:15 8 * @Description 郵件傳送介面類 9 */ 10 public interface IMailUtil { 11 /** 12 * @Author hafiz.Zhang 13 * @Description: 傳送郵件 14 * @param mail要傳送郵件的實體類 15 * @return Boolean 傳送結果 16 */ 17 public abstract Boolean send(MailEntity mail); 18 }
4.基於SSL協議傳送郵件工具實現類
1 package com.hafiz.zhang.mail.tools; 2 3 import java.util.List; 4 5 import org.apache.commons.mail.EmailAttachment; 6 import org.apache.commons.mail.EmailException; 7 import org.apache.commons.mail.HtmlEmail; 8 import org.slf4j.Logger; 9 import org.slf4j.LoggerFactory; 10 11 import com.hafiz.zhang.mail.entity.MailEntity; 12 13 /** 14 * @author hafiz.Zhang 15 * @Date 2016年5月23日 下午3:09:13 16 * @Description 通過SSL方式傳送郵件工具類 17 */ 18 public class SSLMailUtil implements IMailUtil{ 19 private static Logger log = LoggerFactory.getLogger(SSLMailUtil.class); 20 21 public Boolean send(MailEntity mail) { 22 // 建立待傳送的emil物件 23 HtmlEmail email = new HtmlEmail(); 24 try { 25 26 // 設定SMTP傳送伺服器的名字:163的如下:"smtp.163.com" 27 email.setHostName(mail.getHost()); 28 //設定埠號 29 if(null != mail.getPort()) { 30 email.setSmtpPort(Integer.parseInt(mail.getPort())); 31 } 32 // 設定郵件字元編碼集 33 email.setCharset(MailEntity.ENCODING); 34 // 設定抄送人 35 email.addCc(mail.getCopier().toArray(new String[mail.getCopier().size()])); 36 // 設定傳送人的郵箱 37 email.setFrom(mail.getSender(), mail.getSenderName()); 38 // 收件人的郵箱 39 email.addTo(mail.getReceiver().toArray(new String[mail.getReceiver().size()])); 40 // 如果需要認證資訊的話,設定認證:使用者名稱-密碼。分別為發件人在郵件伺服器上的註冊名稱和密碼 41 email.setAuthentication(mail.getUserName(), mail.getPassword()); 42 // 設定要傳送的郵件主題 43 email.setSubject(mail.getSubject()); 44 // 設定要傳送的資訊,由於使用了HtmlEmail,可以在郵件內容中使用HTML標籤 45 email.setMsg(mail.getContent()); 46 // 設定附件 47 List<EmailAttachment> attachments = mail.getAttachments(); 48 if(null != attachments && attachments.size() > 0) { 49 for(EmailAttachment attachment : attachments) { 50 email.attach(attachment); 51 } 52 } 53 // 傳送 54 email.send(); 55 56 log.info(mail.getSender() + " 傳送郵箱到 " + mail.getReceiver()); 57 58 return true; 59 } catch (EmailException e) { 60 log.error(mail.getSender() + " 傳送郵箱到 " + mail.getReceiver() + " 失敗,錯誤原因:" + e.getMessage()); 61 e.printStackTrace(); 62 } 63 return false; 64 } 65 }
5.基於TLS協議的傳送郵件實現類
1 package com.hafiz.zhang.mail.tools; 2 3 import java.util.List; 4 import java.util.Properties; 5 6 import javax.mail.Session; 7 8 import org.apache.commons.mail.DefaultAuthenticator; 9 import org.apache.commons.mail.EmailAttachment; 10 import org.apache.commons.mail.EmailException; 11 import org.apache.commons.mail.HtmlEmail; 12 import org.slf4j.Logger; 13 import org.slf4j.LoggerFactory; 14 15 import com.hafiz.zhang.mail.entity.MailEntity; 16 17 /** 18 * @author hafiz.Zhang 19 * @Date 2016年5月23日 下午3:23:15 20 * @Description 通過TLS協議傳送郵件工具類 21 */ 22 public class TLSMailUtil implements IMailUtil{ 23 private static Logger log = LoggerFactory.getLogger(TLSMailUtil.class); 24 25 public Boolean send(MailEntity mail){ 26 HtmlEmail email = new HtmlEmail(); 27 try { 28 Properties prop = new Properties(); 29 // 設定SMTP傳送伺服器的名字:163的如下:"smtp.163.com" 30 prop.setProperty("mail.smtp.host", mail.getHost()); 31 // 設定SMTP傳送伺服器的埠 32 prop.setProperty("mail.smtp.port", mail.getPort()); 33 // 設定是否需要認證 34 prop.setProperty("mail.smtp.auth", "true"); 35 // 開啟TLS加密方式 36 prop.setProperty("mail.smtp.starttls.enable", "true"); 37 // 新增信任的伺服器 38 prop.setProperty("mail.smtp.ssl.trust", mail.getHost()); 39 // 進行認證並獲取需要的session 40 DefaultAuthenticator defaultAuthenticator = 41 new DefaultAuthenticator(mail.getUserName(), mail.getPassword()); 42 Session session = Session.getInstance(prop,defaultAuthenticator); 43 email.setMailSession(session); 44 // 設定字元編碼集 45 email.setCharset(MailEntity.ENCODING); 46 // 設定傳送人的郵箱 47 email.setFrom(mail.getSender(), mail.getSenderName()); 48 // 設定收件人的郵箱 49 email.addTo(mail.getReceiver().toArray(new String[mail.getReceiver().size()])); 50 // 設定抄送人 51 email.addCc(mail.getCopier().toArray(new String[mail.getCopier().size()])); 52 // 設定要傳送的郵件主題 53 email.setSubject(mail.getSubject()); 54 // 設定要傳送的資訊,由於使用了HtmlEmail,可以在郵件內容中使用HTML標籤 55 email.setMsg(mail.getContent()); 56 // 設定附件 57 List<EmailAttachment> attachments = mail.getAttachments(); 58 if(null != attachments && attachments.size() > 0) { 59 for(EmailAttachment attachment : attachments) { 60 email.attach(attachment); 61 } 62 } 63 // 傳送 64 email.send(); 65 log.info(mail.getSender() + " 傳送郵箱到 " + mail.getReceiver()); 66 return true; 67 } catch (EmailException e) { 68 log.error(mail.getSender() + " 傳送郵箱到 " + mail.getReceiver() + " 失敗,錯誤原因:" + e.getMessage()); 69 e.printStackTrace(); 70 } 71 return false; 72 } 73 }
注意:
1.如果沒有設定開啟TLS加密方式的程式碼(上面程式碼中標紅處),則會出現Caused by: com.sun.mail.smtp.SMTPSendFailedException: 530 5.7.57 SMTP; Client was not authenticated to send anonymous mail during MAIL FROM異常,異常圖片如下:
2.如果沒有設定新增信任的主機伺服器的程式碼(上面程式碼中標紅處),則會出現:Caused by: javax.mail.MessagingException: Could not convert socket to TLS;異常,異常圖片如下:
6.獲取郵件傳送類的工廠類
1 package com.hafiz.zhang.mail.tools.factory; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 6 import com.hafiz.zhang.mail.tools.IMailUtil; 7 8 /** 9 * @author hafiz.Zhang 10 * @Date 2016年5月23日 下午3:43:15 11 * @Description 獲取郵件傳送類的工廠類 12 */ 13 public class MailUtilsFactory { 14 private static Logger logger = LoggerFactory.getLogger(MailUtilsFactory.class); 15 16 private MailUtilsFactory() { 17 super(); 18 } 19 20 public static IMailUtil getMailUtil(String name) { 21 IMailUtil mailUtil = null; 22 try { 23 mailUtil = (IMailUtil)Class.forName(name).newInstance(); 24 } catch (Exception e) { 25 logger.error("獲取郵件傳送類失敗,失敗原因:" + e.getMessage()); 26 e.printStackTrace(); 27 } 28 return mailUtil; 29 } 30 }
7.測試類
1 package com.hafiz.zhang.mail.test; 2 3 import java.net.MalformedURLException; 4 import java.net.URL; 5 import java.util.ArrayList; 6 import java.util.List; 7 8 import org.apache.commons.mail.Email; 9 import org.apache.commons.mail.EmailAttachment; 10 11 import com.hafiz.zhang.mail.entity.MailEntity; 12 import com.hafiz.zhang.mail.tools.IMailUtil; 13 import com.hafiz.zhang.mail.tools.factory.MailUtilsFactory; 14 import com.hafiz.zhang.mail.tools.factory.Protocols; 15 16 /** 17 * @author hafiz.Zhang 18 * @Date 2016年5月23日 下午3:39:52 19 * @Description 兩種方式傳送郵件的測試類 20 */ 21 public class TestMailSender { 22 public static void main(String[] args) throws MalformedURLException { 23 MailEntity mail = new MailEntity(); 24 mail.setHost("smtp.163.com"); 25 mail.setSender("xxx@163.com"); 26 mail.setPort("25"); 27 mail.setUserName("xxx@163.com"); 28 mail.setPassword("xxxxx"); 29 //設定收件人 30 List<String> receiver = new ArrayList<String>(); 31 receiver.add("xxx@qq.com"); 32 //設定抄送人 33 List<String> copier = new ArrayList<String>(); 34 copier.add("xxx@163.com"); 35 36 List<EmailAttachment> attachments = new ArrayList<EmailAttachment>(); 37 // 新增本地附件 38 EmailAttachment att = new EmailAttachment(); 39 att.setDescription("這是附件圖片"); 40 att.setDisposition(EmailAttachment.ATTACHMENT); 41 att.setName("好看的圖片.png"); 42 att.setPath("C:\\Users\\ZHF\\Desktop\\333.png"); 43 attachments.add(att); 44 // 新增網路附件 45 EmailAttachment att2 = new EmailAttachment(); 46 att2.setDescription("這是網路附件"); 47 att2.setDisposition(EmailAttachment.ATTACHMENT); 48 att2.setName("網路附件.jpg"); 49 att2.setURL(new URL("http://7xpsw5.com1.z0.glb.clouddn.com/41a9ccf2-2a22-44bd-aa3b-8e8779db1caf.jpg")); 50 attachments.add(att2); 51 52 mail.setAttachments(attachments); 53 mail.setCopier(copier); 54 mail.setReceiver(receiver); 55 mail.setSubject("測試郵件傳送主題"); 56 // String string = "<html><head></head><body><div>測試Java傳送郵件內容</div></body></html>"; 57 String string = "測試Java傳送郵件內容"; 58 mail.setContent(string); 59 // IMailUtil mailUtil = MailUtilsFactory.getMailUtil(Protocols.SSL_MAIL_UTIL); 60 IMailUtil mailUtil = MailUtilsFactory.getMailUtil(Protocols.TLS_MAIL_UTIL); 61 mailUtil.send(mail); 62 System.out.println("郵件傳送成功"); 63 } 64 }
8.工程pom.xml檔案(由於本例使用maven工程)
1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 3 <modelVersion>4.0.0</modelVersion> 4 5 <groupId>com.hafiz.zhang</groupId> 6 <artifactId>sendEmail</artifactId> 7 <version>0.0.1-SNAPSHOT</version> 8 <packaging>jar</packaging> 9 10 <name>sendEmail</name> 11 <url>http://maven.apache.org</url> 12 13 <properties> 14 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 15 </properties> 16 17 <dependencies> 18 <dependency> 19 <groupId>org.apache.commons</groupId> 20 <artifactId>commons-email</artifactId> 21 <version>1.4</version> 22 </dependency> 23 <dependency> 24 <groupId>org.slf4j</groupId> 25 <artifactId>slf4j-log4j12</artifactId> 26 <version>1.6.4</version> 27 </dependency> 28 <dependency> 29 <groupId>org.slf4j</groupId> 30 <artifactId>slf4j-api</artifactId> 31 <version>1.6.4</version> 32 </dependency> 33 <dependency> 34 <groupId>log4j</groupId> 35 <artifactId>log4j</artifactId> 36 <version>1.2.16</version> 37 </dependency> 38 <dependency> 39 <groupId>junit</groupId> 40 <artifactId>junit</artifactId> 41 <version>4.12</version> 42 </dependency> 43 </dependencies> 44 </project>
至於,本文為何採用slf4j而不是直接採用log4j進行日誌控制輸出,詳見另一篇部落格:slf4j介紹以及實現原理窺探
9.日誌輸出格式檔案
1 ### set log levels ### 2 log4j.rootLogger = DEBUG , stdout , D , E , F 3 4 ### \u8F93\u51FA\u5230\u63A7\u5236\u53F0 ### 5 log4j.appender.stdout = org.apache.log4j.ConsoleAppender 6 log4j.appender.stdout.Target = System.out 7 log4j.appender.stdout.layout = org.apache.log4j.PatternLayout 8 log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %p ] [ %l-%t:%r ] %m%n 9 10 ## \u8F93\u51FA\u5230\u65E5\u5FD7\u6587\u4EF6 ### 11 log4j.appender.D = org.apache.log4j.DailyRollingFileAppender 12 log4j.appender.D.DatePattern = '.'yyyyMMdd 13 log4j.appender.D.File = logs/api/warn.log 14 log4j.appender.D.Append = true 15 log4j.appender.D.Threshold = WARN 16 log4j.appender.D.layout = org.apache.log4j.PatternLayout 17 log4j.appender.D.encoding=UTF-8 18 log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %p ] [ %l-%t:%r ] %m%n 19 20 ## \u4FDD\u5B58\u5F02\u5E38\u4FE1\u606F\u5230\u5355\u72EC\u6587\u4EF6 ### 21 log4j.appender.E = org.apache.log4j.DailyRollingFileAppender 22 log4j.appender.E.File = logs/api/error.log 23 log4j.appender.E.DatePattern = '.'yyyyMMdd 24 log4j.appender.E.Append = true 25 log4j.appender.E.Threshold = ERROR 26 log4j.appender.E.layout = org.apache.log4j.PatternLayout 27 log4j.appender.E.encoding=UTF-8 28 log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %p ] [ %l-%t:%r ] %m%n 29 30 ## \u4FDD\u5B58\u5F02\u5E38\u4FE1\u606F\u5230\u5355\u72EC\u6587\u4EF6 ### 31 log4j.appender.F = org.apache.log4j.DailyRollingFileAppender 32 log4j.appender.F.File = logs/api/debug.log 33 log4j.appender.F.DatePattern = '.'yyyyMMdd 34 log4j.appender.F.Append = true 35 log4j.appender.F.Threshold = DEBUG 36 log4j.appender.F.layout = org.apache.log4j.PatternLayout 37 log4j.appender.F.encoding=UTF-8 38 log4j.appender.F.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %p ] [ %l-%t:%r ] %m%n
測試結果:
郵件實際收到的內容:
為了保證以後的相容性,本例採用了工廠設計模式,有什麼不正確的或需要改進的,請各位批評指導,謝謝!
附:專案結構圖