摘要:
JavaMail API是读取、撰写、发送电子信息的可选包。我们可用它来建立如Eudora、Foxmail、MS Outlook Express一般的邮件用户代理程序(Mail User Agent,简称MUA)。让我们看看JavaMail API是如何提供信息访问功能的吧!
本教程主要以实践结合理论的方式来编写,内容通俗易懂!
1、安装JavaMail
为了使用JavaMail API,需要从http://java.sun.com/products/javamail/downloads/index.html下载文件名格式 为javamail-[version].zip的文件(这个文件中包括了JavaMail实现),并将其中的mail.jar文件添加到 CLASSPATH中。这个实现提供了对SMTP、IMAP4、POP3的支持。
注意:我们还需要安装JavaBeans Activation Framework,因为这个框架是JavaMail API所需要的。如果我们使用J2EE的话,那么我们并无需单独下载JavaMail,因为它存在于J2EE.jar中,只需将J2EE.jar加入到 CLASSPATH即可。 (建议独立下载mail.jar,因为j2ee.jar里面的mail.jar缺少某些文件)
2.安装JavaBeans Activation Framework (JAF)
从 http://www.oracle.com/technetwork/java/jaf11-139815.html下载JavaBeans Activation Framework,并将其添加到CLASSPATH中。此框架增加了对任何数据块的分类、以及对它们的处理的特性。这些特性是JavaMail API需要的。虽然听起来这些特性非常模糊,但是它对于我们的JavaMail API来说只是提供了基本的MIME类型支持。
到此为止,我们应当把mail.jar和activation.jar都添加到了CLASSPATH中。
当然如果从方便的角度讲,直接把这两个Jar文件复制到JRE目录的lib/ext目录中也可以。
注:如果用Eclipse的话,新建项目->右击选择build path->add external archives 选择导入上面个包就可以了
【Java mail 入门教程】第二讲 hello world 入门程序(发送邮件)
第二讲主要讲述如何运用java.mail提供的函数库写一个hello world 程序,功能是能够发送一封内容为hello world,主题为hello world的邮件。
现实中我们要发送一封邮件的时候,会进行以下步骤:
1、 打开邮件登陆网站 (建立会话Session)
2、 填写用户名和密码登陆 (username, password)
3、 选择写信,填写收件人 (Recipient)
4、 填写主题和正文 (Subject, Text)
5、 发送 (sendMessage)
看了以上步骤基本上清楚写一个简单的邮件发送需要那些操作了,如果还不清楚的不用急,我在代码后面会详细地介绍用到的每一个类。
废话少说了,先上个程序让大家看看,最好自己先看代码猜想一下每行代码的意思和能亲自上机操作
运行前请检查邮件SMTP服务有没有打开,如果没有打开,那么就不支持通过SMTP发送邮件!!!
import java.util.Properties; import javax.mail.Address; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage;
public class TestMail {
public static void main(String[] args) throws Exception { Properties props = new Properties(); props.put("mail.smtp.host", "smtp.qq.com"); props.put("mail.smtp.auth", "true"); //基本的邮件会话 Session session = Session.getInstance(props); //构造信息体 MimeMessage message = new MimeMessage(session); //发件地址 Address address = new InternetAddress("123456789@qq.com"); message.setFrom(address); //收件地址 Address toAddress = new InternetAddress("123456789@qq.com"); message.setRecipient(MimeMessage.RecipientType.TO, toAddress);
//主题 message.setSubject("Hello world"); //正文 message.setText("Hello world"); message.saveChanges(); // implicit with send() //Exception in thread "main" javax.mail.NoSuchProviderException: smtp session.setDebug(true); Transport transport = session.getTransport("smtp"); transport.connect("smtp.qq.com", "123456789@qq.com", "****"); //发送 transport.sendMessage(message, message.getAllRecipients()); transport.close(); } } |
如果运行时出现
Exception in thread "main" javax.mail.NoSuchProviderException: smtp
请检查classpath里面有没有mail.jar和activation.jar这两个包,同时检查mail.jar包里面有没有META- INF/javamail.default.providers对应的smtp协议配置(没有mail.jar和activation.jar这两个包请 查看第一讲的环境配置)
1、 Properties 类表示了一个持久的属性集。Properties在很多场合也会用到,例如,XML的配置文件里,一个键值对应一个字符串。
mail.smtp.host-> smtp.qq.com
mail.smtp.auth-> true
2、 Session类定义了基本的邮件会话。就像Http会话那样,我们进行收发邮件的工作都是基于这个会话的。Session对象利用了 java.util.Properties对象获得了邮件服务器、用户名、密码信息和整个应用程序都要使用到的共享信息。 Session类的构造方法是私有的,所以我们可以使用Session类提供的getDefaultInstance()这个静态工厂方法获得一个默认的 Session对象,这样就建立了一个链接邮箱服务器的会话。
3、 MimeMessage类是为了构造信息体,里面提供的信息包括收件人地址、发件人地址、邮件主题、邮件内容等,MimeMessage类实现了抽象类 Message,该类是使用MIME类型、MIME信息头的邮箱信息。信息头只能使用US-ASCII字符,而非ASCII字符将通过编码转换为 ASCII的方式使用。
建立MimeMessage后就可以配置收件人地址、发件人地址…………
4、 Address邮件地址类,像Message一样,Address类也是一个抽象类,所以我们将使用javax.mail.internet.InternetAddress这个子类。通过传入代表邮件地址的字符串,我们可以建立一个邮件地址类
在发送信息时,Transport类将被用到。这个类实现了发送信息的协议(通称为SMTP),当然,方法是多样的。我们也可由Session获得 相应协议对应的Transport实例。并通过传递用户名、密码、邮件服务器主机名等参数建立与邮件服务器的连接,并使用sendMessage()方法 将信息发送,最后关闭连接:
message.saveChanges(); // implicit with send()Transport transport = session.getTransport("smtp");transport.connect(host, username, password);transport.sendMessage(message, message.getAllRecipients());transport.close();
评论:上面的方法是一个很好的方法,尤其是在 我们在同一个邮件服务器上发送多个邮件时。因为这时我们将在连接邮件服务器后连续发送邮件,然后再关闭掉连接。send()这个基本的方法是在每次调用时 进行与邮件服务器的连接的,对于在同一个邮件服务器上发送多个邮件来讲可谓低效的方式。
注意:如果需要在发送邮件过程中监控mail命令的话,可以在发送前设置debug标志:
session.setDebug(true)。
第三讲的接收邮件情况跟发送邮件一样,首先建立Session,然后取得相对应的协议(pop/imap)的Store(代表了存储邮件的邮件服务 器),在连接Store的过程中需要指定host,username,password,连接Store成功后会返回Folder对象,最后通过 Folder对象的getMessages()方法获取邮件信息。在读取邮件内容后,别忘记了关闭Folder和Store。
folder.close(true);
store.close();
传递给Folder.close()方法的类型参数表示是否在删除操作邮件后更新Folder。
注意:在连接前检查自己的POP,IMAP邮件服务有没有打开。最好尝试一下 ping pop.qq.com如果有返回数据表证明服务器可以连接。
package com.mail; /** * 接收邮件 */ import java.util.Properties; import javax.mail.Folder; import javax.mail.Message; import javax.mail.Session; import javax.mail.Store; public class RevMail { public static void main(String[] args) throws Exception { Properties props = new Properties(); Session session = Session.getDefaultInstance(props); // Store store = session.getStore("imap"); //取得pop3协议的邮件服务器 Store store = session.getStore("pop3"); //连接pop.qq.com邮件服务器 store.connect("pop.qq.com", "123456789@qq.com", "********"); //返回文件夹对象 Folder folder = store.getFolder("INBOX"); //设置仅读 folder.open(Folder.READ_ONLY); //获取信息 Message message[] = folder.getMessages(); for(int i=message.length-1; i>0; i--) { //打印主题 System.out.println(message[i].getSubject()); System.out.println("-----------------------------------------"); } folder.close(true); store.close(); } } |
PS:如果用imap连接,请用这段代码Store store = session.getStore("imap"); store.connect("imap.qq.com", "123456789@qq.com", "*********");
有些邮件服务器不支持imap协议的,请大家注意
其实删除邮件很简单,在接收邮件的基础上对需要删除的邮件做一个标记(Flags类),然后程序就会对有做了标记的邮件进行删除。
在Flags类的内部类Flag中预定义了一些标志:
Flags.Flag.ANSWERED
Flags.Flag.DELETED (删除的标记,这一讲我们就要用到这个标记)
Flags.Flag.DRAFT
Flags.Flag.FLAGGED
Flags.Flag.RECENT
Flags.Flag.SEEN
Flags.Flag.USER
删除邮件:message[i].setFlag(Flags.Flag.DELETED, true);
讲到这里我想大家都已经想到删除一封邮件的程序到底怎样写了。
不过有一点要注意的,上一讲我们打开Folder的时候是用READ_ONLY,当然读文件用READ_ONLY是做够的,但是我要这一讲要做的操作时删除文件,READ_ONLY是没有删除邮件的权限,所以我们要改为READ_WRITE。
如果要检查某个邮件是否标记了Flags.Flag.DELETED,可以使用message[i].isSet(Flags.Flag DELETED)
注意:这个删除操作是将邮件彻底删除的,大家要谨慎使用,务必读懂代码再进行操作。不小心删除了重要的邮件,小弟不负责人哦,(*^__^*) 嘻嘻……
这个只是一个很简单的删除邮件,在现实中我们会考虑到删除邮件后会将邮件移动另外一个目录下(如已删除、垃圾箱),这些功能我会在后面会讲!
给个例子大家参考:
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Properties;
import javax.mail.Flags; import javax.mail.Folder; import javax.mail.Message; import javax.mail.Session; import javax.mail.Store;
public class DelMail {
public static void main(String[] args) throws Exception { Properties props = new Properties(); Session session = Session.getDefaultInstance(props); //取得pop3协议的邮件服务器 Store store = session.getStore("pop3"); //连接pop.qq.com邮件服务器 store.connect("pop.qq.com", "123456789@qq.com", "******"); //返回文件夹对象 Folder folder = store.getFolder("INBOX"); //设置读写 folder.open(Folder.READ_WRITE); //获取信息 Message message[] = folder.getMessages();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
for(int i=0; i<message.length; i++) { System.out.println(i + ": " + message[i].getFrom()[0] + "\t" + message[i].getSubject()); System.out.println("Do you want to delete message? [YES to delete]"); String line = br.readLine(); if(line.equals("YES")) { //设置删除标记 message[i].setFlag(Flags.Flag.DELETED, true); } }
folder.close(true); store.close(); } } |
PS:程序最后folder.close(true)执行了才会生效!
在前面的教程里面我们都是用Session.getDefaultInstance(props)没有通过认证的方式取得session,这样明显的缺点是我们要连接邮件服务器的用户名和密码都要写死在代码里面,如:transport.connect("smtp.qq.com", "***@qq.com", "****");
store.connect("pop.qq.com", "*****@qq.com", "******");
这样子显得程序拓展性很差,如果要切换不同的用户就要修改源代码,这样是很麻烦的,还有用户名和密码明文地写在代码里很容易就泄露,这样是很不安全的。这一讲就是通过运用Authenticator类来进行邮件的认证。
Authenticator是一个抽象类,所以要extends Authenticator 抽象类在子类中覆盖父类中的 getPasswordAuthentication() 方法,就可以实现以不同的方式来进行登录邮箱时的用户身份认证。JavaMail 中的这种设计是使用了策略模式(Strategy),具体的请参看相关文章。
这一讲我们来建立一个通过对话框输入用户名和密码进行收发邮件。如果想了解不通过邮件认证连接服务器,请观看2~3讲的内容。
1、 首先我们建立一个继承Authenticator的子类。
import java.util.*; import javax.mail.*; import javax.swing.*;
public class GUIAuthenticator extends Authenticator {
protected PasswordAuthentication getPasswordAuthentication() { String user; String pwd; // 弹出输入对话框 String result = JOptionPane.showInputDialog("请输入用户名和密码,以','隔开!"); StringTokenizer st = new StringTokenizer(result, ","); user = st.nextToken(); pwd = st.nextToken(); return new PasswordAuthentication(user, pwd); } } |
作用:这个类的作用是在连接邮件服务器的时候,就会调用这个类的
getPasswordAuthentication方法,然后提示输入用户名和密码。
Ps:这个只是一个很简单的类,只作为一个例子给大家参考,真正的邮件认证不是那么简单的。
2、 通过邮件认证发送邮件:
package com.mail; import java.util.*; import javax.mail.*; import javax.mail.internet.*; /** * 通过邮件认证发送邮件 * @author sam * */ public class SendMailAuthenticator {
private String smtpServer = "smtp.qq.com"; private String from = "123456789@qq.com"; private String to = "123456789@qq.com"; private String subject = "测试使用邮件认证"; private String body = "测试使用邮件认证正文";
public void sendMail(Authenticator auth) throws Exception { // 设置协议、是否身份验证、服务器等信息 Properties props = new Properties(); props.setProperty("mail.transport.protocol", "smtp"); props.setProperty("mail.smtp.auth", "true"); props.setProperty("mail.host", smtpServer); // 通过传入的参数获得Authenticator子类对象 Session session = Session.getInstance(props, auth); // 构造信息体 MimeMessage message = new MimeMessage(session); // 发件地址 Address fromAddress = new InternetAddress(from); message.setFrom(fromAddress); // 收件地址 Address toAddress = new InternetAddress(to); message.setRecipient(MimeMessage.RecipientType.TO, toAddress);
// 主题 message.setSubject(subject); // 正文 message.setText(body);
message.saveChanges();
session.setDebug(true);
/* * * 由于Session对象中注册了Authenticator子类对象,因此可以直接 * * 从该Authenticator子类对象中获取登录的相关信息,故直接使用 * Transport 抽象类中静态 send() * 方法来简化代码编写 */
Transport.send(message); }
public static void main(String[] args) throws Exception { SendMailAuthenticator send = new SendMailAuthenticator(); Authenticator auth = new GUIAuthenticator(); send.sendMail(auth); } } |
3、 通过邮件认证接受邮件
package com.mail; import java.util.*; import javax.mail.*; import javax.mail.internet.*;
/** * 通过邮件认证接收邮件 * @author sam * */ public class RevMailAuthenticator { //邮件服务器地址 private String smtpServer = "pop.qq.com";
public void sendMails(Authenticator auth) throws Exception { // 设置协议、是否身份验证、服务器等信息 Properties props = new Properties(); props.setProperty("mail.transport.protocol", "pop3"); props.setProperty("mail.smtp.auth", "true"); props.setProperty("mail.host", smtpServer); // 通过传入的参数获得Authenticator子类对象 Session session = Session.getInstance(props, auth); /*********************************************************************** * 由于Session对象中注册了Authenticator子类对象,因此可以直接 * * 从该Authenticator子类对象中获取登录的相关信息,故直接使用 * * store.connect();而不用传递用户名和密码参数 */ Store store = session.getStore("pop3"); //连接pop.qq.com邮件服务器 store.connect(); //返回文件夹对象 Folder folder = store.getFolder("INBOX"); //设置仅读 folder.open(Folder.READ_ONLY); //获取信息 Message message[] = folder.getMessages(); for(int i=message.length-1; i>0; i--) { //打印主题 System.out.println(message[i].getSubject()); System.out.println("-----------------------------------------"); } folder.close(true); store.close(); }
// 测试 public static void main(String[] args) throws Exception { RevMailAuthenticator sender = new RevMailAuthenticator(); // 这里体现了使用策略模式的好处,客户端可以选择使用 // 具体的哪一种身份验证方式来提交信息 //对话框方式输入用户名和密码 Authenticator auth = new GUIAuthenticator(); sender.sendMails(auth); } } |
总结:通过邮件认证的方式连接邮件服务器,程序的通用性好了很多,我们每次运行程序的时候都可以切换不同的用户,程序灵活了很多,而且不用修改代码 就可以重用。其实Authenticator是设计模式的一种,Authenticator 的具体子类来实现不同的身份验证方式,这样使得用户可以通过不同的登录方式来登录,现实中也是这样的。
第六讲我将会讲解邮件的回复,邮件的回复的代码很简单,在迭代邮件的信息的时候调用MimeMessage reply = (MimeMessage) messages[i].reply(Boolean) TRUE 是邮件回复给发送者,FALSE恢复给所有人。如果要回复到新的收件人请使用SetReply-to。
同时可以使用 reply.setSubject(“***”)设置回复的标题,不设置的话默认是Re: 加上接收到邮件的标题。同理,也可以自己设置正文内容。
这里一定要设置发送邮件的地址,而且必须要和邮件认证的用户名一致,不然会报错。
最后调用Transport.send(reply)将邮件发出去
注意:1、reply.setFrom() 必须要设置而且要和邮件认证的用户名一致,不然会报以下错误:
com.sun.mail.smtp.SMTPSendFailedException: 501 mail from address must be same as authorization user
2、建议使用邮件认证登陆邮件服务器,不然会出现以下错误:
javax.mail.AuthenticationFailedException: failed to connect, no password specified?
3、代码虽然很简单,但是程序难免会出现各种异常。如果大家在调试程序的时候出现错误,请给我留言。
给一个成功例子大家参考:
import java.io.*; import java.util.*;
import javax.mail.*; import javax.mail.internet.*;
public class ReplyMail {
private static final String HOST_NAME = "pop.qq.com"; private static final String SEND_HOST_NAME = "smtp.qq.com"; private static final String PASSWORD = "*******"; private static final String EMAIL_FROM = "flyingsam@qq.com"; private static final String USER_NAME = "flyingsam@qq.com"; private static final String PROTOCOL = "pop3"; private static final String SEND_PROTOCOL = "smtp";
public static void listMail() throws Exception { Properties props = new Properties(); props.put("mail.smtp.host", SEND_HOST_NAME); props.put("mail.pop.host", HOST_NAME); props.put("mail.transport.protocol","smtp"); props.put("mail.smtp.auth", "true");
Session session = Session.getDefaultInstance(props, new SimpleAuthenticator(USER_NAME, PASSWORD)); Store store = session.getStore(ReplyMail.PROTOCOL); store.connect(HOST_NAME, USER_NAME, PASSWORD); Folder folder = store.getFolder("INBOX"); folder.open(Folder.READ_ONLY); Message[] messages = folder.getMessages(); InternetAddress address; for (int i = messages.length-1; i >= 0; i--) { address = (InternetAddress)messages[i].getFrom()[0]; if(address != null) { System.out.println(address.getPersonal()); } if (null != address && "秋日私语".equals(address.getPersonal())) { System.out.println("第" + i + "个:" + messages[i].getSubject()); MimeMessage replyMessage = (MimeMessage) messages[i].reply(false); // replyMessage.setFrom(new InternetAddress(EMAIL_FROM));
replyMessage.setRecipients(MimeMessage.RecipientType.TO, address.getAddress()); replyMessage.setText("这是回复邮件,不知道能否成功!"); replyMessage.saveChanges(); Transport transport = session.getTransport("smtp"); transport.connect(SEND_HOST_NAME, USER_NAME, PASSWORD); transport.send(replyMessage); System.out.println("回复成功"); } } folder.close(true); store.close(); }
public static void main(String[] args) { try { ReplyMail.listMail(); } catch (Exception e) { e.printStackTrace(); } } } |
类SimpleAuthenticator的代码:
public class SimpleAuthenticator extends Authenticator { private String username; private String password; public SimpleAuthenticator(String username, String password) { this.username = username; this.password = password; } public PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(this.username, this.password); } } |
总结:邮件回复就那么几行代码,不过在写程序的时候出现的问题接二连三,一个问题解决了,又出现另外一个问题。一直在寻找答案,在国内各大技术论坛、社区 都找过了,可是没有我想要的答案,最后决定去外国google搜一下,看看有没有外国朋友遇到过这个问题,可是在google搜到的英文网站的资料都读 了,问题依然没有解决,最后去查找java mail的源代码,读了这个类库的一些源代码理解了背后原理,最后将问题解决了。其实遇到问题不难解决,难就难有没有好的解决问题的思路,如果遇到问题就 扔掉或者坐在一边等人家给答案,那是永远学不会东西的。经过这次的我收获很大!
邮件转发的思路:
1、 取得要转发的邮件信息(Message message)
2、 创建一个邮件信息体(Message forward)
3、 设置forward的主题(Subject)、源地址(From)、接收者(Recipient)
4、 创建Multipart的容器,在邮件转发里容器只要两个邮件体(BodyPart)就可以实现转发,一个是文字邮件部分,一个是被转发的邮件体(请看下面说明)
5、 讲Multipart容器放到转发邮件信息里,forward.setContent(multipart);
6、 发送邮件Transport.send(forward);
说明:思路3 源地址要与message的源地址一致
思路4 每个邮件是由多个部分组成,每个部分称为一个邮件体部分,是一个BodyPart类对象。BodyPart包含在Multipart容器里。
以下是一个经过测试可以运行的程序:
package com.mail;
import java.util.Properties;
import javax.mail.*; import javax.mail.internet.*;
public class ForwardMail {
private static final String HOST_NAME = "pop.qq.com"; private static final String SEND_HOST_NAME = "smtp.qq.com"; private static final String PASSWORD = "********"; private static final String EMAIL_FROM = "******@qq.com"; private static final String USER_NAME = "*******@qq.com"; private static final String PROTOCOL = "pop3"; private static final String SEND_PROTOCOL = "smtp";
public static void forwardMail(Session session, Message message) throws Exception { // 创建转发邮件信息 Message forward = new MimeMessage(session); // 设置主题 forward.setSubject("Fwd: " + message.getSubject()); forward.setFrom(new InternetAddress(EMAIL_FROM)); forward.addRecipient(Message.RecipientType.TO, new InternetAddress("********@sina.com")); // 文字邮件体部分 BodyPart messageBodyPart = new MimeBodyPart(); messageBodyPart.setText( "邮件转发" ); //创建Multipart的容器 Multipart multipart = new MimeMultipart(); multipart.addBodyPart(messageBodyPart); // 被转发的文字邮件体部分 messageBodyPart = new MimeBodyPart(); messageBodyPart.setDataHandler(message.getDataHandler()); // 添加到Multipart容器 multipart.addBodyPart(messageBodyPart); forward.setContent(multipart); // 发送 Transport.send(forward); System.out.println("msg forward ...."); }
public static void listMail() throws Exception { Properties props = new Properties(); props.put("mail.smtp.host", SEND_HOST_NAME); props.put("mail.pop.host", HOST_NAME); props.put("mail.transport.protocol","smtp"); props.put("mail.smtp.auth", "true");
Session session = Session.getDefaultInstance(props, new SimpleAuthenticator(USER_NAME, PASSWORD)); Store store = session.getStore(PROTOCOL); store.connect(HOST_NAME, USER_NAME, PASSWORD); Folder folder = store.getFolder("INBOX"); folder.open(Folder.READ_ONLY); Message[] messages = folder.getMessages(); InternetAddress address; for (int i = messages.length-1; i >= 0; i--) { address = (InternetAddress)messages[i].getFrom()[0]; /* if(address != null) { System.out.println(address.getPersonal()); } */ if (null != address && "秋日私语".equals(address.getPersonal())) { System.out.println("第" + i + "个:" + messages[i].getSubject()); //转发邮件 forwardMail(session, messages[i]); } } folder.close(true); store.close(); }
public static void main(String[] args) { try { listMail(); } catch (Exception e) { e.printStackTrace(); } } } |