原 Java发送邮件工具类(可发送匿名邮件)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介:

    为了不想到处去下载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:\\根目录下:

    180251_8tQ8_1434710.jpg

    直接在MailUtil类中加入main方法:

175037_iSrw_1434710.jpg

    Ctrl+F11执行这个程序,首先可以在控制台看见以下内容(注意执行的程序和发送邮件的时间):

174237_kd0T_1434710.jpg

    然后再进入邮箱,看到收到的邮件:

175306_ZwuT_1434710.jpg

    打开邮件后,会看到以下内容(注意标题,发件人,收件人和邮件内容,发送时间):

181351_PB8i_1434710.jpg

    在看看附件的内容(注意附件名字):

175920_ZHBP_1434710.jpg

    匿名邮件的发送也类似,我已经测试过了,这里不再贴出。好了,最后再贴出工具类的完整代码:

?
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 ;
     }
}


目录
相关文章
|
29天前
|
缓存 前端开发 Java
【前端学java】java基础巩固复习巩固语法练习-工具类的封装(14)
【8月更文挑战第10天】java基础巩固,工具类的封装
17 1
【前端学java】java基础巩固复习巩固语法练习-工具类的封装(14)
|
16天前
|
Java
Java应用结构规范问题之在UnitConvertUtils工具类将千米转换为米的问题如何解决
Java应用结构规范问题之在UnitConvertUtils工具类将千米转换为米的问题如何解决
|
28天前
|
存储 设计模式 安全
Java GenericObjectPool 对象池化技术--SpringBoot sftp 连接池工具类
Java GenericObjectPool 对象池化技术--SpringBoot sftp 连接池工具类
17 0
|
2月前
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
43 1
|
2月前
|
安全 Java 开发者
Java中的并发工具类与线程安全实现
Java中的并发工具类与线程安全实现
|
3月前
|
设计模式 缓存 算法
编写高效的Java工具类:实用技巧与设计模式
编写高效的Java工具类:实用技巧与设计模式
|
2月前
|
并行计算 Java API
Java中的并发工具类详解
Java中的并发工具类详解
|
2月前
|
设计模式 缓存 算法
编写高效的Java工具类:实用技巧与设计模式
编写高效的Java工具类:实用技巧与设计模式
|
3月前
|
Java
JAVA工具类匹配重复或者连续的字符和符号
JAVA工具类匹配重复或者连续的字符和符号
|
3月前
|
算法 Java
基于java雪花算法工具类SnowflakeIdUtils-来自chatGPT
基于java雪花算法工具类SnowflakeIdUtils-来自chatGPT