一、Java邮件开发介绍
为什么要学习javamail开发
•现在很多WEB应用在开发时都需要集成邮件发送功能,例如:
•给新注册的用户自动发送一封包含其注册信息的欢迎E-Mail.
•给过生日的注册会员自动发送一封表示祝贺的E-Mail .
•将网站的最新活动信息通过E-Mail发送给所有的注册会员.
•等等 …...
•这些功能的实现都需要软件开发人员在开发WEB应用中编写相应的邮件处理程序.
二、邮件开发涉到的一些基本概念
1、邮件服务器和电子邮箱
邮件服务器:
•要在Internet上提供电子邮件功能,必须有专门的电子邮件服务器.例如现在Internet很多提供邮件服务的厂商:sina、sohu、163等等他们都有自己的邮件服务器.这些服务器类似于现实生活中的邮局,它主要负责接收用户投递过来的邮件,并把邮件投递到邮件接收者的电子邮箱中.
电子邮箱:
•电子邮箱(E-mail地址)的获得需要在邮件服务器上进行申请 ,确切地说,电子邮箱其实就是用户在邮件服务器上申请的一个帐户.用户在邮件服务器上申请了一个帐号后,邮件服务器就会为这个帐号分配一定的空间,用户从而可以使用这个帐号以及空间,发送电子邮件和保存别人发送过来的电子邮件.
2、邮件传输协议
SMTP协议
•用户连上邮件服务器后,要想给它发送一封电子邮件,需要遵循一定的通迅规则,SMTP协议就是用于定义这种通讯规则的.
•因而,通常我们也把处理用户smtp请求(邮件发送请求)的邮件服务器称之为SMTP服务器.(25)
POP3协议
•同样,用户若想从邮件服务器管理的电子邮箱中接收一封电子邮件的话,他连上邮件服务器后,也需要遵循一定的通迅格式,POP3协议用于定义这种通讯格式.
•因而,通常我们也把处理用户pop3请求(邮件接收请求)的邮件服务器称之为POP3服务器.(110)
3、电子邮件的传输过程
下图用于演示lisi@sina.com与wangwu@sohu.com帐户相互发送邮件的过程.
三、手工演示电子邮件的发送
准备实验环境
•提前在sohu和sina上注册一个免费帐号
•获取smtp和pop3服务器的名称
•Sina或sohu会在其帮助中心里提示管理当前注册帐号的邮件服务器的主机名.
•编写base64编码程序对用户名和密码进行编码.
•手工使用SMTP协议发送电子邮件
整个流程如下:
telnet localhost 25 //telnet 可连接互联网任意一台主机
ehlo flx
auth login
YWFh
MTIz
mail from: <aaa@flx.com>
rcpt to: <bbb@flx.com>
data
from: <jjs@taiwan.com>
to: <bbb@flx.com>
subject: test
美令,咱们分手吧
中正
.
quit
telnet localhost 110
user aaa
pass 123
stat
list
retr 2
quit
四、编写Socket程序发送电子邮件
public class Demo1 {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost",25);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
OutputStream out = socket.getOutputStream();
System.out.println(br.readLine());
out.write("ehlo flx\r\n".getBytes());
System.out.println(br.readLine());
System.out.println(br.readLine());
out.write("auth login\r\n".getBytes());
System.out.println(br.readLine());
out.write("YWFh\r\n".getBytes());
System.out.println(br.readLine());
out.write("MTIz\r\n".getBytes());
System.out.println(br.readLine());
out.write("mail from: <aaa@flx.com>\r\n".getBytes());
System.out.println(br.readLine());
out.write("rcpt to: <bbb@flx.com>\r\n".getBytes());
System.out.println(br.readLine());
out.write("data\r\n".getBytes());
System.out.println(br.readLine());
out.write("from:<aaa@flx.com>\r\nto:<bbb@flx.com>\r\nsubject:test\r\n".getBytes());
out.write(".\r\n".getBytes());
System.out.println(br.readLine());
out.write("quit\r\n".getBytes());
System.out.println(br.readLine());
br.close();
out.close();
socket.close();
}
}
五、创建邮件—— MIME协议
•MIME协议是对RFC822文档的升级和补充,它描述了如何生成一封复杂邮件.通常我们把MIME协议描述的邮件称之为MIME邮件,MIME协议描述的数据称之为MIME消息.
•对于一封复杂邮件,如果包含了多个不同的数据,MIME协议规定了要使用分隔线对多段数据进行分隔,并使用Content-Type头字段对数据的类型、以及多个数据之间的关系进行描述.
MIME协议常用头字段
Content-type:字段
•数据类型
•以"主类型/子类型"的形式出现,主类型有text、image、audio、video、application、message等,分别表示文本、图片、音频、视频、应用程序、组合结构、消息等.每个主类型下面都有多个子类型,例如text主类型包含plain、html、xml、css等子类型.
•数据的关系
•multipart/mixed、multipart/related、multipart/alternative
Content-Disposition头字段
•Content-Disposition头字段用于指定邮件阅读程序处理数据内容的方式,有inline和attachment两种标准方式,inline表示直接处理,而attachment表示当作附件处理.如果将Content-Disposition设置为attachment,在其后还可以指定filename属性,如下所示:
上面的MIME头字段表示MIME消息体的内容为邮件附件,附件名"1.bmp"
Content-Disposition: attachment; filename="1.bmp"
Content-ID头字段
•Content-ID头字段用于为"multipart/related"组合消息中的内嵌资源指定一个唯一标识号,在HTML格式的正文中可以使用这个唯一标识号来引用该内嵌资源.例如,假设将一个表示内嵌图片的MIME消息的Content-ID头字段设置为如下形式:
那么,在HTML正文中就需要使用如下HTML语句来引用该图片资源:
注意,在引用Content-ID头字段标识的内嵌资源时,要在资源的唯一标识号前面加上"cid:",以说明要采用唯一标识号对资源进行引用.
Content-ID: it315logo_gif
<img src="cid:it315logo_gif">
MIME协议例:
//创建简单邮件
public class SimpleMail {
public static void main(String[] args) throws Exception {
Session session = Session.getDefaultInstance(new Properties());
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("aaa@flx.com"));
message.setRecipient(Message.RecipientType.TO, new InternetAddress("bbb@flx.com"));
message.setSubject("test");
message.setContent("aaaaaaaaaaaa", "text/html");
message.saveChanges();
message.writeTo(new FileOutputStream("c:\\1.eml"));
}
}
//创建带图片邮件
public class ImageMail {
public static void main(String[] args) throws Exception {
Session session = Session.getDefaultInstance(new Properties());
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("aaa@flx.com"));
message.setRecipient(Message.RecipientType.TO, new InternetAddress("bbb@flx.com"));
message.setSubject("test");
//创建邮件中的数据
//创建正文
MimeBodyPart text = new MimeBodyPart();
text.setContent("aaaa<br/><img src='cid:1.jpg'><br/>aaaaaaaa", "text/html");
//创建图片,不需要再setContent,setDataHandler可以感知类型
MimeBodyPart image = new MimeBodyPart();
DataHandler dh = new DataHandler(new FileDataSource("src/1.jpg"));//jaf技术,把文件作为流
image.setDataHandler(dh);
image.setContentID("1.jpg");
//描述数据之间的关系
MimeMultipart mm = new MimeMultipart();
mm.addBodyPart(text);
mm.addBodyPart(image);
mm.setSubType("related");
message.setContent(mm);
message.saveChanges();
message.writeTo(new FileOutputStream("c:\\1.eml"));
}
}
//创建带附件的邮件
public class AttachMail {
public static void main(String[] args) throws Exception {
Session session = Session.getDefaultInstance(new Properties());
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("aaa@flx.com"));
message.setRecipient(Message.RecipientType.TO, new InternetAddress("bbb@flx.com"));
message.setSubject("test");
//创建封装正文数据的bodypart
MimeBodyPart text = new MimeBodyPart();
text.setContent("aaaaaaaaaaaa", "text/html");
//创建封装附件数据的bodypart
MimeBodyPart attach = new MimeBodyPart();
DataHandler dh = new DataHandler(new FileDataSource("src/1.mp3"));
attach.setDataHandler(dh);
attach.setFileName(dh.getName()); //设置附件名
//mixed
MimeMultipart mm = new MimeMultipart();
mm.addBodyPart(text);
mm.addBodyPart(attach);
mm.setSubType("mixed");
message.setContent(mm);
message.saveChanges();
message.writeTo(new FileOutputStream("c:\\1.eml"));
}
}
//创建最复杂邮件
public class ComplexMail {
public static void main(String[] args) throws Exception {
//创建邮件
Session session = Session.getDefaultInstance(new Properties());
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("aaa@flx.com"));
message.setRecipient(Message.RecipientType.TO, new InternetAddress("bbb@flx.com"));
message.setSubject("测试");
//创建bodypart封装正文
MimeBodyPart text = new MimeBodyPart();
text.setContent("这是中文邮件a<img src='cid:1.jpg'>", "text/html;charset=UTF-8");
//创建bodypart封装图片
MimeBodyPart image = new MimeBodyPart();
image.setDataHandler(new DataHandler(new FileDataSource("src/1.jpg")));
image.setContentID("1.jpg");
//创建bodypart封装附件,解决乱码
MimeBodyPart attach = new MimeBodyPart();
DataHandler dh = new DataHandler(new FileDataSource("src/光辉岁月.mp3"));
attach.setDataHandler(dh);
attach.setFileName(MimeUtility.encodeText(dh.getName()));//content-disposition
//描述数据关系
MimeMultipart content = new MimeMultipart();
content.addBodyPart(text);
content.addBodyPart(image);
content.setSubType("related");
MimeBodyPart mbp = new MimeBodyPart();
mbp.setContent(content);
MimeMultipart mm = new MimeMultipart();
mm.addBodyPart(mbp);
mm.addBodyPart(attach);
mm.setSubType("mixed");
message.setContent(mm);
message.saveChanges();
message.writeTo(new FileOutputStream("c:\\1.eml"));
}
}
ps:MIME协议已经突破原来的邮件领域广泛用在数据描述协议中,简单的一堆数据,不同类型,传给对方.
六、发送邮件
JavaMail API按其功能划分通常可分为如下三大类:
创建和解析邮件内容的API :Message类是创建和解析邮件的核心API,它的实例对象代表一封电子邮件.
发送邮件的API:Transport类是发送邮件的核心API类,它的实例对象代表实现了某个邮件发送协议的邮件发送对象,例如SMTP协议.
接收邮件的API:Store类是接收邮件的核心API类,它的实例对象代表实现了某个邮件接收协议的邮件接收对象,例如POP3协议.
Session类
Session类用于定义整个应用程序所需的环境信息,以及收集客户端与邮件服务器建立网络连接的会话信息,如邮件服务器的主机名、端口号、采用的邮件发送和接收协议等.Session对象根据这些信息构建用于邮件收发的Transport和Store对象,以及为客户端创建Message对象时提供信息支持.
使用JavaMail发送一封简单的邮件:
•创建包含邮件服务器的网络连接信息的Session对象.
•创建代表邮件内容的Message对象.
•创建Transport对象、连接服务器、发送Message、关闭连接.
//发送邮件
public class SendMail {
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
prop.setProperty("mail.smtp.host", "localhost");
prop.setProperty("mail.transport.protocol", "smtp");
prop.setProperty("mail.smtp.auth", "true");
javax.mail.Session session = javax.mail.Session.getInstance(prop);
session.setDebug(true);//打印与服务器的交互过程
Message message = createMessage(session);
Transport ts = session.getTransport();
ts.connect("aaa", "123");
ts.sendMessage(message,message.getAllRecipients());//获取收件人
ts.close();
}
private static Message createMessage(Session session) throws Exception {
//创建邮件
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("aaa@flx.com"));
message.setRecipient(Message.RecipientType.TO, new InternetAddress("bbb@flx.com"));
message.setSubject("test");
message.setContent("aaaaaaaaaaaa", "text/html");
message.saveChanges();
return message;
}
}
七、WEB应用中集成邮件发送程序
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><title>注册页面</title></head>
<body>
<form action="${pageContext.request.contextPath }/servlet/RegisterServlet" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
电子邮箱:<input type="text" name="email"><br/>
<input type="submit" value="注册">
</form>
</body>
</html>
//用户注册时,发送邮件通知
public class RegisterServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)throws Exception {
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
User user = new User();
user.setEmail(email);
user.setPassword(password);
user.setUsername(username);
try{
//开启发送邮件线程,如果不开线程的话,如邮件有问题,后续代码无法执行
Thread t = new Thread(new SendMail(user));
t.start();
request.setAttribute("message", "注册成功!!");
}catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "注册失败!!");
}
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
public class SendMail implements Runnable {
private String host = "localhost";
private String email = "aaa@flx.com";
private String username = "aaa";
private String password = "123";
private User user;
public SendMail(User user) {
this.user = user;
}
public void run() {
try{
Thread.sleep(1000*30);
send(user);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
public void send(User user) throws Exception {
Properties prop = new Properties();
prop.setProperty("mail.host", host);
prop.setProperty("mail.transport.protocol", "smtp");
Session session = Session.getInstance(prop);
Message message = createmessage(session, user);
Transport ts = session.getTransport();
ts.connect(username, password);
ts.sendMessage(message, message.getAllRecipients());
ts.close();
}
public Message createmessage(Session session,User user) throws Exception{
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(email));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail()));
message.setSubject("xxx网站用户注册邮件");
String content = "恭喜您,注册成功,您注册的用户名:"+ user.getUsername()!!";
message.setContent(content, "text/html;charset=UTF-8");
message.saveChanges();
return message;
}
}
八、邮件群发
1、邮件群发首先要采用递归查询的方式抓取邮箱,比如首先抓取新浪首页,抓到邮箱后保存起来,然后抓取到的url也保存起来(称为新浪的二级网址),再挨个访问url去抓取邮箱,依次类推,为了防止内存溢出,要进行层次控制,还要进行排重避免抓取相同的网址.
2、群发邮件的时候要进行多线程处理,加快速度,如10万-20万开启一个线程,20万-30万开启一个线程...
3、如我们使用新浪的邮箱进行群发,那么新浪会控制一个邮箱每天最多可以发多少封来控制群发,后来新浪发展到控制一个ip每天可以发多少封,可以使用adsl拨号(每次是一个ip)或者代理ip,但是都繁琐活不稳定,那么就需要自己搭建邮件服务器(在DNS注册,注册MX(为了能被别人找到)和A记录(为了和巨头邮件服务器连接时不需要账号密码)),因为我们的服务器还是要和其它巨头服务器相连,他们还是会禁掉我们的邮件服务器,他们巨头之间有邮件服务器白名单,一般小型公司很难加入,那么要实现群发,只能给巨头交钱.