JavaMail是JavaEE中的一个组件,用来开发邮件客户端程序(MUA)。JavaMail API本身是与具体的消息协议无关的,可以在运行程序之前设置实现具体协议的providers,例如在Sun的mail.jar中提供了一个缺省的文件javamail.default.providers,里面提供了三个协议(imap, smtp和pop3)的实现,其中smtp的provider类是com.sun.mail.smtp.SMTPTransport。如果用户需要开发NNTP协议的客户端程序,可以考虑其他的provider,例如GNU JavaMail。
JavaMail需要JavaSE中的 JavaBeans Activation Framework(JAF)才能运行,JAF位于package: javax.activation.*。这是因为接口Part/MimePart(实现类包括 Message,MimeMessage,BodyPart和MimeBodyPart)需要javax.activation.DataHandler来处理邮件的内容。
********Message的结构********
javax.mail.Message类是一个抽象类,针对Internet邮件,JavaMail中提供了一个具体类javax.mail.internet.MimeMessage。
一条邮件消息Message包含header和content两部分。
header包含Part接口中定义的通用头属性和实现类(比如Message,MimeMessage)中定义的头属性。例如MimeMessage的setHeader("X-Mailer","ljsspace")设置Internet邮件的X-Mailer头,MimeBodyPart的setHeader("Content-ID","a5c8x3f7i98")设置一个BodyPart的Content-ID,以便在其他Part的content中通过cid:a5c8x3f7i98引用该部分内容。
content可以是文本(包含text/plain或text/html),DataHandler或者Multipart/MimeMultipart。例如Part接口的setText(String)用来设置text/plain内容,setContent(String,"text/html")用来设置text/html内容,setContent(Multipart)用来设置Multipart内容,setDataHandler(DataHandler)设置DataHandler内容。另外,为了获取Message的内容,使用getDataHandler和getContent是具有同等效果。
上面提到的Multipart/MimeMultipart类是一个容器,它可以包含多个BodyPart/MimeBodyPart作为该容器中的组成部分,例如一封Internet邮件包含一个text/plain正文和一个附件,正文是一个MimeBodyPart,附件也是一个MimeBodyPart,合起来就是一个MimeMultipart,然后该MimeMultipart作为邮件MimeMessage的content。BodyPart也是Part接口的实现,即也是包含header和content两部分,显然BodyPart的content也可以是一个Multipart,甚至更多级的层次结构。
********接收和发送邮件********
接收邮件在JavaMail中使用的术语的是store,对应的Internet邮件协议有pop3和imap等(遵照JavaMail的习惯,协议名称一律用小写),每一个store中可以能有多有folders(一般情况下pop3中只支持INBOX这一个folder)。发送邮件在JavaMail中使用的术语是transport,对应的Internet邮件协议有smtp等。
在收发邮件之前需要建立一个Session对象,它是一个Factory类,可以生成收发邮件的Store和Transport对象。在smtp协议中,大部分MTA需要用邮件发送人地址的帐号通过认证才允许发邮件,以防open relay产生垃圾邮件,因此需要设置mail.smtp.auth为true,并在构建Session对象时指定一个Authenticator(也可以在transport.connect()时指定认证帐号)。
发送邮件可以用transport.send()或Transport.send(),后者是一个静态方法。如果使用静态方法,JavaMail会根据接收人的邮件地址构造transport对象来发送邮件,这是在后台自动完成的。另外Transport.send有两个重载方法,Transport.send(MimeMessage)根据message中的接收人列表发送邮件,Transport(MimeMessage,InternetAddress[])忽略message中的接收人列表,而直接按第二个参数的地址列表发送邮件,利用这个特点可以给邮件组(mailing list)发送邮件,参考以下代码实现。
接收邮件在JavaMail中使用一个lightweight的类MimeMessage,例如在调用inbox.getMessages()之后,并不是立即将有所有的邮件头和内容全部从store中读取过来,只有在需要的时候才读取。为此,JavaMail还提供了一个FetchProfile类,可以从store中获取所有邮件的部分头信息,而不必读取邮件内容。
********总结********
JavaMail是一个独立于具体协议的邮件或消息框架,除了上面介绍的核心内容之外,JavaMail还支持类似于Swing/AWT中的事件响应机制,而且还有一个专门的package(javax.mail.search)用来根据过滤条件从folder中检索邮件。使用JavaMail可以较轻松地构建一个跨平台的Internet邮件客户端程序,或者在企业环境下开发出邮件或消息子系统。
参考:JavaMail 1.4: http://www.oracle.com/technetwork/java/javamail/index.html
代码:
import java.io.File; import java.io.InputStream; import java.util.Date; import java.util.Properties; import javax.activation.DataHandler; import javax.activation.DataSource; import javax.activation.FileDataSource; import javax.mail.Authenticator; import javax.mail.FetchProfile; import javax.mail.Folder; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import javax.mail.PasswordAuthentication; 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; /** * * JavaMail examples: send and fetch mail messages * * Copyright (c) 2011 ljs (http://blog.csdn.net/ljsspace/) * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php) * * @author ljs * 2011-10-10 * */ public class JavaMail { /** * send a message to a mailing list (e.g. [email protected]). The actual receivers (e.g. toAddr) * are specified at the last moment when transport.send is called. */ public void sendMail(String host, final String user, final String password, String fromAddr,String toAddr,String subject, String msgText,File attachment,String filename) { // Transport transport = null; try { Properties props = System.getProperties(); //use system properties props.put("mail.debug", "true"); //or: session.setDebug(true) props.put("mail.transport.protocol", "smtp"); props.put("mail.smtp.host", host); props.put("mail.smtp.user", user); //if SMTP authentication is required, we must set this property props.put("mail.smtp.auth", "true"); Authenticator auth = new Authenticator() { public PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(user, password); } }; Session session = Session.getInstance(props, auth);//use authentication // session = Session.getDefaultInstance(props, null); MimeMessage msg = new MimeMessage(session); msg.setFrom(new InternetAddress(fromAddr)); //InternetAddress[] toAddress = {new InternetAddress(toAddr)}; //msg.setRecipients(Message.RecipientType.TO, toAddress); String mailingListAddr = "[email protected]"; msg.addRecipient(Message.RecipientType.TO, new InternetAddress(mailingListAddr,"Java List")); msg.setSubject(subject); msg.setHeader("X-Mailer", "ljsspace-javamail"); msg.setSentDate(new Date()); //create a multipart as the content MimeMultipart multipart = new MimeMultipart(); MimeBodyPart mbp1 = new MimeBodyPart(); mbp1.setText(msgText); multipart.addBodyPart(mbp1); //attachment DataSource fileDS = new FileDataSource(attachment); DataHandler fileDH = new DataHandler(fileDS); MimeBodyPart file_attachment = new MimeBodyPart(); file_attachment.setDataHandler(fileDH); file_attachment.setFileName(filename); multipart.addBodyPart(file_attachment); //set content for the message; the content is a multipart object msg.setContent(multipart); //msg.saveChanges(); //unnecessary: Transport.send will call saveChanges //transport = session.getTransport(); //transport.connect(); //transport.send(msg); InternetAddress[] toAddress = {new InternetAddress(toAddr)}; //Transport.send(msg); //only send to message's getRecipients() Transport.send(msg,toAddress); //ignore message's getRecipients() } catch (Exception e) { e.printStackTrace(); } // finally{ // try { // transport.close(); // } catch (Exception ex) { // ex.printStackTrace(); // } // } } /** * Fetch messages from the INBOX store * @param preview If true, display all messages' profiles; otherwise, display the contents of the first two new messages */ public void fetchMail(boolean preview,String host, final String user, final String password) { Store store = null; Folder inbox = null; try { Properties props = System.getProperties(); props.put("mail.debug", "true"); props.put("mail.store.protocol", "pop3"); //or store like imap props.put("mail.pop3.user", user); //no password! props.put("mail.pop3.host", host); Authenticator auth = new Authenticator() { public PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(user, password); } }; Session session = Session.getDefaultInstance(props, auth); // Get hold of a POP3 message store using session (a factory object) store = session.getStore(); //the same as session.getStore("pop3"). Since we've set mail.store.protocol property, we just call session.getStore(). // store.connect(host, user, password); //use authenticator instead store.connect(); //connect using session's authenticator // inbox = store.getDefaultFolder(); //the root folder in the default namespace inbox = store.getFolder("INBOX"); //the only folder in POP3 inbox.open(inbox.READ_WRITE); // Get the messages (a light-weight operation) Message[] msgs = inbox.getMessages(); if(preview){ FetchProfile fp = new FetchProfile(); fp.add(FetchProfile.Item.ENVELOPE); //fetch subject, from, etc. fp.add("X-mailer"); //fetch x-mailer inbox.fetch(msgs, fp); for (int i = 0; i < inbox.getMessageCount(); i++) { displayMessageProfile(msgs[i]); } }else{ //read the first 2 messages only (from the newest to the oldest) for (int i = inbox.getMessageCount()-1; i>=0 && i > inbox.getMessageCount() - 3; i--) { processMessage(msgs[i]); //msgs[i].setFlag(Flags.Flag.DELETED, true); } } } catch (Exception e) { e.printStackTrace(); } finally { try { if (inbox != null) inbox.close(true); //expunge the deleted message if (store != null) store.close(); } catch (Exception ex) { ex.printStackTrace(); } } } // display a light-weight message's profile private void displayMessageProfile(Message message) { try { System.out.println(message.getFrom()[0] + "\t" + message.getSubject()); } catch (MessagingException e) { e.printStackTrace(); } } private void processMessage(Message message) { try { System.out.println("*******" + message.getSubject() + "*******"); readPart(message); } catch (MessagingException e) { e.printStackTrace(); } } private void readPart(Part p) { try { Object content = p.getContent(); if(content instanceof String){ System.out.println(content); }else if (content instanceof Multipart) { Multipart mp = (Multipart) content; int count = mp.getCount(); for (int i = 0; i < count; i++) { Part bodyPart = mp.getBodyPart(i); //or check: boyPart.getDisposition() readPart(bodyPart); //recursion } }else if (content instanceof InputStream) { System.out.println("****found inputstream as a part's content****"); }else{ System.out.println("****unknown type****"); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { //test store: pop3 String pop3host="pop.126.com"; String pop3user="abc"; //test account String pop3pass="******"; JavaMail mailer = new JavaMail(); mailer.fetchMail(true,pop3host, pop3user, pop3pass); //test transport: smtp String smtphost="smtp.126.com"; String smtpuser="abc"; //test account String smtppass="******"; String fromAddr="[email protected]"; String toAddr="[email protected]"; String subject="A Test Message sent via JavaMail"; String msgText = "Please see the attachment for details."; File attachment = new File("/tmp/test.pdf"); String filename = "test.pdf"; mailer.sendMail(smtphost, smtpuser,smtppass, fromAddr,toAddr,subject, msgText,attachment,filename); } }