一、背景
最近朋友的公司有用到这个功能,之前对这一块也不是很熟悉,就和他一起解决出现的异常的同时,也初窥一下使用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("[email protected]"); 26 mail.setPort("25"); 27 mail.setUserName("[email protected]"); 28 mail.setPassword("xxxxx"); 29 //设置收件人 30 List<String> receiver = new ArrayList<String>(); 31 receiver.add("[email protected]"); 32 //设置抄送人 33 List<String> copier = new ArrayList<String>(); 34 copier.add("[email protected]"); 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
测试结果:
邮件实际收到的内容:
为了保证以后的兼容性,本例采用了工厂设计模式,有什么不正确的或需要改进的,请各位批评指导,谢谢!
附:项目结构图