使用JavaMail收发邮件

JavaMail

JavaMail,顾名思义,提供给开发者处理电子邮件相关的编程接口。它是Sun发布的用来处理email的API。它可以方便地执行一些常用的邮件传输。我们可以基于JavaMail开发出类似于Microsoft Outlook的应用程序。

JavaMail官网地址:http://www.oracle.com/technetwork/java/javamail/index.html 大家可在其中选择最新版下载(目前最新版本1.4.7)

POP3、IMAP、SMTP

概念普及

要做邮件收发功能,就必须先了解什么是POP3、IMAP、SMTP

SMTP

SMTP 的全称是“Simple Mail Transfer Protocol”,即简单邮件传输协议。它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。SMTP 服务器就是遵循 SMTP 协议的发送邮件服务器

POP3

POP3是Post Office Protocol 3的简称,即邮局协议的第3个版本,它规定怎样将个人计算机连接到Internet的邮件服务器和下载电子邮件的电子协议。它是因特网电子邮件的第一个离线协议标准,POP3允许用户从服务器上把邮件存储到本地主机(即自己的计算机)上,同时删除保存在邮件服务器上的邮件,而POP3服务器则是遵循POP3协议的接收邮件服务器,用来接收电子邮件的。

IMAP

IMAP,即Internet Message Access Protocol(互联网邮件访问协议),您可以通过这种协议从邮件服务器上获取邮件的信息、下载邮件等。IMAP与POP类似,都是一种邮件获取协议。

IMAP和POP3的区别

POP3允许电子邮件客户端下载服务器上的邮件,但是您在电子邮件客户端的操作(如:移动邮件、标记已读等),这是不会反馈到服务器上的,比如:您通过电子邮件客户端收取了QQ邮箱中的3封邮件并移动到了其他文件夹,这些移动动作是不会反馈到服务器上的,也就是说,QQ邮箱服务器上的这些邮件是没有同时被移动的 。但是IMAP就不同了,电子邮件客户端的操作都会反馈到服务器上,您对邮件进行的操作(如:移动邮件、标记已读等),服务器上的邮件也会做相应的动作。也就是说,IMAP是“双向”的。同时,IMAP可以只下载邮件的主题,只有当您真正需要的时候,才会下载邮件的所有内容。
总结:IMAP 可以从整体上提供更稳定的使用体验。POP3 更易丢失邮件或多次下载相同的邮件,而 IMAP 则可以通过在邮件客户端和网络邮箱之间进行双向同步的功能来避免这种情况。

开启IMAP/SMTP服务

根据上一节内容,本文选择使用IMAP服务器作为邮件接收服务器。
要实现邮件收发功能,必须先开启SMTP和IMAP。
以QQ邮箱为例,登录QQ邮箱,进入 设置 -> 账户,开启IMAP/SMTP服务(还可设置邮件收取选项),如图:

Alt text

注意:QQ邮箱在开启后会生成授权码,此授权码在发送及接受邮件时当做密码使用。目前只有QQ邮箱需要授权码,其他邮箱均直接使用其登录密码即可。

各类型邮箱POP3/IMAP/SMTP服务器整理

本人目前只研究过QQ邮箱、QQ企业邮箱、163邮箱、Gmail邮箱、Gmail企业邮箱。现整理如下:

邮箱 POP3服务器 IMAP服务器 SMTP服务器
QQ邮箱 pop.qq.com imap.qq.com smtp.qq.com
QQ企业邮箱 pop.exmail.qq.com imap.exmail.qq.com smtp.exmail.qq.com
163邮箱 pop3.163.com imap.163.com smtp.163.com
Gmail邮箱 pop.gmail.com imap.gmail.com smtp.gmail.com
Gmail企业邮箱 pop.gmail.com imap.gmail.com smtp.gmail.com

注意:Gmail企业邮箱和Gmail邮箱收发服务器相同,但都需要翻墙。另外,要能正常收发Gmail邮件,还需修改Gmail配置,启用不够安全的应用的访问权限,否则会被Gmail拦截掉,设置启用权限的地址:https://www.google.com/settings/security/lesssecureapps (需要先登录)

端口号

端口 POP3服务器 IMAP服务器 SMTP服务器
默认端口 110 143 25
SSL安全链接的端口 995 993 465

注意:随着各个Mail服务器对于安全的重视,纷纷采用基于SSL的Mail登陆方式进行发送和接收电子邮件。所以当使用JavaMail发送电子邮件时,需要根据SSL设定,增加安全验证的功能。

发送邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
private static final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
/**
* 发邮件
*/
public static Boolean sendMail(SendMailVO sendMailVO) {
// 配置发送邮件的环境属性
final Properties props = new Properties();
props.setProperty("mail.smtp.auth", "true");// 发送服务器需要身份验证
props.setProperty("mail.smtp.socketFactory.class", SSL_FACTORY);
props.setProperty("mail.smtp.socketFactory.fallback", "false");
props.setProperty("mail.smtp.socketFactory.port", "465");// 发送邮件服务器端口号
props.setProperty("mail.host", sendMailVO.getHost());// 发送邮件服务器主机名,如qq邮箱为:smtp.qq.com
props.put("mail.user", sendMailVO.getUsername());// 发件人的账号
props.put("mail.password", sendMailVO.getPassword());// 访问SMTP服务时需要提供的密码,qq邮箱时需要使用授权码
try {
List<String> receiver = sendMailVO.getReceiver();// 收件人邮箱,此处是一个List集合,收件人可以有多个
List<String> copyReceiver = sendMailVO.getCopyReceiver();// 抄送人邮箱,可以有多个
logger.info("============== start sendMail ================");
logger.info("Sender: " + props.getProperty("mail.user"));
logger.info("To: " + JSON.toJSONString(receiver));
logger.info("cc: " + JSON.toJSONString(copyReceiver));
logger.info("Subject: " + sendMailVO.getTopic());
logger.info("Body: " + sendMailVO.getContent());
// 构建授权信息,用于进行SMTP进行身份验证
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// 用户名、密码
String userName = props.getProperty("mail.user");
String password = props.getProperty("mail.password");
return new PasswordAuthentication(userName, password);
}
};
Session mailSession = Session.getInstance(props, authenticator);// 使用环境属性和授权信息,创建邮件会话
MimeMessage message = new MimeMessage(mailSession);// 创建邮件消息
// 设置发件人
InternetAddress form = new InternetAddress(props.getProperty("mail.user"));
message.setFrom(form);
// 设置收件人
InternetAddress[] to = new InternetAddress[receiver.size()];
for (int i = 0; i < receiver.size(); i++) {
to[i] = new InternetAddress(receiver.get(i));
}
message.setRecipients(Message.RecipientType.TO, to);
// 设置抄送人
if (copyReceiver != null && copyReceiver.size() > 0) {
InternetAddress[] cc = new InternetAddress[copyReceiver.size()];
for (int i = 0; i < copyReceiver.size(); i++) {
cc[i] = new InternetAddress(copyReceiver.get(i));
}
message.setRecipients(Message.RecipientType.CC, cc);
}
// 设置邮件标题
message.setSubject(sendMailVO.getTopic());
// 设置邮件的内容体
message.setContent(sendMailVO.getContent(), "text/html;charset=UTF-8");
// 发送邮件
Transport.send(message);
logger.info("email send success!");
logger.info("============== end sendMail ================");
return true;
} catch (MessagingException e) {
logger.error("email send failed!", e);
return false;
}
}

接收邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
* 接收邮件
*/
public static List<MailReceiveVO> receiveMail(ReceiveMailVO receiveMailVO) throws MessagingException {
logger.info("============== start receiveMail ================");
logger.info("current user email : " + receiveMailVO.getUsername()); // 当前用户的邮箱账号
long time = System.currentTimeMillis();
// 准备连接服务器的会话信息
Properties props = new Properties();
props.setProperty("mail.imap.socketFactory.class", SSL_FACTORY);
props.setProperty("mail.imap.socketFactory.fallback", "false");
props.setProperty("mail.imap.socketFactory.port", "993");// 接收邮件服务器端口号
props.setProperty("mail.imap.host", receiveMailVO.getHost());// 接收邮件服务器
// 创建Session实例对象
Session session = Session.getInstance(props);
// 创建IMAP协议的Store对象
Store store = session.getStore("imaps");
store.connect(receiveMailVO.getHost(), receiveMailVO.getUsername(), receiveMailVO.getPassword());
// 获得收件箱
IMAPFolder folder = (IMAPFolder) store.getFolder("INBOX");
// 以读写模式打开收件箱
folder.open(Folder.READ_WRITE);
// 获得收件箱中未读邮件列表
Message[] messages = folder.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));// 已读的历史邮件不处理,只处理未读的
logger.info("unread mail size : " + messages.length);
List<MailReceiveVO> mails = new ArrayList<>();
// 解析邮件
for(int i = 0; i < messages.length; i++) {
try {
MimeMessage smsg = (MimeMessage) messages[i];
// 将邮件服务器的邮件Message拷贝到本地中,提高解析邮件内容的效率
MimeMessage cmsg = copyToClientMessage(smsg, session);
MailReceiveVO mailReceiveVO = new MailReceiveVO();
mailReceiveVO.setTopic(cmsg.getSubject());// 主题
mailReceiveVO.setSendTime(cmsg.getSentDate());// 发送时间
// 发件人信息
Address[] froms = cmsg.getFrom();
if (froms != null) {
InternetAddress addr = (InternetAddress) froms[0];
MailContactDO mailContactDO = new MailContactDO();
mailContactDO.setEmail(addr.getAddress());// 发件人邮件地址
mailContactDO.setName(addr.getPersonal()); // 发件人邮件昵称
mailReceiveVO.setSender(mailContactDO);
}
// 收件人信息
Address[] receiverAddrs = cmsg.getRecipients(Message.RecipientType.TO);
List<MailContactDO> receiver = new ArrayList<>();
if (receiverAddrs != null && receiverAddrs.length > 0) {
for (Address tmpAddr : receiverAddrs) {
InternetAddress addr = (InternetAddress) tmpAddr;
MailContactDO mailContactDO = new MailContactDO();
mailContactDO.setEmail(addr.getAddress());
mailContactDO.setName(addr.getPersonal());
receiver.add(mailContactDO);
}
} else { // 如果没有收件人信息,则默认将自己添加到收件人中
MailContactDO mailContactDO = new MailContactDO();
mailContactDO.setEmail(receiveMailVO.getUsername());
receiver.add(mailContactDO);
}
mailReceiveVO.setReceiver(receiver);
// 抄送人信息
Address[] copyReceiverAddrs = cmsg.getRecipients(Message.RecipientType.CC);
if (copyReceiverAddrs != null && copyReceiverAddrs.length > 0) {
List<MailContactDO> copyReceiver = new ArrayList<>();
for (Address tmpAddr : copyReceiverAddrs) {
InternetAddress addr = (InternetAddress) tmpAddr;
MailContactDO mailContactDO = new MailContactDO();
mailContactDO.setEmail(addr.getAddress());
mailContactDO.setName(addr.getPersonal());
copyReceiver.add(mailContactDO);
}
mailReceiveVO.setCopyReceiver(copyReceiver);
}
// 解析邮件内容
String content = getText(cmsg);
mailReceiveVO.setContent(content);
mailReceiveVO.setState(MailState.UNREAD);
smsg.setFlag(Flags.Flag.SEEN, true); // 修改邮件服务器的邮件状态,把邮件标记为已读
mails.add(mailReceiveVO);
} catch (FolderClosedException e){ // 收件箱打开超时后会自动关闭,这次捕获此异常,并重新打开收件箱,重新处理当前邮件
folder.open(Folder.READ_WRITE);
--i;
} catch (IOException e) { // 获取邮件内容失败
logger.error("Failed to get mail content", e);
} catch (Exception e){ // 解析邮件内容失败
logger.error("Failed to resolve mail content", e);
}
}
// 释放资源
folder.close(true);
store.close();
logger.info("接收邮件耗时:" + (System.currentTimeMillis() - time));
logger.info("============== end receiveMail ================");
return mails;
}
/**
* 解析邮件内容
*/
private static String getText(Part p) throws MessagingException, IOException {
if (p.isMimeType("text/*")) {
return (String)p.getContent();
}
if (p.isMimeType("multipart/alternative")) {
// prefer html text over plain text
Multipart mp = (Multipart)p.getContent();
String text = null;
for (int i = 0; i < mp.getCount(); i++) {
Part bp = mp.getBodyPart(i);
if (bp.isMimeType("text/plain")) {
if (text == null)
text = getText(bp);
} else if (bp.isMimeType("text/html")) {
String s = getText(bp);
if (s != null)
return s;
} else {
return getText(bp);
}
}
return text;
} else if (p.isMimeType("multipart/*")) {
Multipart mp = (Multipart)p.getContent();
for (int i = 0; i < mp.getCount(); i++) {
String s = getText(mp.getBodyPart(i));
if (s != null)
return s;
}
}
return null;
}
/**
* 将邮件服务器中的邮件Message拷贝到本地中(这样在解析邮件内容时能提高性能)
* @param serverMessage 邮件服务器上的邮件Message对象
* @param session session
* @return 拷贝到本地后的Message对象(如果拷贝失败,则返回服务器的Message)
*/
private static MimeMessage copyToClientMessage(Message serverMessage, Session session){
MimeMessage smsg = (MimeMessage)serverMessage;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
smsg.writeTo(bos);
bos.close();
SharedByteArrayInputStream bis = new SharedByteArrayInputStream(bos.toByteArray());
MimeMessage cmsg = new MimeMessage(session, bis);
bis.close();
return cmsg;
} catch (Exception e) {
return smsg;
}
}

代码说明:
1、本例是基于IMAP协议来处理邮件接收工作的,代码中只从收件箱中获取未被标记为已读的邮件,将这些邮件解析成MailReceiveVO对象,add到最后需要返回的mailsList集合中,并且在add之前,将邮件服务器上的邮件修改标记为已读状态(因为本例用的是IMAP协议,所以此修改会同步到邮件服务器),等到下一次执行该方法时,将不再处理这次已接收过的邮件。
2、代码中为避免直接使用邮件服务器的Message对象去解析邮件数据(这样做会因为网络传输等原因影响解析效率及程序性能),所以通过copyToClientMessage方法,将邮件服务器上的Message对象通过流的方式拷贝到本地,所有的解析操作都将基于这个本地副本进行,由此大大提高解析效率。(此处理方法来源于官网FAQ:http://www.oracle.com/technetwork/java/javamail/faq-135477.html#imapserverbug)
3、收件箱打开时间过长会有超时问题,超时后会自动关闭,本例代码中捕获FolderClosedException异常,在catch中重新打开收件箱,并重新处理当前邮件,以免出现邮件漏处理的情况。

注意:本例中的getText方法未对附件等情况做处理,如有此类需求,请自行百度。(^o^)

坚持原创技术分享,您的支持将鼓励我继续创作!