常见的邮件协议包括 :
SMTP : 简单邮件传输协议,用于发送电子邮件的传输协议
POP3 : 用于接收电子邮件的标准协议
IMAP : 互联网消息访问协议,是 POP3 的替代协议
这三种协议都有对应 SSL 加密传输的协议,分别是 SMTPS、 POP3S 和 IMAPS
JavaMail 体系结构
除 JavaMail 的核心包之外, JavaMail 还需要 JAF( JavaBeans Activation Framework)来处理不是纯文本的邮件内容。这包括 MIME(多用途互联网邮件扩展)、 URL 页面和文件附件等内容
JavaMail 是由 Sun 定义的一套收发电子邮件的 API,不同的厂商可以提供自己的实现类。但它并没有包含在 JDK 中,而是作为 Java EE 的一部分。
JavaMail 的关键对象
1> Properties : 属性对象,其中封装服务器地址、 端口、 用户名、 密码等信息,具体相关信息如下 :
属性名
|
属性类型
|
说明
|
mail.smtp.host
|
String
|
SMTP 服务器地址,如 smtp.sina.com.cn |
mail.smtp.port
|
int
|
SMTP 服务器端口号,默认为 25 |
mail.smtp.auth
|
boolean
|
SMTP 服务器是否需要用户认证,默认为 false |
mail.smtp.user
|
String
|
SMTP 默认的登录用户名 |
mail.smtp.from
|
String
|
默认的邮件发送源地址 |
mail.smtp.socketFactory.class
|
String
|
socket 工厂类类名,通过设置该属性可以覆盖提供者默认的实现。必须实现 javax.net.SocketFactory 接口 |
mail.smtp.socketFactory.port
|
int
|
指定 socket 工厂类所用的端口号,如果没有设定,则使用默认的端口 |
mail.smtp.socketFactory.fallback
|
boolean
|
设置为 true 时,当使用指定的 socket 类创建 socket 失败后,将使用 java.net.Socket 创建 socket。默认为 true |
mail.smtp.timeout
|
int
|
I/O 连接超时时间,单位为毫秒,默认为永不超时 |
Session : 会话对象,是一堆配置信息的集合
Transport 和 Store : 传输和存储
使用 JavaMail 发送邮件实例
1> 引入 JavaMail 相关包
<
dependency
>
<
groupId
>
javax.mail
groupId
>
<
artifactId
>
mail
artifactId
>
<
version
>
1.4
version
>
dependency
>
2>
import
javax.activation.DataHandler;
import
javax.activation.DataSource;
import
javax.activation.FileDataSource;
import
javax.mail.*;
import
javax.mail.internet.InternetAddress;
import
javax.mail.internet.MimeBodyPart;
import
javax.mail.internet.MimeMessage;
import
javax.mail.internet.MimeMultipart;
import
java.util.Properties;
public class
MailUtils
{
private
String
host
=
""
;
// smtp服务器
private
String
from
=
""
;
// 发件人地址
private
String
to
=
""
;
// 收件人地址
private
String
affix
=
""
;
// 附件地址
private
String
affixName
=
""
;
// 附件名称
private
String
user
=
""
;
// 用户名
private
String
pwd
=
""
;
// 密码
private
String
subject
=
""
;
// 邮件标题
public void
setAddress(String from, String to, String subject)
{
this
.
from
= from;
this
.
to
= to;
this
.
subject
= subject;
}
public void
setAffix(String affix, String affixName)
{
this
.
affix
= affix;
this
.
affixName
= affixName;
}
public void
send(String host, String user, String pwd, String content)
{
this
.
host
= host;
this
.
user
= user;
this
.
pwd
= pwd;
Properties props =
new
Properties();
// 设置发送邮件的邮件服务器的属性(这里使用网易的smtp服务器)
props.put(
"mail.smtp.host"
, host);
// 需要经过授权,也就是有户名和密码的校验,这样才能通过验证(一定要有这一条)
props.put(
"mail.smtp.auth"
,
"true"
);
// 用刚刚设置好的props对象构建一个session
Session session = Session.
getDefaultInstance
(props);
// 有了这句便可以在发送邮件的过程中在console处显示过程信息,供调试使
// 用(你可以在控制台(console)上看到发送邮件的过程)
session.setDebug(
true
);
// 用session为参数定义消息对象
MimeMessage message =
new
MimeMessage(session);
try
{
// 加载发件人地址
message.setFrom(
new
InternetAddress(
from
));
// 加载收件人地址
message.addRecipient(Message.RecipientType.
TO
,
new
InternetAddress(
to
));
// 加载标题
message.setSubject(
subject
);
// 向multipart对象中添加邮件的各个部分内容,包括文本内容和附件
Multipart multipart =
new
MimeMultipart();
// 设置邮件的文本内容
BodyPart contentPart =
new
MimeBodyPart();
contentPart.setText(content);
multipart.addBodyPart(contentPart);
// 添加附件
BodyPart messageBodyPart =
new
MimeBodyPart();
DataSource source =
new
FileDataSource(
affix
);
// 添加附件的内容
messageBodyPart.setDataHandler(
new
DataHandler(source));
// 添加附件的标题
// 这里很重要,通过下面的Base64编码的转换可以保证你的中文附件标题名在发送时不会变成乱码
sun.misc.BASE64Encoder enc =
new
sun.misc.BASE64Encoder();
messageBodyPart.setFileName(
"=?GBK?B?"
+ enc.encode(
affixName
.getBytes()) +
"?="
);
multipart.addBodyPart(messageBodyPart);
// 将multipart对象放到message中
message.setContent(multipart);
// 保存邮件
message.saveChanges();
// 发送邮件
Transport transport = session.getTransport(
"smtp"
);
// 连接服务器的邮箱
transport.connect(host, user, pwd);
// 把邮件发送出去
transport.sendMessage(message, message.getAllRecipients());
transport.close();
}
catch
(Exception e)
{
e.printStackTrace();
}
}
public static void
main(String[] args)
{
MailUtils cn =
new
MailUtils();
// 设置发件人地址、收件人地址和邮件标题
// 设置要发送附件的位置和标题
cn.setAffix(
"/Users/chenshun131/Desktop/附录A.pdf"
,
"123.pdf"
);
/**
* 设置smtp服务器以及邮箱的帐号和密码
* 用QQ 邮箱作为发生者不好使 (原因不明)
* 163 邮箱可以,但是必须开启 POP3/SMTP服务 和 IMAP/SMTP服务
* 因为程序属于第三方登录,所以登录密码必须使用163的授权码
*/
// 注意: [授权码和你平时登录的密码是不一样的]
}
}
直接使用 JavaMail 编写邮件收发程序并不是一件轻松的事情,这归咎于 JavaMail 零散而复杂的 API 以及各种强制需要处理的检查型异常。Spring 对使用 JavaMail 发送邮件进行了很大程度的简化,它为 80%的需求提供了简单的处理方法,剩下的需求则可以通过直接调用 JavaMailAPI 完成
Spring 在 org.springframework.mail 包里通过 MailMessage 和 MailSender 这两个高层抽象接口描述了,两个最重要的内容 : 邮件消息和邮件发送者
MailMessage : 抽象的邮件消息,该接口描述了邮件消息的通用模型,允许开发者通过多个简洁的属性设置方法填充邮件消息的各项内容
MailMessage 有两个实现类: SimpleMailMessage 和 MimeMailMessage,前者都是完全符合 Bean 风格的实现类,后者通过内置 JavaMail 的 MimeMessage 提供实现。在发送简单文本型邮件时, SimpleMailMessage 就可以满足要求,如果 发送复杂的邮件,则可以利用 MimeMailMessage 或直接使用MimeMessage
MailSender : 抽象的邮件发送者,邮件发送者负责将邮件发送到指定的地址上,该接口只用于发送简单的邮件。如果需要发送复杂的邮件,则需要使用 JavaMailSender 子接口
Spring 的邮件异常体系
Spring 在 org.springframework.mail.javamail 包下提供了对 JavaMail 邮件系统的支持。首先,通过 JavaMailSenderImpl 可以方便地创建 JavaMail 环境;其次,通过 MimeMessageHelper 构造出 MimeMessage 对象
向邮件中添加附件或内嵌文件时,文件对应的 MIME 类型是一个很重要的信息,因为 Outlook、Foxmail 等邮件客户端软件必须根据 MIME 类型决定如何处理邮件中的内嵌文件。文件扩展名和 MIME 类型的对应关联在 activation.jar/META-INF 目录下的
mimetypes.default 文件中定义 :
MIME 类型的规范名
|
文件扩展名
|
text/html
|
html htm HTML HTM
|
text/plain
|
txt text TXT TEXT
|
image/gif
|
gif GIF
|
image/ief
|
ief
|
image/jpeg
|
jpeg jpg jpe JPG
|
image/tiff
|
tiff tif
|
image/x-xwindowdump
|
xwd
|
application/postscript
|
ai eps ps
|
application/rtf
|
rtf
|
application/x-tex
|
tex
|
application/x-texinfo
|
texinfo texi
|
application/x-troff
|
t tr roff
|
audio/basic
|
au
|
audio/midi
|
midi mid
|
audio/x-aifc
|
aifc
|
audio/x-aiff
|
aif aiff
|
audio/x-mpeg
|
mpeg mpg
|
audio/x-wav
|
wav
|
video/mpeg
|
mpeg mpg mpe
|
video/quicktime
|
qt mov
|
video/x-msvideo
|
avi
|
注 : 在 http://www.w3school.com.cn/media/media_mimeref.asp
可以找到所有 MIME 类型的信息
发送邮件频繁发送可能会被邮箱服务器识别为垃圾邮件,如 163邮箱可能会出现
554 DT:SPM 发送的邮件内容包含了未被许可的信息,或被系统识别为垃圾邮件。请检查是否有用户发送病毒或者垃圾邮件;
通过 Spring 的 JavaMail 发送各种形式的邮件
例如如下代码
@Autowired
private
JavaMailSender
sender
;
public void
sendSimpleMail() {
// 发送纯文本邮件
SimpleMailMessage msg =
new
SimpleMailMessage();
msg.setSubject(
"注册成功"
);
msg.setText(
"恭喜,您在宝宝淘论坛已经注册成功!您的用户ID为:1234567890"
);
sender
.send(msg);
}
public void
sendHtmlMail()
throws
MessagingException {
// 发送 HTML 类型的邮件
MimeMessage msg =
sender
.createMimeMessage();
MimeMessageHelper helper =
new
MimeMessageHelper(msg,
false
,
"utf-8"
);
// 推荐使用 utf-8 编码
helper.setSubject(
"注册成功"
);
String htmlText =
""
+
"
\"
content-type
\"
content=
\"
text/html; charset=utf-8
\"
>"
+
""
+
"恭喜,您在宝宝淘论坛已经注册成功!您的用户ID为:"
+
"1234567890
"
;
helper.setText(htmlText,
true
);
sender
.send(msg);
}
/**
* 对于一个 Web 应用程序来说,一般情况下,我们不推荐使用内嵌文件的邮件,用
* 户大可将这些资源文件放在一台 Web 资源服务器上,然后简单地通过 URL 来引用这
* 些文件。这带来了明显的好处:邮件体积缩小很多,并且提高了邮件的收发效率,同
* 时邮件的展现效果并不会受到影响
*/
public void
sendInlineMail()
throws
MessagingException {
// 发送带内嵌文件的邮件
MimeMessage msg =
sender
.createMimeMessage();
// 内嵌文件邮件是 multipart类型,第二个入参需要设置为 true
MimeMessageHelper helper =
new
MimeMessageHelper(msg,
true
,
"utf-8"
);
helper.setSubject(
"宝宝淘论坛注册成功"
);
String htmlText =
""
+
"
\"
content-type
\"
content=
\"
text/html; charset=utf-8
\"
>"
+
""
+
"欢迎访问宝宝淘论坛!(我想显示一些其他信息)"
+
"
\"
cid:img01
\"
> "
+
""
;
helper.setText(htmlText,
true
);
ClassPathResource img =
new
ClassPathResource(
"bbt.gif"
);
helper.addInline(
"img01"
, img);
sender
.send(msg);
}
public void
sendAttachmentMail()
throws
Exception {
// 发送带附件的邮件
MimeMessage msg =
sender
.createMimeMessage();
MimeMessageHelper helper =
new
MimeMessageHelper(msg,
true
,
"utf-8"
);
helper.setSubject(
"宝宝淘论坛注册成功"
);
helper.setText(
"欢迎访问宝宝淘论坛!"
);
ClassPathResource file1 =
new
ClassPathResource(
"bbt.zip"
);
helper.addAttachment(
"file01.zip"
, file1.getFile());
ClassPathResource file2 =
new
ClassPathResource(
"file.doc"
);
helper.addAttachment(
"file02.doc"
, file2.getFile());
sender
.send(msg);
}
// 双版本邮件,在 Foxmail 中,用户在默认情况下看到纯文件版本的邮件,可以通过点击工具栏的“ HTML”按钮查看 HTML 版本的邮件
public void
sendAlternativeMail()
throws
Exception {
// 发送纯文本和 HTML 双版本的邮件
MimeMessagePreparator mmp =
new
MimeMessagePreparator() {
public void
prepare(MimeMessage msg)
throws
Exception {
MimeMessageHelper helper =
new
MimeMessageHelper(msg,
true
,
"utf-8"
);
helper.setSubject(
"注册成功"
);
MimeMultipart mmPart =
new
MimeMultipart(
"alternative"
);
// 创建双版本邮件内容
msg.setContent(mmPart);
// 创建纯文本版本的邮件体
BodyPart plainTextPart =
new
MimeBodyPart();
plainTextPart.setText(
"欢迎访问宝宝淘论坛!(创建纯文本版本的邮件体)"
);
mmPart.addBodyPart(plainTextPart);
// 创建 HTML 版本的邮件体
BodyPart htmlPart =
new
MimeBodyPart();
String htmlText =
""
+
"
\"
content-type
\"
content=
\"
text/html; charset=utf-8
\"
>"
+
""
+
"欢迎访问宝宝淘论坛! (创建 HTML 版本的邮件体)"
+
""
;
htmlPart.setContent(htmlText,
"text/html;charset=utf-8"
);
mmPart.addBodyPart(htmlPart);
}
};
sender
.send(mmp);
}
需要在 Spring 工程中引入两个库
<
dependency
>
<
groupId
>
javax.mail
groupId
>
<
artifactId
>
mail
artifactId
>
<
version
>
1.4
version
>
dependency
>
<
dependency
>
<
groupId
>
org.freemarker
groupId
>
<
artifactId
>
freemarker
artifactId
>
<
version
>
2.3.23
version
>
dependency
>
在 spring-mvc.xml 中配置发送者邮件相关信息
<
bean
id
="sender"
class
="org.springframework.mail.javamail.JavaMailSenderImpl"
p
:host
=" smtp.163.com
"
p
:username
="chenshun131"
p
:password
="xxxx"
>
<
property
name
="javaMailProperties"
>
<
props
>
<
prop
key
="mail.smtp.auth"
>
true
prop
>
props
>
property
>
bean
>
在实际应用中发送邮件
邮件发送程序直接在代码中构建邮件内容,对于一些简单的邮件来说,这种方式是可行的。但如果邮件体的内容很复杂,这种方式的弊端将马上暴露出来 —— 直接使用 Servlet 构造复杂内容网页非常复杂
对于 HTML 格式的邮件来说,除少部分内容外,大部分的 HTML 代码都是固定的,因此,在实际应用中,常采用的办法是制作好邮件模板,在发送邮件时,通过模板解析构建最终邮件内容
由于发送邮件相对来说是比较重量级的操作,它受限于邮件服务器和网络的性能,可能需要好几秒的时间。如果直接在业务流程的线程中采用同步的方式发送邮件,业务流程的响应速度将会受到很大的影响。为了降低这种影响,在实际的应用系统中,一般需要采用异步邮件发送方式,使用单独的线程发送邮件,甚至使用 JMS 消息提交邮件发送任务,由单独的邮件发送服务器负责实际邮件发送的任务
使用邮件模板
在开源领域, Velocity 和 FreeMarker 是广泛使用的两个模板框架,由于 FreeMarker 的后发优势其更受青睐度。模版的原理其实很简单,就是用动态的数据替换模板中的特殊标签,生成最终的内容
Spring 为 Freemarker 提供了一个 FreeMarkerConfigurer,通过这个类可以方便地创建Freemarker 的基础设施,然后就可以在此基础上获取 Freemarker 的基础组件实例
例如如下代码 :
@Autowired
private
JavaMailSender
sender
;
@Autowired
private
FreeMarkerConfigurer
freeMarkerConfigurer
;
@Test
public void
sendTemplateMail()
throws
MessagingException {
MimeMessage msg =
sender
.createMimeMessage();
MimeMessageHelper helper =
new
MimeMessageHelper(msg,
false
,
"utf-8"
);
helper.setSubject(
"宝宝淘论坛注册成功:基于模板"
);
String htmlText = getMailText(
"1234567890"
);
// 使用模板产生HTML 邮件体内容
helper.setText(htmlText,
true
);
sender
.send(msg);
}
private
String getMailText(String userId) {
// 通过模板构造邮件内容
String htmlText =
null
;
try
{
Template tpl =
freeMarkerConfigurer
.getConfiguration().getTemplate(
"registerUser.ftl"
);
// 通过指定模板名获取 Freemarker 模板实例
Map map =
new
HashMap();
// 通过 Map 传递动态数据
map.put(
"userId"
, userId);
// 注意动态数据的名字必须和模板标签中指定属性相匹配
htmlText = FreeMarkerTemplateUtils.
processTemplateIntoString
(tpl, map);
// 解析模板并替换动态数据,产生最终的内容
}
catch
(Exception e) {
throw new
RuntimeException(e);
}
return
htmlText;
}
在 spring-mvc.xml 中配置 freemarker发送者邮件相关信息
<
bean
id
="freeMarkerConfigurer"
class
="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"
p
:templateLoaderPath
="classpath:mailTemplate/"
>
<
property
name
="freemarkerSettings"
>
<
props
>
<
prop
key
="template_update_delay"
>
1800
prop
>
<
prop
key
="default_encoding"
>
UTF-8
prop
>
<
prop
key
="locale"
>
zh_CN
prop
>
props
>
property
>
bean
>
registerUser.ftl 中的信息
<
html
>
<
head
>
<
meta
http-equiv=
"content-type"
content=
"text/html; charset=utf-8"
>
head
>
<
body
>
恭喜,您在宝宝淘论坛已经注册成功!您的用户ID为:
<
font
size=
'20'
size=
'30'
>
${
userId
}
font
>
body
>
html
>
异步发送邮件
例如如下代码 :
@Autowired
private
TaskExecutor
taskExecutor
;
// 拥有异步执行能力的任务执行器
@Test
public void
sendAsyncMail() {
taskExecutor
.execute(
new
Runnable() {
public void
run() {
try
{
sendTemplateMail();
// 异步调用 sendTemplateMail()方法发送邮件
System.
out
.println(
"邮件发送成功!"
);
}
catch
(Exception e) {
System.
out
.println(
"邮件发送失败!,异常信息:"
+ e.getMessage());
}
}
});
}
在 spring-mvc.xml 中配置任务执行器信息
<
bean
id
="taskExecutor"
class
="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"
p
:corePoolSize
="10"
p
:maxPoolSize
="30"
/>