为了不想到处去下载jar包,我使用maven为我管理,在开始编码这些东西之前,我们先在pom.xml文件中<dependencies>标签内加入以下内容:
1
2
3
4
5
6
7
8
9
10
11
|
<!-- Following jars are involved by MailSender.java -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>
1.5
.
2
</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>
1.1
.
1
</version>
</dependency>
|
为了方便,我抽象了一个MailMessage对象,此对象代表了邮件对象,内封装了收信人、发信人、邮件内容、抄送人、密件抄送等等诸多代表邮件的属性,如下:
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
|
package
com.abc.common.mail;
/**
* Represents a Mail message object which contains all the massages needed
* by an email.
*/
class
MailMessage {
private
String subject;
private
String from;
private
String[] tos;
private
String[] ccs;
private
String[] bccs;
private
String content;
private
String[] fileNames;
/**
* No parameter constructor.
*/
public
MailMessage(){}
/**
* Construct a MailMessage object.
*/
public
MailMessage(String subject, String from, String[] tos,
String[] ccs, String[] bccs, String content, String[] fileNames) {
this
.subject = subject;
this
.from = from;
this
.tos = tos;
this
.ccs = ccs;
this
.bccs = bccs;
this
.content = content;
this
.fileNames = fileNames;
}
/**
* Construct a simple MailMessage object.
*/
public
MailMessage(String subject, String from, String to, String content) {
this
.subject = subject;
this
.from = from;
this
.tos =
new
String[]{to};
this
.content = content;
}
public
String getSubject() {
return
subject;
}
public
void
setSubject(String subject) {
this
.subject = subject;
}
public
String getFrom() {
return
from;
}
public
void
setFrom(String from) {
this
.from = from;
}
public
String[] getTos() {
return
tos;
}
public
void
setTos(String[] tos) {
this
.tos = tos;
}
public
String[] getCcs() {
return
ccs;
}
public
void
setCcs(String[] ccs) {
this
.ccs = ccs;
}
public
String[] getBccs() {
return
bccs;
}
public
void
setBccs(String[] bccs) {
this
.bccs = bccs;
}
public
String getContent() {
return
content;
}
public
void
setContent(String content) {
this
.content = content;
}
public
String[] getFileNames() {
return
fileNames;
}
public
void
setFileNames(String[] fileNames) {
this
.fileNames = fileNames;
}
}
|
另外,我们还需要一个对象来描述发送者的授权问题。即,发送者在发送邮件直线需要获取SMTP服务器的授权,只有经过授权的账户才能发送邮件,这个对象如下:
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
|
package
com.abc.common.mail;
import
javax.mail.Authenticator;
import
javax.mail.PasswordAuthentication;
public
class
MailAuthenticator
extends
Authenticator {
/**
* Represents the username of sending SMTP server.
* <p>For example: If you use smtp.163.com as your smtp server, then the related
* username should be: <br>'<b>testname@163.com</b>', or just '<b>testname</b>' is OK.
*/
private
String username =
null
;
/**
* Represents the password of sending SMTP sever.
* More explicitly, the password is the password of username.
*/
private
String password =
null
;
public
MailAuthenticator(String user, String pass) {
username = user;
password = pass;
}
protected
PasswordAuthentication getPasswordAuthentication() {
return
new
PasswordAuthentication(username, password);
}
}
|
最后,是最重要的主类了。调用此类的sendEmail(MailMessage mail)方法可以发送邮件,这封邮件中可以包含一个或多个附件。
但是在发送附件之前,我们需要了解附件名和内容乱码的问题:MIME要解决的一个问题就是将SMTP协议不支持的字节流转换成为SMTP 协议支持的字节流。比如我们要通过邮件传输一个附件文档,该附件文档就是一个8bit 字节流,如果简单的直接通过SMTP 发送,其最高位信息将被丢失。MIME规定可以用两种编码方式将8bit 的字节流编码成为低于8bit 的字节流,它们分别是BASE64 编码(BASE64 将8bit 字节流编码成6bit 字节流)和QP 编码。这两种编码方式同样应用在对中文的编码上。例如如果邮件中文题目叫做“CVS 介绍”,那么其编码后的形式可能为:
1
|
Subject: =?gb2312?B?Q1ZTLS3QpMX0LnBwdA==?=
|
其中,标题字符串以”=?”开始,以”?=”结束。”gb2312”表示字符串的字符集,而以”?”分隔的”B”就表示此字符串的编码方式为BASE64。那么,此编码从何而来的呢?查阅相关资料后,发现MimeUtility.encodeWord()和MimeUtility.encodeText()等方法就是用来编码中文等特殊字符的:
1
2
3
4
5
6
|
//solve encoding problem of attachments file name.
try
{
fileName = MimeUtility.encodeText(fileName);
}
catch
(UnsupportedEncodingException e) {
LOGGER.error(
"Cannot convert the encoding of attachments file name."
, e);
}
|
同样的, 我们处理此标题时就要先将BASE64编码的6bit 字节流转换为原来的8bit 字节流,再根据字符集”gb2312”转换为Java 中的String 类型。这里可以简单的使用JavaMail 提供的MimeUtility.decodeWord()或者MimeUtility.decodeText()静态方法将编码后的字符串解码。当然,不是每个情况都会出现乱码的,所以,不要对所有的乱码都执行这个操作,因此,我们需要判断其内容是不是符合某些规则,满足这些规则的字符串,我们可以视其为乱码,并执行相应的解码操作。于是,我封装了一个方法,方法内部进行了内容的判断,如果满足规则,则进行解码,否则不进行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/**
* For receiving an email, the sender, receiver, reply-to and subject may
* be messy code. The default encoding of HTTP is ISO8859-1, In this situation,
* use MimeUtility.decodeTex() to convert these information to GBK encoding.
* @param res The String to be decoded.
* @return A decoded String.
*/
private
static
String mimeDecodeString(String res) {
if
(res !=
null
) {
String s = res.trim();
try
{
if
(s.startsWith(
"=?GB"
) || s.startsWith(
"=?gb"
)
|| from.startsWith(
"=?UTF"
) || s.startsWith(
"=?utf"
)) {
s = MimeUtility.decodeText(from);
}
}
catch
(Exception e) {
LOGGER.error(
"Decode string error. Origin string is: "
+ res, e);
}
return
from;
}
return
null
;
}
|
另外,这个类中还有一个发送匿名邮件的API叫sendAnonymousEmail(MailMessage mail)(仅供交流学习研究使用,不要拿去做坏事哦)。注意,此处的匿名,并不是不写发送者的邮箱,这里的匿名是指我们可以输入任何有效的邮箱地址,这个地址不一定存在,只需要满足邮箱格式的地址即可。比如noreply@sina.cc,又比如111111@111.com,通过这类地址实现隐藏发送者地址的目的。事实上,我们也无需输入真实的发送者地址,因为这封邮件将跳过发送者的SMTP服务器而直接发送到接收者的服务器上。要想明白这个道理,我们得先说说MX。
MX(Mail Exchanger)记录是邮件交换记录,它指向一个邮件服务器,用于电子邮件系统发邮件时根据收信人的地址后缀来定位邮件服务器。例如,当Internet上的某用户要发一封信给 user@mydomain.com 时,该用户的邮件系统通过本机DNS查找mydomain.com这个域名的MX记录,如果MX记录存在,用户计算机就将邮件发送到MX记录所指定的邮件服务器上。
简单的说,MX记录就是用于为发送的邮件指路的记录,它直接指向收件人邮箱所在的域的邮件接收服务器。有了这个邮件接收服务器地址,我们的机器就可以直接向该服务器传送邮件了。
话不多说,看看这个发送匿名邮件的API吧:
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
|
/**
* Send anonymous email. Note that although we could give any address as from address,
* (for example: <b>'a@a.a' is valid</b>), the from of MailMessage should always be the
* correct format of email address(for example the <b>'aaaa' is invalid</b>). Otherwise
* an exception would be thrown say that username is invalid.
* @param mail The MailMessage object which contains at least all the required
* attributes to be sent.
*/
public
static
void
sendAnonymousEmail(MailMessage mail) {
String dns =
"dns://"
;
Hashtable<String, String> env =
new
Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.dns.DnsContextFactory"
);
env.put(Context.PROVIDER_URL, dns);
String[] tos = mail.getTos();
try
{
DirContext ctx =
new
InitialDirContext(env);
for
(String to:tos) {
String domain = to.substring(to.indexOf(
'@'
) +
1
);
//Get MX(Mail eXchange) records from DNS
Attributes attrs = ctx.getAttributes(domain,
new
String[] {
"MX"
});
if
(attrs ==
null
|| attrs.size() <=
0
) {
throw
new
java.lang.IllegalStateException(
"Error: Your DNS server has no Mail eXchange records!"
);
}
@SuppressWarnings
(
"rawtypes"
)
NamingEnumeration servers = attrs.getAll();
String smtpHost =
null
;
boolean
isSend =
false
;
StringBuffer buf =
new
StringBuffer();
//try all the mail exchange server to send the email.
while
(servers.hasMore()) {
Attribute hosts = (Attribute) servers.next();
for
(
int
i =
0
; i < hosts.size(); ++i) {
//sample: 20 mx2.qq.com
smtpHost = (String) hosts.get(i);
//parse the string to get smtpHost. sample: mx2.qq.com
smtpHost = smtpHost.substring(smtpHost.lastIndexOf(
' '
) +
1
);
try
{
sendEmail(smtpHost, mail,
true
);
isSend =
true
;
return
;
}
catch
(Exception e) {
LOGGER.error(
""
, e);
buf.append(e.toString()).append(
"\r\n"
);
continue
;
}
}
}
if
(!isSend) {
throw
new
java.lang.IllegalStateException(
"Error: Send email error."
+ buf.toString());
}
}
}
catch
(NamingException e) {
LOGGER.error(
""
, e);
}
}
|
这个API中,先从邮件中封装的收件人地址里取出收件人所在发服务器地址,然后通过该地址查找本地DNS记录,如果未找到,则抛出IllegalStateException,因为没法知道收件人的邮件服务器地址就没法发送匿名邮件了。如果找到,则尝试依次向每个邮件服务器发送该邮件,如果发送成功,则立即返回,不再尝试下一个邮件服务器地址。如果发送失败,则会抛出异常,提醒失败。注意到这个API中间的
1
|
sendEmail(smtpHost, mail,
true
);
|
此方法是我封装的用于发送邮件的基础方法。话不多说,先上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
* Send Email. Use string array to represents attachments file names.
* @see #sendEmail(String, String, String[], String[], String[], String, File[])
*/
private
static
void
sendEmail(String smtpHost, MailMessage mail,
boolean
isAnonymousEmail) {
if
(mail ==
null
) {
throw
new
IllegalArgumentException(
"Param mail can not be null."
);
}
String[] fileNames = mail.getFileNames();
//only needs to check the param: fileNames, other params would be checked through
//the override method.
File[] files =
null
;
if
(fileNames !=
null
&& fileNames.length >
0
) {
files =
new
File[fileNames.length];
for
(
int
i =
0
; i < files.length; i++) {
File file =
new
File(fileNames[i]);
files[i] = file;
}
}
sendEmail(smtpHost, mail.getSubject(), mail.getFrom(), mail.getTos(),
mail.getCcs(), mail.getBccs(), mail.getContent(), files, isAnonymousEmail);
}
|
为了重用有些代码,我特意提取了一部分公共的部分,因此此方法是一个重载方法,也是最核心的方法了。需要注意的是,发送匿名邮件时,需要将mail.smtp.auth属性设置为false,并且在获取邮件session时,不需要提供邮件验证器Authenticator:
1
2
3
4
5
6
|
if
(isAnonymousEmail) {
//only anonymous email needs param smtpHost
props.put(
"mail.smtp.host"
, smtpHost);
props.put(
"mail.smtp.auth"
,
"false"
);
session = Session.getInstance(props,
null
);
}
|
下面再看看这个被调用的sendEmail方法吧:
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
|
/**
* Send Email. Note that content and attachments cannot be empty at the same time.
* @param smtpHost The SMTPHost. This param is needed when sending an anonymous email.
* When sending normal email, the param is ignored and the default SMTPServer
* configured is used.
* @param subject The email subject.
* @param from The sender address. This address must be available in SMTPServer.
* @param tos The receiver addresses. At least 1 address is valid.
* @param ccs The 'copy' receiver. Can be empty.
* @param bccs The 'encrypt copy' receiver. Can be empty.
* @param content The email content.
* @param attachments The file array represent attachments to be send.
* @param isAnonymousEmail If this mail is send in anonymous mode. When set to true, the
* param smtpHost is needed and sender's email address from should be in correct
* pattern.
*/
private
static
void
sendEmail(String smtpHost, String subject, String from,
String[] tos, String[] ccs, String[] bccs, String content,
File[] attachments,
boolean
isAnonymousEmail) {
//parameter check
if
(isAnonymousEmail && smtpHost ==
null
) {
throw
new
IllegalStateException(
"When sending anonymous email, param smtpHost cannot be null"
);
}
if
(subject ==
null
|| subject.length() ==
0
) {
subject =
"Auto-generated subject"
;
}
if
(from ==
null
) {
throw
new
IllegalArgumentException(
"Sender's address is required."
);
}
if
(tos ==
null
|| tos.length ==
0
) {
throw
new
IllegalArgumentException(
"At lease 1 receive address is required."
);
}
if
(content ==
null
&& (attachments ==
null
|| attachments.length ==
0
)) {
throw
new
IllegalArgumentException(
"Content and attachments cannot be empty at the same time"
);
}
if
(attachments !=
null
&& attachments.length >
0
) {
List<File> invalidAttachments =
new
ArrayList<>();
for
(File attachment:attachments) {
if
(!attachment.exists() || attachment.isDirectory()
|| !attachment.canRead()) {
invalidAttachments.add(attachment);
}
}
if
(invalidAttachments.size() >
0
) {
String msg =
""
;
for
(File attachment:invalidAttachments) {
msg +=
"\n\t"
+ attachment.getAbsolutePath();
}
throw
new
IllegalArgumentException(
"The following attachments are invalid:"
+ msg);
}
}
Session session;
Properties props =
new
Properties();
props.put(
"mail.transport.protocol"
,
"smtp"
);
if
(isAnonymousEmail) {
//only anonymous email needs param smtpHost
props.put(
"mail.smtp.host"
, smtpHost);
props.put(
"mail.smtp.auth"
,
"false"
);
session = Session.getInstance(props,
null
);
}
else
{
//normal email does not need param smtpHost and
//uses the default host SMTPServer
props.put(
"mail.smtp.host"
, SMTPServer);
props.put(
"mail.smtp.auth"
,
"true"
);
session = Session.getInstance(
props,
new
MailAuthenticator(SMTPUsername, SMTPPassword));
}
//create message
MimeMessage msg =
new
MimeMessage(session);
try
{
//Multipart is used to store many BodyPart objects.
Multipart multipart=
new
MimeMultipart();
BodyPart part =
new
MimeBodyPart();
part.setContent(content,
"text/html;charset=gb2312"
);
//add email content part.
multipart.addBodyPart(part);
//add attachment parts.
if
(attachments !=
null
&& attachments.length >
0
) {
for
(File attachment: attachments) {
String fileName = attachment.getName();
DataSource dataSource =
new
FileDataSource(attachment);
DataHandler dataHandler =
new
DataHandler(dataSource);
part =
new
MimeBodyPart();
part.setDataHandler(dataHandler);
//solve encoding problem of attachments file name.
try
{
fileName = MimeUtility.encodeText(fileName);
}
catch
(UnsupportedEncodingException e) {
LOGGER.error(
"Cannot convert the encoding of attachments file name."
, e);
}
//set attachments the original file name. if not set,
//an auto-generated name would be used.
part.setFileName(fileName);
multipart.addBodyPart(part);
}
}
msg.setSubject(subject);
msg.setSentDate(
new
Date());
//set sender
msg.setFrom(
new
InternetAddress(from));
//set receiver,
for
(String to: tos) {
msg.addRecipient(RecipientType.TO,
new
InternetAddress(to));
}
if
(ccs !=
null
&& ccs.length >
0
) {
for
(String cc: ccs) {
msg.addRecipient(RecipientType.CC,
new
InternetAddress(cc));
}
}
if
(bccs !=
null
&& bccs.length >
0
) {
for
(String bcc: bccs) {
msg.addRecipient(RecipientType.BCC,
new
InternetAddress(bcc));
}
}
msg.setContent(multipart);
//save the changes of email first.
msg.saveChanges();
//to see what commands are used when sending a email,
//use session.setDebug(true)
//session.setDebug(true);
//send email
Transport.send(msg);
LOGGER.info(
"Send email success."
);
System.out.println(
"Send html email success."
);
}
catch
(NoSuchProviderException e) {
LOGGER.error(
"Email provider config error."
, e);
}
catch
(MessagingException e) {
LOGGER.error(
"Send email error."
, e);
}
}
|
有了《JavaMail发送和接收邮件API(详解)》一文的基础和前文的叙述,我想里面的逻辑应该不用多解释了吧。下面主要讲讲里面的几个变量。
正如你所见,里面的几个变量SMTPServer、SMTPUsername和SMTPPassword是需要配置的。如果是发送匿名邮件,那么SMTPUsername和SMTPPassword两个变量可以不用配置。这里,我是将这些内容搬到了项目的一个配置文件里,并在初始化这个对象时就去读取指定的配置文件获得这些值:
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
|
private
static
String SMTPServer;
private
static
String SMTPUsername;
private
static
String SMTPPassword;
static
{
loadConfigProperties();
}
/**
* Load configuration properties to initialize attributes.
*/
private
static
void
loadConfigProperties() {
//get current path
File f =
new
File(
""
);
String absolutePath = f.getAbsolutePath();
String propertiesPath =
""
;
String OSName = System.getProperty(
"os.name"
);
if
(OSName.contains(
"Windows"
)) {
propertiesPath = absolutePath +
"\\..\\src\\main\\resources\\project.properties"
;
}
else
if
(OSName.contains(
"unix"
)) {
propertiesPath = absolutePath +
"/../src/main/resources/project.properties"
;
}
f =
new
File(propertiesPath);
if
(!f.exists()) {
throw
new
RuntimeException(
"Porperties file not found at: "
+ f.getAbsolutePath());
}
Properties props =
new
Properties();
try
{
props.load(
new
FileInputStream(f));
SMTPServer = props.getProperty(
"AbcCommon.mail.SMTPServer"
);
SMTPUsername = props.getProperty(
"AbcCommon.mail.SMTPUsername"
);
SMTPPassword = props.getProperty(
"AbcCommon.mail.SMTPPassword"
);
POP3Server = props.getProperty(
"AbcCommon.mail.POP3Server"
);
POP3Username = props.getProperty(
"AbcCommon.mail.POP3Username"
);
POP3Password = props.getProperty(
"AbcCommon.mail.POP3Password"
);
}
catch
(FileNotFoundException e) {
LOGGER.error(
"File not found at "
+ f.getAbsolutePath(), e);
}
catch
(IOException e) {
LOGGER.error(
"Error reading config file "
+ f.getName(), e);
}
}
|
说了这么多发送邮件,下面再说说接收邮件。
首先,接收邮件,是肯定需要用户名和密码的,因此此方法至少含有两个参数。由于各大邮件服务公司的邮件服务器命名不统一,因此还需要一个参数来指定接收邮件的服务器,于是,此方法将含有三个参数。接收邮件的思路是:用username和password创建一个邮件验证器Authenticator,通过这个Authenticator来获得一个邮件Session。拿到Session后,通过
1
2
|
Store store = session.getStore(
"pop3"
);
Folder inbox = store.getFolder(
"INBOX"
);
|
一句,可以获得该账户的收件箱。《JavaMail发送和接收邮件API(详解)》一文中有提到,Store即是用来接收邮件的对象。然后可以通过
1
|
Message[] messages = inbox.getMessages();
|
来获得收件箱中的所有信息。接下来就可以迭代这个数组获取需要的内容了。整个方法如下:
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
|
/**
* Receive Email from POPServer. Use POP3 protocal by default. Thus,
* call this method, you need to provide a pop3 mail server address.
* @param emailAddress The email account in the POPServer.
* @param password The password of email address.
*/
public
static
void
receiveEmail(String host, String username, String password) {
//param check. If param is null, use the default configured value.
if
(host ==
null
) {
host = POP3Server;
}
if
(username ==
null
) {
username = POP3Username;
}
if
(password ==
null
) {
password = POP3Password;
}
Properties props = System.getProperties();
//MailAuthenticator authenticator = new MailAuthenticator(username, password);
try
{
Session session = Session.getDefaultInstance(props,
null
);
// Store store = session.getStore("imap");
Store store = session.getStore(
"pop3"
);
// Connect POPServer
store.connect(host, username, password);
Folder inbox = store.getFolder(
"INBOX"
);
if
(inbox ==
null
) {
throw
new
RuntimeException(
"No inbox existed."
);
}
// Open the INBOX with READ_ONLY mode and start to read all emails.
inbox.open(Folder.READ_ONLY);
System.out.println(
"TOTAL EMAIL:"
+ inbox.getMessageCount());
Message[] messages = inbox.getMessages();
SimpleDateFormat sdf =
new
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss"
);
for
(
int
i =
0
; i < messages.length; i++) {
Message msg = messages[i];
String from = InternetAddress.toString(msg.getFrom());
String replyTo = InternetAddress.toString(msg.getReplyTo());
String to = InternetAddress.toString(
msg.getRecipients(Message.RecipientType.TO));
String subject = msg.getSubject();
Date sent = msg.getSentDate();
Date ress = msg.getReceivedDate();
String type = msg.getContentType();
System.out.println(
(i +
1
) +
".---------------------------------------------"
);
System.out.println(
"From:"
+ mimeDecodeString(from));
System.out.println(
"Reply To:"
+ mimeDecodeString(replyTo));
System.out.println(
"To:"
+ mimeDecodeString(to));
System.out.println(
"Subject:"
+ mimeDecodeString(subject));
System.out.println(
"Content-type:"
+ type);
if
(sent !=
null
) {
System.out.println(
"Sent Date:"
+ sdf.format(sent));
}
if
(ress !=
null
) {
System.out.println(
"Receive Date:"
+ sdf.format(ress));
}
// //Get message headers.
// @SuppressWarnings("rawtypes")
// Enumeration headers = msg.getAllHeaders();
// while (headers.hasMoreElements()) {
// Header h = (Header) headers.nextElement();
// String name = h.getName();
// String val = h.getValue();
// System.out.println(name + ": " + val);
// }
// //get the email content.
// Object content = msg.getContent();
// System.out.println(content);
// //print content
// Reader reader = new InputStreamReader(
// messages[i].getInputStream());
// int a = 0;
// while ((a = reader.read()) != -1) {
// System.out.print((char) a);
// }
}
// close connection. param false represents do not delete messaegs on server.
inbox.close(
false
);
store.close();
// } catch(IOException e) {
// LOGGER.error("IOException caught while printing the email content", e);
}
catch
(MessagingException e) {
LOGGER.error(
"MessagingException caught when use message object"
, e);
}
}
|
注意到我们在处理邮件中可能出现乱码的内容时,调用了前文提到的自定义的mimeDecodeString()方法:
1
|
System.out.println(
"Subject:"
+ mimeDecodeString(subject));
|
还有,这里面的几个变量:POP3Server、POP3Username和POP3Password也是需要配置的,并会在初始化这个工具类的时候读取。
话不多说,让我们先发送一封试试。为了方便,随便找几个文件放入C:\\根目录下:
直接在MailUtil类中加入main方法:
Ctrl+F11执行这个程序,首先可以在控制台看见以下内容(注意执行的程序和发送邮件的时间):
然后再进入邮箱,看到收到的邮件:
打开邮件后,会看到以下内容(注意标题,发件人,收件人和邮件内容,发送时间):
在看看附件的内容(注意附件名字):
匿名邮件的发送也类似,我已经测试过了,这里不再贴出。好了,最后再贴出工具类的完整代码:
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
|
package
com.abc.common.mail;
import
java.io.File;
import
java.io.FileInputStream;
import
java.io.FileNotFoundException;
import
java.io.IOException;
import
java.io.UnsupportedEncodingException;
import
java.text.SimpleDateFormat;
import
java.util.ArrayList;
import
java.util.Date;
import
java.util.Hashtable;
import
java.util.List;
import
java.util.Properties;
import
javax.activation.DataHandler;
import
javax.activation.DataSource;
import
javax.activation.FileDataSource;
import
javax.mail.BodyPart;
import
javax.mail.Folder;
import
javax.mail.Message;
import
javax.mail.Message.RecipientType;
import
javax.mail.MessagingException;
import
javax.mail.Multipart;
import
javax.mail.NoSuchProviderException;
import
javax.mail.Session;
import
javax.mail.Store;
import
javax.mail.Transport;
import
javax.mail.internet.InternetAddress;
import
javax.mail.internet.MimeBodyPart;
import
javax.mail.internet.MimeMessage;
import
javax.mail.internet.MimeMultipart;
import
javax.mail.internet.MimeUtility;
import
javax.naming.Context;
import
javax.naming.NamingEnumeration;
import
javax.naming.NamingException;
import
javax.naming.directory.Attribute;
import
javax.naming.directory.Attributes;
import
javax.naming.directory.DirContext;
import
javax.naming.directory.InitialDirContext;
import
org.apache.log4j.Logger;
public
class
MailUtil {
private
static
final
Logger LOGGER = Logger.getLogger(MailUtil.
class
);
private
static
String SMTPServer;
private
static
String SMTPUsername;
private
static
String SMTPPassword;
private
static
String POP3Server;
private
static
String POP3Username;
private
static
String POP3Password;
static
{
loadConfigProperties();
}
public
static
void
main(String[] args) {
//发送邮件
MailMessage mail =
new
MailMessage(
"test-subject"
,
"xxxx@163.com"
,
"yyyy@126.com"
,
"This is mail content"
);
//set attachments
String[] attachments =
new
String[]{
"C:\\AndroidManifest.xml"
,
"C:\\ic_launcher-web.png"
,
"C:\\光良 - 童话.mp3"
,
"C:\\文档测试.doc"
,
"C:\\中文文件名测试.txt"
};
mail.setFileNames(attachments);
sendEmail(mail);
//接收邮件
receiveEmail(POP3Server, POP3Username, POP3Password);
//发送匿名邮件
MailMessage anonymousMail =
new
MailMessage(
"subject"
,
"a@a.a"
,
"zzzz@qq.com"
,
"content"
);
anonymousMail.setFileNames(attachments);
sendAnonymousEmail(anonymousMail);
}
/**
* Load configuration properties to initialize attributes.
*/
private
static
void
loadConfigProperties() {
File f =
new
File(
""
);
//this path would point to AbcCommon
String absolutePath = f.getAbsolutePath();
String propertiesPath =
""
;
String OSName = System.getProperty(
"os.name"
);
if
(OSName.contains(
"Windows"
)) {
propertiesPath = absolutePath +
"\\..\\src\\main\\resources\\project.properties"
;
}
else
if
(OSName.contains(
"unix"
)) {
propertiesPath = absolutePath +
"/../src/main/resources/project.properties"
;
}
f =
new
File(propertiesPath);
if
(!f.exists()) {
throw
new
RuntimeException(
"Porperties file not found at: "
+ f.getAbsolutePath());
}
Properties props =
new
Properties();
try
{
props.load(
new
FileInputStream(f));
SMTPServer = props.getProperty(
"AbcCommon.mail.SMTPServer"
);
SMTPUsername = props.getProperty(
"AbcCommon.mail.SMTPUsername"
);
SMTPPassword = props.getProperty(
"AbcCommon.mail.SMTPPassword"
);
POP3Server = props.getProperty(
"AbcCommon.mail.POP3Server"
);
POP3Username = props.getProperty(
"AbcCommon.mail.POP3Username"
);
POP3Password = props.getProperty(
"AbcCommon.mail.POP3Password"
);
}
catch
(FileNotFoundException e) {
LOGGER.error(
"File not found at "
+ f.getAbsolutePath(), e);
}
catch
(IOException e) {
LOGGER.error(
"Error reading config file "
+ f.getName(), e);
}
}
/**
* Send email. Note that the fileNames of MailMessage are the absolute path of file.
* @param mail The MailMessage object which contains at least all the required
* attributes to be sent.
*/
public
static
void
sendEmail(MailMessage mail) {
sendEmail(
null
, mail,
false
);
}
/**
* Send anonymous email. Note that although we could give any address as from address,
* (for example: <b>'a@a.a' is valid</b>), the from of MailMessage should always be the
* correct format of email address(for example the <b>'aaaa' is invalid</b>). Otherwise
* an exception would be thrown say that username is invalid.
* @param mail The MailMessage object which contains at least all the required
* attributes to be sent.
*/
public
static
void
sendAnonymousEmail(MailMessage mail) {
String dns =
"dns://"
;
Hashtable<String, String> env =
new
Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.dns.DnsContextFactory"
);
env.put(Context.PROVIDER_URL, dns);
String[] tos = mail.getTos();
try
{
DirContext ctx =
new
InitialDirContext(env);
for
(String to:tos) {
String domain = to.substring(to.indexOf(
'@'
) +
1
);
//Get MX(Mail eXchange) records from DNS
Attributes attrs = ctx.getAttributes(domain,
new
String[] {
"MX"
});
if
(attrs ==
null
|| attrs.size() <=
0
) {
throw
new
java.lang.IllegalStateException(
"Error: Your DNS server has no Mail eXchange records!"
);
}
@SuppressWarnings
(
"rawtypes"
)
NamingEnumeration servers = attrs.getAll();
String smtpHost =
null
;
boolean
isSend =
false
;
StringBuffer buf =
new
StringBuffer();
//try all the mail exchange server to send the email.
while
(servers.hasMore()) {
Attribute hosts = (Attribute) servers.next();
for
(
int
i =
0
; i < hosts.size(); ++i) {
//sample: 20 mx2.qq.com
smtpHost = (String) hosts.get(i);
//parse the string to get smtpHost. sample: mx2.qq.com
smtpHost = smtpHost.substring(smtpHost.lastIndexOf(
' '
) +
1
);
try
{
sendEmail(smtpHost, mail,
true
);
isSend =
true
;
return
;
}
catch
(Exception e) {
LOGGER.error(
""
, e);
buf.append(e.toString()).append(
"\r\n"
);
continue
;
}
}
}
if
(!isSend) {
throw
new
java.lang.IllegalStateException(
"Error: Send email error."
+ buf.toString());
}
}
}
catch
(NamingException e) {
LOGGER.error(
""
, e);
}
}
/**
* Send Email. Use string array to represents attachments file names.
* @see #sendEmail(String, String, String[], String[], String[], String, File[])
*/
private
static
void
sendEmail(String smtpHost,
MailMessage mail,
boolean
isAnonymousEmail) {
if
(mail ==
null
) {
throw
new
IllegalArgumentException(
"Param mail can not be null."
);
}
String[] fileNames = mail.getFileNames();
//only needs to check the param: fileNames, other params would be checked through
//the override method.
File[] files =
null
;
if
(fileNames !=
null
&& fileNames.length >
0
) {
files =
new
File[fileNames.length];
for
(
int
i =
0
; i < files.length; i++) {
File file =
new
File(fileNames[i]);
files[i] = file;
}
}
sendEmail(smtpHost, mail.getSubject(), mail.getFrom(), mail.getTos(),
mail.getCcs(), mail.getBccs(), mail.getContent(), files, isAnonymousEmail);
}
/**
* Send Email. Note that content and attachments cannot be empty at the same time.
* @param smtpHost The SMTPHost. This param is needed when sending an anonymous email.
* When sending normal email, the param is ignored and the default SMTPServer
* configured is used.
* @param subject The email subject.
* @param from The sender address. This address must be available in SMTPServer.
* @param tos The receiver addresses. At least 1 address is valid.
* @param ccs The 'copy' receiver. Can be empty.
* @param bccs The 'encrypt copy' receiver. Can be empty.
* @param content The email content.
* @param attachments The file array represent attachments to be send.
* @param isAnonymousEmail If this mail is send in anonymous mode. When set to true, the
* param smtpHost is needed and sender's email address from should be in correct
* pattern.
*/
private
static
void
sendEmail(String smtpHost, String subject,
String from, String[] tos, String[] ccs, String[] bccs,
String content, File[] attachments,
boolean
isAnonymousEmail) {
//parameter check
if
(isAnonymousEmail && smtpHost ==
null
) {
throw
new
IllegalStateException(
"When sending anonymous email, param smtpHost cannot be null"
);
}
if
(subject ==
null
|| subject.length() ==
0
) {
subject =
"Auto-generated subject"
;
}
if
(from ==
null
) {
throw
new
IllegalArgumentException(
"Sender's address is required."
);
}
if
(tos ==
null
|| tos.length ==
0
) {
throw
new
IllegalArgumentException(
"At lease 1 receive address is required."
);
}
if
(content ==
null
&& (attachments ==
null
|| attachments.length ==
0
)) {
throw
new
IllegalArgumentException(
"Content and attachments cannot be empty at the same time"
);
}
if
(attachments !=
null
&& attachments.length >
0
) {
List<File> invalidAttachments =
new
ArrayList<>();
for
(File attachment:attachments) {
if
(!attachment.exists() || attachment.isDirectory()
|| !attachment.canRead()) {
invalidAttachments.add(attachment);
}
}
if
(invalidAttachments.size() >
0
) {
String msg =
""
;
for
(File attachment:invalidAttachments) {
msg +=
"\n\t"
+ attachment.getAbsolutePath();
}
throw
new
IllegalArgumentException(
"The following attachments are invalid:"
+ msg);
}
}
Session session;
Properties props =
new
Properties();
props.put(
"mail.transport.protocol"
,
"smtp"
);
if
(isAnonymousEmail) {
//only anonymous email needs param smtpHost
props.put(
"mail.smtp.host"
, smtpHost);
props.put(
"mail.smtp.auth"
,
"false"
);
session = Session.getInstance(props,
null
);
}
else
{
//normal email does not need param smtpHost and uses the default host SMTPServer
props.put(
"mail.smtp.host"
, SMTPServer);
props.put(
"mail.smtp.auth"
,
"true"
);
session = Session.getInstance(props,
new
MailAuthenticator(SMTPUsername, SMTPPassword));
}
//create message
MimeMessage msg =
new
MimeMessage(session);
try
{
//Multipart is used to store many BodyPart objects.
Multipart multipart=
new
MimeMultipart();
BodyPart part =
new
MimeBodyPart();
part.setContent(content,
"text/html;charset=gb2312"
);
//add email content part.
multipart.addBodyPart(part);
//add attachment parts.
if
(attachments !=
null
&& attachments.length >
0
) {
for
(File attachment: attachments) {
String fileName = attachment.getName();
DataSource dataSource =
new
FileDataSource(attachment);
DataHandler dataHandler =
new
DataHandler(dataSource);
part =
new
MimeBodyPart();
part.setDataHandler(dataHandler);
//solve encoding problem of attachments file name.
try
{
fileName = MimeUtility.encodeText(fileName);
}
catch
(UnsupportedEncodingException e) {
LOGGER.error(
"Cannot convert the encoding of attachments file name."
, e);
}
//set attachments the original file name. if not set,
//an auto-generated name would be used.
part.setFileName(fileName);
multipart.addBodyPart(part);
}
}
msg.setSubject(subject);
msg.setSentDate(
new
Date());
//set sender
msg.setFrom(
new
InternetAddress(from));
//set receiver,
for
(String to: tos) {
msg.addRecipient(RecipientType.TO,
new
InternetAddress(to));
}
if
(ccs !=
null
&& ccs.length >
0
) {
for
(String cc: ccs) {
msg.addRecipient(RecipientType.CC,
new
InternetAddress(cc));
}
}
if
(bccs !=
null
&& bccs.length >
0
) {
for
(String bcc: bccs) {
msg.addRecipient(RecipientType.BCC,
new
InternetAddress(bcc));
}
}
msg.setContent(multipart);
//save the changes of email first.
msg.saveChanges();
//to see what commands are used when sending a email, use session.setDebug(true)
//session.setDebug(true);
//send email
Transport.send(msg);
LOGGER.info(
"Send email success."
);
System.out.println(
"Send html email success."
);
}
catch
(NoSuchProviderException e) {
LOGGER.error(
"Email provider config error."
, e);
}
catch
(MessagingException e) {
LOGGER.error(
"Send email error."
, e);
}
}
/**
* Receive Email from POPServer. Use POP3 protocal by default. Thus,
* call this method, you need to provide a pop3 mail server address.
* @param emailAddress The email account in the POPServer.
* @param password The password of email address.
*/
public
static
void
receiveEmail(String host, String username, String password) {
//param check. If param is null, use the default configured value.
if
(host ==
null
) {
host = POP3Server;
}
if
(username ==
null
) {
username = POP3Username;
}
if
(password ==
null
) {
password = POP3Password;
}
Properties props = System.getProperties();
//MailAuthenticator authenticator = new MailAuthenticator(username, password);
try
{
Session session = Session.getDefaultInstance(props,
null
);
// Store store = session.getStore("imap");
Store store = session.getStore(
"pop3"
);
// Connect POPServer
store.connect(host, username, password);
Folder inbox = store.getFolder(
"INBOX"
);
if
(inbox ==
null
) {
throw
new
RuntimeException(
"No inbox existed."
);
}
// Open the INBOX with READ_ONLY mode and start to read all emails.
inbox.open(Folder.READ_ONLY);
System.out.println(
"TOTAL EMAIL:"
+ inbox.getMessageCount());
Message[] messages = inbox.getMessages();
SimpleDateFormat sdf =
new
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss"
);
for
(
int
i =
0
; i < messages.length; i++) {
Message msg = messages[i];
String from = InternetAddress.toString(msg.getFrom());
String replyTo = InternetAddress.toString(msg.getReplyTo());
String to = InternetAddress.toString(
msg.getRecipients(Message.RecipientType.TO));
String subject = msg.getSubject();
Date sent = msg.getSentDate();
Date ress = msg.getReceivedDate();
String type = msg.getContentType();
System.out.println((i +
1
) +
".---------------------------------------------"
);
System.out.println(
"From:"
+ mimeDecodeString(from));
System.out.println(
"Reply To:"
+ mimeDecodeString(replyTo));
System.out.println(
"To:"
+ mimeDecodeString(to));
System.out.println(
"Subject:"
+ mimeDecodeString(subject));
System.out.println(
"Content-type:"
+ type);
if
(sent !=
null
) {
System.out.println(
"Sent Date:"
+ sdf.format(sent));
}
if
(ress !=
null
) {
System.out.println(
"Receive Date:"
+ sdf.format(ress));
}
// //Get message headers.
// @SuppressWarnings("rawtypes")
// Enumeration headers = msg.getAllHeaders();
// while (headers.hasMoreElements()) {
// Header h = (Header) headers.nextElement();
// String name = h.getName();
// String val = h.getValue();
// System.out.println(name + ": " + val);
// }
// //get the email content.
// Object content = msg.getContent();
// System.out.println(content);
// //print content
// Reader reader = new InputStreamReader(
// messages[i].getInputStream());
// int a = 0;
// while ((a = reader.read()) != -1) {
// System.out.print((char) a);
// }
}
// close connection. param false represents do not delete messaegs on server.
inbox.close(
false
);
store.close();
// } catch(IOException e) {
// LOGGER.error("IOException caught while printing the email content", e);
}
catch
(MessagingException e) {
LOGGER.error(
"MessagingException caught when use message object"
, e);
}
}
/**
* For receiving an email, the sender, receiver, reply-to and subject may
* be messy code. The default encoding of HTTP is ISO8859-1, In this situation,
* use MimeUtility.decodeTex() to convert these information to GBK encoding.
* @param res The String to be decoded.
* @return A decoded String.
*/
private
static
String mimeDecodeString(String res) {
if
(res !=
null
) {
String from = res.trim();
try
{
if
(from.startsWith(
"=?GB"
) || from.startsWith(
"=?gb"
)
|| from.startsWith(
"=?UTF"
) || from.startsWith(
"=?utf"
)) {
from = MimeUtility.decodeText(from);
}
}
catch
(Exception e) {
LOGGER.error(
"Decode string error. Origin string is: "
+ res, e);
}
return
from;
}
return
null
;
}
}
|