JavaMail是API 是一个标准的Java扩展,它是J2EE的范畴,在J2EE开发过程中可能会需要用到这个API。在学习JavaMail之前,有必须要对现在的互联网的邮件协议进行有个大体的了解。

邮件协议

在Internet中,常用的邮件操作相关的协议有3个—SMTP、POP、IMAP。

简单邮件传输协议(simple mail transferprotocol,SMTP),这个协议是邮件服务器之间相互发送邮件的协议,也是客户端利用该协议向邮件服务器端发送邮件的协议。一般一个邮件首先会被传送到某一个邮件服务器,再被邮件服务器分发到一个或多个目标邮件服务器。

邮局协议第3版(postoffice protocol version 3,POP3),协议主要用于从邮件服务器检索以得到新的邮件,大量的客户机从邮件服务器接收邮件就是使用POP3协议。

因特网消息访问协议(internet messager accessprotocol,IMAP),该协议比POP3功能更加强大,它可在接收邮件时,把邮件保存在邮件服务器中,既可在服务器中保留邮件也可把邮件下载
安装与配置JavaMail

由于JavaMail是一个扩展的部分,要进行发送接收邮件,需要两个包:

一个是JavaMail,这个包含了SMTPPOP3IMAP提供了支持,封装了电子邮件功能中的邮件对象、发送功能、身份认证、接收等。当前最新的版本是1.5

一个是JAF(JavaBeans Activation Framework),主要用来描述和显示邮件中的相关内容的,当前最新的版本是1.1.1
  具体所需要的包,可以在本文的附件中直接下载。
邮件发送与接收

JavaMail包中的类比较多,主要用到的有会话类地址类邮件类邮件发送类邮件接收类邮件文件夹类这些常用的类。

会话类(Session),主要用来创建邮件对象、实现邮件对象中数据的封装并可指定邮件服务器认证的客户端属性。它表示程序与某一个邮件服务器即将建立通信,在建立的过程可以进行口令认证。

地址类(Address),这个地址类主要是表示邮件发送人和邮件接收人的地址,一般主要用的是InternetAddress。

邮件类(Message),邮件消息的主要类,它包含了邮件中的所有部分,继承自一个接口Part,一般在使用的过程中直接是利用它的子类MimeMessage

邮件发送类(Transport),一般是从一个会话中获取一个邮件发送类的实例,将已经写好的邮件利用SMTP协议发送到指定的邮件服务器,在发送的过程中,首先根据指定口令连接到邮件服务器,再发送邮件。

邮件接收类(Store),这个其实就是邮件服务器中的存储库,里面放着所有的邮件文件夹

邮件文件夹类(Folder),该文件夹就是消息的具体所在文件夹,默认的邮件均在INBOX文件中。
发送邮件
基本步骤:

1 利用Properties来设置Session,一般主要设置两个mail.smtp.host和mail.smtp.auth,第一个主要是设置邮件服务器名,第二个是设置口令true或者false

2 利用Session.getInstance(Properties)启动一个与邮件服务器的连接

3 根据获取的Session来传建一个消息Message

4 定义消息的发信人地址InternetAddress和消息的收信人地址。

5 设置消息发送的主题和内容

6 利用Message.saveChanges()来存储填写的邮件信息

7 根据Session.getTransport("smtp")获取邮件发送类

8 利用发送人的用户名和密码连接到指定的邮件服务器

9 将该消息发送
注意:发送消息最重要的是要正确的找到发送消息的邮件服务器名,至于收信人的邮箱无所谓,可以是任意正确的邮件,譬如发送人是163邮件,可以发送给搜狐邮箱,新浪邮箱,QQ邮箱。
示例代码:

package whut.mailsender;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.Session;
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 whut.mailreceiver.MailAuthenticator;
public class SimpleSMTPSender {
    public static void main(String[] args) {
        try{
            Properties props=new Properties();
            //传递一个邮件服务器名smtp.163.com
            //mail.smtp.host代表是发信人所在的邮箱服务器名
            props.put("mail.smtp.host", "smtp.163.com");
            props.put("mail.smtp.auth", true);
            //对于发送邮件,只需要保证发送人所在的邮件服务器正确打开就可以了
            //收信人的邮箱可以是任意地址,如@163.com,@qq.com,@126.com
                               
            //创建一个程序与邮件服务器的通信
            Session mailConnection=Session.getInstance(props,null);
            Message msg=new MimeMessage(mailConnection);
                               
            //创建一个要输入用户名和指令的
            //Session mailConnection=Session.getInstance(props,new MailAuthenticator());
                               
                               
            //设置发送人和接受人
            Address sender=new InternetAddress("yyyy@163.com");
            Address receiver=new InternetAddress("xxx@163.com");
                               
            /*
             * 群发邮件的方法
             * StringBuffer buffer=new StringBuffer();
             * buffer.append("11@163.com,")
             * buffer.append("22@163.com")
             * String all=buffer.toString();
             * Address[] allre=InternetAddress.parse(all);
             * msg.setRecipient(Message.RecipientType.TO, allre);
             */
            msg.setFrom(sender);
            msg.setRecipient(Message.RecipientType.TO, receiver);
            msg.setSubject("You must comply");
                               
            //msg.setContent("Hello", "text/plain");
                               
                               
            //下面是模拟发送带附件的邮件
            //新建一个MimeMultipart对象用来存放多个BodyPart对象
            Multipart mtp=new MimeMultipart();
            //------设置信件文本内容------
            //新建一个存放信件内容的BodyPart对象
            BodyPart mdp=new MimeBodyPart();
            //给BodyPart对象设置内容和格式/编码方式
            mdp.setContent("hello","text/html;charset=gb2312");
            //将含有信件内容的BodyPart加入到MimeMultipart对象中
            mtp.addBodyPart(mdp);
                               
            //设置信件的附件(用本地机上的文件作为附件)
            mdp=new MimeBodyPart();
            FileDataSource fds=new FileDataSource("f:/webservice.doc");
            DataHandler dh=new DataHandler(fds);
            mdp.setFileName("webservice.doc");//可以和原文件名不一致
            mdp.setDataHandler(dh);
            mtp.addBodyPart(mdp);
            //把mtp作为消息对象的内容
            msg.setContent(mtp);
                               
            //以上为发送带附件的方式
            //先进行存储邮件
            msg.saveChanges();
                               
            Transport trans=mailConnection.getTransport("smtp");
            String username="yyyy@163.com";
            String pw="";
            //邮件服务器名,用户名,密码
            trans.connect("smtp.163.com", username,  pw);
            trans.sendMessage(msg, msg.getAllRecipients());
            trans.close();
        }catch(Exception e)
        {
            System.err.println(e);
        }
        finally{
            System.exit(0);
        }
    }
}


接受邮件

基本步骤

1 利用Properties创建一个属性,不需要设置任何属性,之间传递Session使用

2 Session.getDefaultInstance()获取一个邮件会话

3 使用该会话向某种提供者请求一个存储库,ss.getStore("pop3");获取一个Store

4 存储库向指定的邮件服务器建立连接

5 通过getFolder("INBOX"),获取该存储库中INBOX文件夹

6 打开INBOX文件夹

7 消息处理

8 关闭文件夹

9 关闭存储库
注意:从服务器返回的邮件有可能在发送者或者接受者出现中文乱码,这里进行了乱码处理
示例代码:

package whut.mailreceiver;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Properties;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Header;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeUtility;
//利用POP3来读取邮件
//主要用来检测消息Message的基本信息,如发送者,收信者,时间
public class POP3Client {
    public static void main(String[] args) {
        Properties props = System.getProperties();
        String host = "pop3.163.com";
        String username = "22222222@163.com";
        String password = "312313121";
        String provider = "pop3";
        try {
            // 连接到POP3服务器
            Session ss = Session.getDefaultInstance(props, null);
            // 向回话"请求"一个某种提供者的存储库,是一个POP3提供者
            Store store = ss.getStore(provider);
            // 连接存储库,从而可以打开存储库中的文件夹,此时是面向IMAP的
            store.connect(host, username, password);
            // 打开文件夹,此时是关闭的,只能对其进行删除或重命名,无法获取关闭文件夹的信息
            // 从存储库的默认文件夹INBOX中读取邮件
            Folder inbox = store.getFolder("INBOX");
            if (inbox == null) {
                System.out.println("NO INBOX");
                System.exit(1);
            }
            // 打开文件夹,读取信息
            inbox.open(Folder.READ_ONLY);
            System.out.println("TOTAL EMAIL:" + inbox.getMessageCount());
            // 获取邮件服务器中的邮件
            Message[] messages = inbox.getMessages();
            for (int i = 0; i < messages.length; i++) {
                System.out.println("------------Message--" + (i + 1)
                        + "------------");
                // 解析地址为字符串
                String from = InternetAddress.toString(messages[i].getFrom());
                if (from != null) {
                    String cin = getChineseFrom(from);
                    System.out.println("From:" + cin);
                }
                String replyTo = InternetAddress.toString(messages[i]
                        .getReplyTo());
                if (replyTo != null)
                    {
                    String rest = getChineseFrom(replyTo);
                    System.out.println("Reply To" + rest);
                    }
                String to = InternetAddress.toString(messages[i]
                        .getRecipients(Message.RecipientType.TO));
                if (to != null) {
                    String tos = getChineseFrom(to);
                    System.out.println("To:" + tos);
                }
                String subject = messages[i].getSubject();
                if (subject != null)
                    System.out.println("Subject:" + subject);
                SimpleDateFormat sdf = new SimpleDateFormat(
                        "yyyy-MM-dd HH:mm:ss");
                Date sent = messages[i].getSentDate();
                if (sent != null)
                    System.out.println("Sent Date:" + sdf.format(sent));
                Date ress = messages[i].getReceivedDate();
                if (ress != null)
                    System.out.println("Receive Date:" + sdf.format(ress));
                // 显示消息的所有首部信息
                // Enumeration headers=messages[i].getAllHeaders();
                // while(headers.hasMoreElements())
                // {
                // Header h=(Header)headers.nextElement();
                // String res=h.getName();
                // String val=h.getValue();
                // System.out.println(res+":"+val);
                // }
                // System.out.println();
                // 读取消息主题部分
                // Object content=messages[i].getContent();
                // System.out.println(content);
                // 根据指定的编码格式读出内容
                // Reader read=new
                // InputStreamReader(messages[i].getInputStream());
                // int a=0;
                // while((a=read.read())!=-1)
                // {
                // System.out.print((char)a);
                // }
                // 获取该消息的类型
                // String type=messages[i].getContentType();
                // String
                // sender=InternetAddress.toString(messages[i].getFrom());
                // System.out.println("Sender:"+sender);
                // System.out.println("Content-type:"+type);
            }
            // 关闭连接,但不删除服务器上的消息
            // false代表不是删除
            inbox.close(false);
            store.close();
        } catch (Exception e) {
            System.err.println(e);
        }
    }
    // 解决中文乱码问题
    public static String getChineseFrom(String res) {
        String from = res;
        try {
            if (from.startsWith("=?GB") || from.startsWith("=?gb")
                    || from.startsWith("=?UTF")) {
                from = MimeUtility.decodeText(from);
            } else {
                from = new String(from.getBytes("ISO8859_1"), "GBK");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return from;
    }
}

一般在读取邮件的时候会有个口令验证,可以在访问的过程中设置用于用户口令输入提示框:此时要修改一下Session的获取方法,传递一个继承了Authenticator,连接到POP3服务器,当提供者需要用户名和密码的时候,则会回调Authenticator的子类的getPasswordAuthentication必,同时须在connect中口令字段为null才能行

Session ss=Session.getDefaultInstance(props, new MailAuthenticator());

//向回话请求一个某种提供者的存储库,是一个POP3提供者

Store store=ss.getStore(provider);

//连接存储库,从而可以打开存储库中的文件夹,此时是面向IMAP的store.connect(host, null, null);

口令弹出框代码如下:

package whut.mailreceiver;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
//口令验证
public class MailAuthenticator extends Authenticator {
    private JDialog passwordDialog=new JDialog(new JFrame(),true);
    private JLabel mainLabel=new JLabel("Please enter your user name and password:");
    private JLabel userLabel=new JLabel("User name:");
    private JLabel passwordLabel=new JLabel("Password:");
    private JTextField usernameField=new JTextField(20);
    private JPasswordField passwordField=new JPasswordField(20);
    private JButton okButton=new JButton("OK");
    public MailAuthenticator()
    {
        this("");
    }
                                                                                                                                                                                                                                                                                                                                                                                                    
    public MailAuthenticator(String username)
    {
        Container pane=passwordDialog.getContentPane();
        pane.setLayout(new GridLayout(4,1));
        pane.add(mainLabel);
                                                                                                                                                                                                                                                                                                                                                                                                        
        JPanel p2=new JPanel();
        p2.add(userLabel);
        p2.add(usernameField);
        pane.add(p2);
                                                                                                                                                                                                                                                                                                                                                                                                        
        JPanel p3=new JPanel();
        p3.add(passwordLabel);
        p3.add(passwordField);
        pane.add(p3);
        JPanel p4=new JPanel();
        p4.add(okButton);
        pane.add(p4);
                                                                                                                                                                                                                                                                                                                                                                                                        
        passwordDialog.setSize(400, 200);
                                                                                                                                                                                                                                                                                                                                                                                                        
        ActionListener al=new HideDialog();
        okButton.addActionListener(al);
        usernameField.addActionListener(al);
        passwordField.addActionListener(al);
    }
                                                                                                                                                                                                                                                                                                                                                                                                    
    class HideDialog implements ActionListener
    {
        public void actionPerformed(ActionEvent e)
        {
            passwordDialog.hide();
        }
    }
                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                    
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
        // TODO Auto-generated method stub
        passwordDialog.show();
        //getPassword是返回的字符数组
        String  username=usernameField.getText();
        String password=new String(passwordField.getPassword());
        passwordField.setText("");
        return new PasswordAuthentication(username,password);
    }
}

邮件传输中的几个类说明

Addres类,一般直接使用它的子类InternetAddress邮箱的地址,有两个特别重要的方法toString(),y用于将地址数组转换为字符串,并且多个地址以逗号隔开,parse()用于将以逗号隔开的地址解析为地址数组,这个可以为群发邮件用。

URLName类,这个目的是为了构造一个不需要该模式的协议处理器,它不会尝试连接,可以用于方便的标识文件夹和非标准的URL的存储库

Message类,主要是消息的抽象类,一般使用其子类MimeMessage,可以通过它来获取邮件的发送者,接受者,时间,主题,内容等邮件首部,查看邮件查收情况。

Part接口,一般包括了部分属性的方法、首部的方法、和内容的方法。实际运用中都是通过它的子类来调用的,可以根据属性来判断一个邮件是否含有附件。利用getAllHeaders()获得邮件的所有首部名和值,它返回的是Enumeration。

查看邮件的内容,查看MIME内容的类型是getContentType(),如返回"text/plain;charset="GBK",

一般通过getContent()来查看返回具体的内容,这里利用了JAF来自动的调整类型为java可识别的类型,当是text/plain的时候,返回是String,内容是多部分的时候,返回的是Multipart对象。如果是其他JAF也会将其转换为Java适当的对象。如果类型不能识别,则返回一个InputStream。

写入邮件的内容,一般文本型直接调用setText();也可以使用setContent(string,chartype);当要发送多个部分的时候,就需要setContent(Multipart p);每一个部分必须是MimeBodyPart,然后将其添加到MimeMultipart.在邮件的传输过程中,如果想要传输文件的话,必须将数据包装到BodyPart中,然后再添加到Multipart 中。

Folder文件,一般都是通过Session、Store或另一个Folder获得,一般获取的文件夹并不确保一定存在,要利用exists()判断。对于文件夹的一些操作必须要注意,检索和获取文件夹信息,必须要打开文件夹上才能操作。删除或者重命名文件夹,只能在文件夹关闭的时候完成。

题外:

国内常见的几个免费邮件服务器名如下

网易免费邮箱:发送服务器:smtp.163.com     接收服务器:pop.163.com

新浪免费邮箱:发送服务器:smtp.sina.com.cn 接收服务器:pop3.sina.com.cn

搜狐邮箱:发送服务器:smtp.sohu.com        接收服务器:pop3.sohu.com

常见乱码处理方式

package whut.mailsender;
import javax.mail.Part;
import javax.mail.internet.MimeUtility;
import sun.misc.BASE64Decoder;
public class StringUtil {
      
    //发信人,收信人,回执人邮件中有中文处理乱码,res为获取的地址
    //http默认的编码方式为ISO8859_1
    //对含有中文的发送地址,使用MimeUtility.decodeTex方法
    //对其他则把地址从ISO8859_1编码转换成gbk编码
    public static String getChineseFrom(String res) {
        String from = res;
        try {
            if (from.startsWith("=?GB") || from.startsWith("=?gb")
                    || from.startsWith("=?UTF")) {
                from = MimeUtility.decodeText(from);
            } else {
                from = new String(from.getBytes("ISO8859_1"), "GBK");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return from;
    }
      
    //转换为GBK编码
    public static String toChinese(String strvalue) {
        try {
            if (strvalue == null)
                return null;
            else {
                strvalue = new String(strvalue.getBytes("ISO8859_1"), "GBK");
                return strvalue;
            }
        } catch (Exception e) {
            return null;
        }
    }
    //接收邮件时,获取某个邮件的中文附件名,出现乱码
    //对于用base64编码过的中文,则采用base64解码,
    //否则对附件名进行ISO8859_1到gbk的编码转换
    public static String getFileChinese(Part part) throws Exception {
        String temp = part.getFileName();// part为Part实例
        if ((temp.startsWith("=?GBK?") && temp.endsWith("?="))
                || (temp.startsWith("=?gbk?b?") && temp.endsWith("?="))) {
            temp = StringUtil.getFromBASE64(temp.substring(8, temp.indexOf("?=") - 1));
        } else {
            temp = StringUtil.toChinese(temp);
        }
        return temp;
    }
    public static String getFromBASE64(String s) {
        if (s == null)
            return null;
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            byte[] b = decoder.decodeBuffer(s);
            return new String(b);
        } catch (Exception e) {
            return null;
        }
    }
}