Java Web 之 JavaMail
本文包括:
1、名词解释
2、邮件收发过程
3、JavaMail 知识概要
4、发送一封符合 MIME 协议的 JavaMail
5、总结
6、MX记录与A记录
1、名词解释
-
邮件服务器:
-
类似于web服务器(如Tomcat)、数据库服务器(如MySql),把一台邮件服务器端软件放在网络上,即可供广大网络用户使用。
-
类似于邮局,用户发邮件时,邮件服务器处理,再投递给相应的邮箱地址。
-
比如有sina、sohu、163、qq等等邮件服务器。
-
-
电子邮箱:邮件服务器中的账户,服务器会为每个邮箱账户分配地址和空间。
-
邮件收发协议:
-
SMTP(发送邮件协议,默认端口25)
-
POP3(收取邮件协议,默认端口110,不能在线操作)
-
IMAP(收取邮件协议,默认端口143,运行在TCP/IP协议之上,与POP3的主要区别:可以在线操作,用户可以不用把所有的邮件全部下载,可以通过客户端直接对服务器上的邮件进行操作)
-
> IMAP是什么?
>
> IMAP,即Internet Message Access Protocol(互联网邮件访问协议),您可以通过这种协议从邮件服务器上获取邮件的信息、下载邮件等。IMAP与POP类似,都是一种邮件获取协议。
> IMAP和POP有什么区别?
>
> POP允许电子邮件客户端下载服务器上的邮件,但是您在电子邮件客户端的操作(如:移动邮件、标记已读等),这是不会反馈到服务器上的,比如:您通过电子邮件客户端收取了QQ邮箱中的3封邮件并移动到了其他文件夹,这些移动动作是不会反馈到服务器上的,也就是说,QQ邮箱服务器上的这些邮件是没有同时被移动的 。但是IMAP就不同了,电子邮件客户端的操作都会反馈到服务器上,您对邮件进行的操作(如:移动邮件、标记已读等),服务器上的邮件也会做相应的动作。也就是说,IMAP是“双向”的。
>
> 同时,IMAP可以只下载邮件的主题,只有当您真正需要的时候,才会下载邮件的所有内容。
2、邮件收发过程
-
邮件收发过程图解:
邮件收发过程图解 -
手动收发邮件:
-
在sina和sohu 分别注册两个邮箱账户
sina: yuyang94895@sina.com / 1qaz2wsx
sohu: yuyang94895@sohu.com /1qaz2wsx
-
用sina向sohu发送一封邮件,通过telnet(socket程序) 手动根据smtp协议发送邮件
-
安装telnet
-
使用SMTP协议发送邮件,以新浪为例(以下命令需要逐行输入):
连接新浪smtp服务器 : telnet stmp.sina.com 25 ehlo xxx ----- SMTP 1代用的是helo,SMTP 2代用的是ehlo auth login ----- 注意:必须使用base64编码后的用户名和密码 eXV5YW5nOTQ4OTU= ----- 经过base64编码后的用户名 MXFhejJ3c3g= -----经过base64编码后的密码 mail from:<yuyang94895@sina.com> ----- 发件人 rcpt to:<yuyang94895@sohu.com> ----- 收件人 data ------ 邮件正文 from:<yuyang94895@sina.com> to:<yuyang94895@sohu.com> subject:第一封0713测试邮件 这是一封测试邮件,哈哈~~~~ . quit ----- 退出客户端
RFC822文档规定了如何编写一封简单邮件(邮件正文):
- 邮件头
- from字段(发件人)
- to字段(收件人)
- subject字段(邮件标题)
- cc字段(抄送) / bcc字段(暗送)
- 邮件体
- 邮件内容
抄送:A 发给B ,选择抄送给C , B可以看见邮件抄送给 C (邮件是给B的,需要让C知道B已经收到邮件)
暗送:A 发给B,选择暗送给C ,B可以看见邮件,但是不能看到邮件发给C (邮件给B,也想给C一份,但不想让B知道)
- 邮件头
-
除了使用网络邮件服务器之外,还可以使用本地邮件服务器,如易邮,首先安装易邮:
-
工具 --- 服务器设置 ---- 修改单域名,如:estore.com
-
账户 --- 新建账户 aaa/111 和 bbb/111
-
用aaa@estore.com 向 bbb@estore.com 发送一封邮件
-
-
smtp连接易邮发件,即以易邮本地服务器为例:
连接易邮smtp服务器:telnet localhost 25 ehlo xxx ----- SMTP2代 auth login ----- 发邮件时,发给服务器用户名和密码必须使用base64编码 YWFh ----- aaa经过base64编码后的字符 MTEx ----- 111经过base64编码后的字符 mail from:<aaa@estore.com> ----- 发件人 rcpt to:<bbb@estore.com> ----- 收件人 data ------ 邮件内容 from:<aaa@estore.com> to:<bbb@estore.com> subject:第一封0713测试邮件 这是一封测试邮件,哈哈~~~~ . quit ----- 退出客户端
-
pop3连接易邮收取邮件
连接易邮pop3服务器 : telnet localhost 110 user bbb pass 111 stat ----- 返回邮箱的统计信息 list 邮件号 ------ 邮件信息 retr 邮件号 ------ 收取邮件内容 quit
putty:
telnet 在 win7 环境下输入中文显示乱码,因 telnet 客户端本身问题,无法解决。
可选择 putty 代替,putty 可以解决乱码问题。
putty 模拟客户端,采用多种连接协议连接服务器 ------ 企业使用远程连接操作 linux
对 putty 设置:
window --- Translation (Use font encoding) -------- font encoding就是系统默认编码集 gbk ,可以将字符集设为 utf-8 , 但对于 windows 不需要,对于 linux才需要
window --- appearance ---- change 把字体改为支持中文的字体(如新宋体),再把字符集改为支持中文的字符集,如 gb2312
Session 中对 ip port 和协议进行配置
-
-
冒名邮件问题:
-
RFC822 文档规定一封简单邮件如何编写,但本身存在漏洞:
-
发邮件过程中 mail from 字段 和 RFC822 文档中 from 字段不同会出现什么问题? 冒名邮件问题
-
对方收取邮件时,只能看到 from 字段内容,无法得知 mail from 地址,这样就可以随意更改。
-
3、JavaMail 知识概要
-
JavaMail 是一套邮件收发程序 API,编写 JavaMail 程序就是编写邮件客户端程序(类似于 outlook、foxmail 等邮件客户端)。
-
JavaMail 开发需要类库 javamail 的API,还需要 Java Activation Framework (jaf) ,JavaMail 依赖于 jaf ,但在 MyEclipse 10 的环境下,不需要弄 jaf ,详情见下一点。
-
导入 jar 包
mail.jar ( JDK6.0 以后官方 API 自带)
- JDK6.0 以后开发,不需要导入 mail.jar 。
- JDK5.0(包括) 之前开发,需要导入 mail.jar ,如果出现了问题,解决措施见下方。
如果出现了如下问题:
java.lang.NoClassDefFoundError:
com/sun/mail/util/LineInputStream 异常
原因: MyEclipse 新建工程自带的 javaee.jar 提供的 javaMail API 与 JavaMail的 jar 包发生冲突
解决 :删除 javaee.jar 里面的 mail 目录和 activation 目录(如果想一劳永逸,去 javaee.jar 所在的硬盘位置把这两个目录删掉。
-
JavaMail 邮件收发四个核心类
-
Message 邮件
-
Session 连接会话
-
Transport 发送邮件
-
Store 收取邮件
-
-
发送邮件编程
-
创建与邮件发送服务器连接Session
-
编写邮件内容 Message
符合邮件内容格式RFC822文档 setFrom , setRecipients , setSubject , setText
-
使用 Transport 工具类 发送邮件
-
demo :
public void demo2() throws AddressException, MessagingException { // 步骤一 创建与邮件服务器连接会话 Properties properties = new Properties();// 配置与服务器连接参数 // 设置properties 属性 properties.put("mail.transport.protocol", "smtp"); properties.put("mail.smtp.host", "localhost"); // localhost是易邮邮件服务器的本地主机 properties.put("mail.smtp.auth", "true");// 连接认证 properties.put("mail.debug", "true");// 在控制台显示连接日志信息 Session session = Session.getInstance(properties);// 与邮件服务器连接会话 // 步骤二 编写Message MimeMessage message = new MimeMessage(session);// 代表一封邮件 // from字段 message.setFrom(new InternetAddress("aaa@estore.com")); // to 字段 message.setRecipients(Message.RecipientType.TO, "bbb@estore.com"); // subject字段 message.setSubject("javamail发送简单邮件"); // 邮件正文内容 message.setText("使用javamail 可以发送简单邮件 ..."); // 步骤三 使用Transport发送邮件 Transport transport = session.getTransport(); // 发邮件前进行身份校验 transport.connect("aaa", "111"); transport.sendMessage(message, message.getAllRecipients()); }
-
4、发送一封符合 MIME 协议的 JavaMail
-
MIME协议的引入:
RFC822 文档只定义简单邮件格式,没有定义复杂邮件如何编写(前文的demo中邮件只包含简单的文本信息)。而 MIME 协议是 RFC822 文档的升级补充,完全支持 RFC822 文档。
-
MIME协议的用法:
对于一封复杂邮件,如果包含了多个不同的数据(例如图片、附件等等),MIME 协议规定:要将邮件体分成多个部分,每个部分使用分隔线进行分隔,并使用 Content-Type 头字段对数据的类型以及多个数据之间的关系进行描述。
-
Content-Type 头字段(每段数据都需要)
-
数据类型:
-
格式:主类型/子类型。
-
主类型包括:text、image、audio、video、application、message等。
-
每个主类型下面又有多个子类型,例如text主类型包含plain、html、css、xml等子类型。
-
-
数据的关系:
-
multipart/mixed 用于携带附件
-
multipart/related 内嵌图片,音乐
-
multipart/alternative 防止兼容问题
发送简历时,将简历文件与邮件正文关系设置为alternative,当邮件客户端如果支持简历格式,简历会显示在正文中,如果不支持简历格式,简历会以附件携带
-
-
-
Content-Disposition 头字段(附件才需要)
该头字段用于指定邮件阅读程序处理数据内容的方式。如果发送复杂邮件时需要携带附件,必须在附件部分,设置 Content-Disposition 头字段,它的值应该为 attachment 。
Content-Dispostion : attachment;filename="1.bmp"
-
Content-ID 头字段(内嵌图片、音乐才需要)
该头字段为 "multipart/related" 组合消息中的内嵌资源指定一个唯一标识号。在邮件正文中通过
![](cid:唯一标识)
引用内嵌图片和资源。
-
图解:
-
JavaMail API中与MIME协议相关的类:
-
MimeMessage 类 ----- 代表整封邮件,包括MIME对象的消息头与MimeMultipart对象
-
MimeBodyPart 类 ---- 代表邮件中一个MIME消息
-
MimeMultiPart 类 ---- 代表一个或多个MIME消息组合而成的组合MIME消息
-
-
JavaMail 怎样描述一封复杂邮件?
-
判断邮件由几个部分组成,为每个部分设计 BodyPart ( MIME 消息)。
-
将所有 BodyPart 组合起来变为 Multipart (注意:只有 BodyPart 与 BodyPart 之间才可以组合,MultiPart 不可以直接和 BodyPart 组合,需要将 MultiPart 转换为 BodyPart 后,再与 BodyPart 组合)。
-
将最后合成的 MultiPart 交给 MimeMessage 对象。
-
-
内嵌图片的邮件 demo :
// 内嵌图片邮件 public void demo3() throws Exception { // 发送邮件需要三个步骤 // 步骤一:创建Session Properties properties = new Properties(); properties.put("mail.transport.protocol", "smtp"); properties.put("mail.smtp.host", "localhost"); properties.put("mail.smtp.auth", "true");// 连接认证 properties.put("mail.debug", "true");// 在控制台显示连接日志信息 Session session = Session.getInstance(properties);// 与邮件服务器连接会话 // 步骤二:创建Message MimeMessage message = new MimeMessage(session); // 设置邮件头(简单邮件和复杂邮件相同 message.setFrom(new InternetAddress("aaa@estore.com")); message.setRecipients(Message.RecipientType.TO, "bbb@estore.com"); message.setSubject("javamail发送内嵌图片邮件"); // 设置邮件体(简单邮件和复杂邮件区别 就在于邮件体) MimeBodyPart pic = new MimeBodyPart();// 图片 // 链接数据文件 pic.setDataHandler(new DataHandler(new FileDataSource("beauty.jpg"))); // 也可以用下面三行代码代替“链接数据文件”的操作,但明显更繁琐 // DataSource dataSource = new FileDataSource("beauty.jpg"); // DataHandler dataHandler = new DataHandler(dataSource); // pic.setDataHandler(dataHandler); // 设置一个唯一标识(用于在正文中引入) pic.setContentID("mypic"); MimeBodyPart content = new MimeBodyPart(); // 邮件正文 content.setContent("<h1>美女图片</h1>![](cid:mypic)", "text/html;charset=utf-8"); // 将两个BodyPart整合 MimeMultipart mimeMultipart = new MimeMultipart(); mimeMultipart.addBodyPart(pic); mimeMultipart.addBodyPart(content); // 设置关系 mimeMultipart.setSubType("related"); // 将MimeMultiPart发给MimeMessage message.setContent(mimeMultipart); // message.writeTo(System.out); // 将该邮件所包含的信息打印到控制台 // 步骤三:Transport发送邮件 Transport transport = session.getTransport(); transport.connect("aaa", "111"); transport.sendMessage(message, message.getAllRecipients()); }
-
包含附件的邮件 demo :
// 携带附件邮件 public void demo4() throws Exception { // 步骤一:创建Session Properties properties = new Properties(); properties.put("mail.transport.protocol", "smtp"); properties.put("mail.smtp.host", "localhost"); properties.put("mail.smtp.auth", "true");// 连接认证 properties.put("mail.debug", "true");// 在控制台显示连接日志信息 Session session = Session.getInstance(properties);// 与邮件服务器连接会话 // 步骤二:创建Message MimeMessage message = new MimeMessage(session); // 设置邮件头(简单邮件和复杂邮件相同 message.setFrom(new InternetAddress("aaa@estore.com")); message.setRecipients(Message.RecipientType.TO, "bbb@estore.com"); message.setSubject("javamail发送携带附件邮件"); // 设置邮件体 MimeBodyPart attachment = new MimeBodyPart(); // 链接数据文件 attachment.setDataHandler(new DataHandler(new FileDataSource( "大嘴巴 - maybe的机率.mp3"))); // 因中文附件名编码的问题,会产生乱码,必须使用 JavaMail 提供的工具类 MimeUtility 来包装中文字符 // 设置 filename 可自动生成: Content-Disposition:attachment;filename=xxx attachment.setFileName(MimeUtility.encodeText("大嘴巴 - maybe的机率.mp3")); MimeBodyPart content = new MimeBodyPart(); content.setContent("<h1>附件是首好听的歌曲!</h1>", "text/html;charset=utf-8"); MimeMultipart mimeMultipart = new MimeMultipart(); mimeMultipart.addBodyPart(attachment); mimeMultipart.addBodyPart(content); mimeMultipart.setSubType("mixed"); message.setContent(mimeMultipart); // 步骤三:Transport发送邮件 Transport transport = session.getTransport(); transport.connect("aaa", "111"); transport.sendMessage(message, message.getAllRecipients()); }
-
既内嵌图片又包含附件的邮件 demo (工具类 MailUtils.java 见 第12 点):
// 编写最复杂一封邮件,既要内嵌图片,也要携带附件 public void demo5() throws Exception { // 步骤一 创建Session Session session = MailUtils.createSession(); // 步骤二 编写邮件Message MimeMessage message = new MimeMessage(session); // 设置邮件头(简单邮件和复杂邮件相同 message.setFrom(new InternetAddress("aaa@estore.com")); message.setRecipients(Message.RecipientType.TO, "bbb@estore.com"); message.setSubject("javamail发送最复杂邮件"); // 设置邮件体 MimeBodyPart pic = new MimeBodyPart(); pic.setDataHandler(new DataHandler(new FileDataSource("beauty.jpg"))); pic.setContentID("myimg");// 内嵌图片唯一标识 MimeBodyPart attachment = new MimeBodyPart(); attachment.setDataHandler(new DataHandler(new FileDataSource( "大嘴巴 - maybe的机率.mp3"))); attachment.setFileName(MimeUtility.encodeText("大嘴巴 - maybe的机率.mp3"));// 附件解决中文路乱码 MimeBodyPart content = new MimeBodyPart(); content.setContent("<h1>最复杂邮件,有图片,有附件</h1>![](cid:myimg)", "text/html;charset=utf-8"); // 整合 MimeMultipart mp1 = new MimeMultipart(); mp1.addBodyPart(pic); mp1.addBodyPart(content); mp1.setSubType("related"); MimeBodyPart temp = new MimeBodyPart();// 将multipart转换bodypart可以和其它bodypart一起合并 temp.setContent(mp1); MimeMultipart mp2 = new MimeMultipart(); mp2.addBodyPart(attachment); mp2.addBodyPart(temp); mp2.setSubType("mixed"); message.setContent(mp2); // 步骤三 发送邮件 Transport MailUtils.sendMail(session, message); }
-
MailUtils.java (抽出公共代码,编写成为一个工具类,很实用的思想):
public class MailUtils { private static String targetSMTP = "localhost";// SMTP服务器地址 private static String user = "aaa"; // 发件账户 private static String pass = "111"; // 发件密码 // 创建邮件服务器链接会话 public static Session createSession() { Properties properties = new Properties(); properties.put("mail.transport.protocol", "smtp"); properties.put("mail.smtp.host", targetSMTP); properties.put("mail.smtp.auth", "true");// 连接认证 properties.put("mail.debug", "true");// 在控制台显示连接日志信息 Session session = Session.getInstance(properties);// 与邮件服务器连接会话 return session; } // 发送邮件 public static void sendMail(Session session, Message message) throws Exception { Transport transport = session.getTransport(); transport.connect(user, pass); transport.sendMessage(message, message.getAllRecipients()); } }
5、总结
-
至此,JavaMail 的基础知识就差不多写完了,通过学习 JavaMail ,可以了解电子邮件(E-Mail)的基本知识。
-
在使用 telnet 的时候,我想用我的 QQ 邮箱发邮件,测试一下能否成功,然而 QQ 邮箱的安全权限比较高,在第三方登陆的时候需要开通 POP3/SMTP 服务,而且每次第三方登陆的时候还需要一个验证码,这个验证码得去 QQ 邮箱(网页版)那里生成,而生成一次就需要验证密保,总之非常麻烦。
-
而用163邮箱的时候就不需要验证码,但是 SSL 加密时(端口465/587),总是会出现各种错误,不用 SSL 加密(端口25)就可以正常发送,
-
之前说过了 telnet 不能解决中文乱码问题,putty 可以,但是这两个工具的命令行终端都十分丑陋,而且打错了字后想要退格(Backspace),命令行终端上仍然显示之前字符,只是光标退格了而已,不知道怎么解决。顺便求推荐好用的终端工具 :)
-
JavaMail 官网介绍: http://www.oracle.com/technetwork/java/javamail/index.html
-
JavaMail 官方文档:http://www.oracle.com/technetwork/java/providers-150193.pdf
6、MX 记录与 A 记录
-
MX 与 A 记录(配置邮件服务器相关的信息)
-
MX 记录:在 DNS 中进行注册,目的让其它服务器找到该服务器地址。
-
查询 MX 记录
> set type=mx > sina.com 非权威应答: sina.com MX preference = 5, mail exchanger = freemx1.sinamail.sina.com.cn sina.com MX preference = 10, mail exchanger = freemx3.sinamail.sina.com.c n sina.com MX preference = 10, mail exchanger = freemx2.sinamail.sina.com.c n
-
从上面的非权威应答( Non-authoritative answer )中可以看到有3个 mx 记录,比如:
freemx.sinamail.sina.com.cn
然而它与
smtp.sina.com
有什么区别呢?
-
freemx.sinamail.sina.com.cn DNS 中查询得到 MX 记录 (发信不需要登陆)
-
smtp.sina.com 从网站上获得地址 (发信需要先登陆)
-
-
用处:但我们登陆 sina 邮箱,向 sohu 邮箱发邮件时,从图中可以看到,我们同时也要访问 sohu 的 smtp 服务器,然而我们是怎么知道 sohu 的 smtp 服务器的呢?这肯定是网络中有个东西在背后帮我们完成了,这个东西就是 MX 记录。
-
-
A 记录:当别人以邮箱服务器身份连接不要认证的 mx 记录时候,需要从 DNS 中获得对方 A 记录,与来访者 IP 进行比较,进行身份验证。
-
查询 A 记录
set type=a sohu.com Non-authoritative answer: Name: sohu.com Addresses: 61.135.181.175, 61.135.181.176
-
-
图解:
-
总结:自己搭建一个邮件服务器,千万不要忘记去 DNS 上注册 MX 记录和 A 记录。