?? 作為企業級應用的JavaEE,郵件收發毫無疑問是其重要技術組成;在這方面,JavaMail庫和Apache的通用電子郵件軟件包給我們提供了兩個選擇.不過通用電子郵件庫實際上是包裹在JavaMail外層的API,所以無論我們選擇哪種API,都需要JavaMail庫;我們可能還需要JavaBeans激活框架(JavaBeans Activation Framework(JAF)),該框架將負責處理關于郵件選項的更復雜的內容.由于通用電子郵件軟件包并沒有實現收取郵件的操作,在這里,我們暫且只討論JavaMail的實現.
一.郵件的發送
? 第一件要知道的事情是,你的SMTP服務器的主機名,它負責將您的郵件發送到外部世界的機器.一般來說這些服務器都符合命名習慣,比如,如果你的郵箱是acmilan@sina.com.cn,那么SMTP服務器的主機名則是smtp.sina.com.cn;另外也可以參考各大網站自己的說明.為了方便,下文中以網易郵箱為例.
? JavaMail使用了Session類的概念來保存諸如SMTP主機和認證的信息,主要想法是基于會話(Sessions)在Java虛擬機中可以被隔離,這可以阻止惡意代碼竊取其他用戶在其他會話中的信息,這些信息可能包括用戶名和密碼等認證信息.你所要發送的郵件將保存在一個Message對象中,而這個Message對象則是由你所構造的session實例來創建
? 要得到一個特定的session對象,可以通過一下代碼:
????? //設置session的屬性
????? Properties pro = new Properties();
????? pro.put("mail.transport.protocol", "smtp");
????? pro.put("mail.smtp.auth", "true");
????? pro.put("mail.smtp.host", "smtp.126.com");
????? pro.put("mail.host", "126.com");
?????
????? //設置認證器
????? PopupAuthenticator pop = new PopupAuthenticator();
????? pop.performCheck("My Name", "My Password");//你的帳戶和密碼
?????
????? //得到session
????? Session mailSession = Session.getInstance(pro, pop);
? 要注意的是,為了避免垃圾郵件,大多數的smtp服務器需要認證,SMTP認證(SMTP AUTH)需要用戶名和密碼來發送郵件;因此,必須在session的初始化參數中設置一個認證者(Authenticator)來返回所需的認證證書,具體代碼必須由自己來實現:
???? class PopupAuthenticator extends Authenticator {
?????? String username = null;
?????? String password = null;
?????? public PopupAuthenticator() {}
?????? public PasswordAuthentication performCheck(String user, String pass) {
???????? username = user;
???????? password = pass;
???????? return getPasswordAuthentication();
??????? }
?????? protected PasswordAuthentication getPasswordAuthentication() {
???????? return new PasswordAuthentication(username, password);
??????? }
????? }
? 接著,就可以用之前得到的session來構造Message對象:
??? Message msg = new MimeMessage(mailSession);?
? 在使用會話創建了一個MimeMessage后,我們需要來填充這個消息.首先是設置表頭信息,Message類定義了郵件系統中使用的屬性,由名字-值對組成,使用這些名字-值可以指定郵件表頭信息,Javamail提供了一系列api用于設置常見的郵件表頭,其中在涉及地址的操作時,我們用InternetAddress來進行封裝:
????? msg.setFrom(new InternetAddress ("acmilan@126.com");
????? msg.setSubject("Hello");
????? msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse("intermilan@126.com", false));
????? msg.setText("I will beat u");
????? msg.setSentDate(new Date());
????? //發送消息
????? Transport.send(msg);
? 對Transport類的調用將會去查找適當的會話,并找出如何發送消息,盡管這樣做看上去有些不直觀。當我們完成這一步的時候,我們的郵件就已經發送出去了。此時,我們還需要添加代碼來捕獲三種JavaMail可能拋出的異常,它們是AddressException、MessagingException和UnsupportedEncodingException. 但這就是最基本的使用JavaMail發送郵件的方法。
? 有時候我們還需要給郵件添加附件.再回到之前對Message的討論中,Message對象同樣定義了郵件的內容,它可以定義一個消息內容,也可以定義多個消息內容,消息內容(通常指的是附件)都將由DataHandle下的類來處理.Message對象由Multipart組成,一個Multipart可含有多個BodyPart,這些BodyPart將用來保存文本信息和附件.
????? MimeMultipart multipart = new MimeMultipart();
????? BodyPart msgBodyPart = new MimeBodyPart();//用來放置文本內容
????? msgBodyPart.setContent(message, "text/plain");
????? BodyPart attBodyPart = new MimeBodyPart();//用來放置附件
????? DataSource ds = new FileDataSource(new File("c:/td.txt"));
????? attBodyPart.setDataHandler(new DataHandler(ds));//設置DataHandler
????? attBodyPart.setFileName("bsbs.txt");//附件的顯示名字
????? multipart.addBodyPart(msgBodyPart);
????? multipart.addBodyPart(attBodyPart);
????? msg.setContent(multipart);
????? Transport.send(msg);
?? 最后,我們來看看如何發送HTML格式的郵件,文本的格式必須相應的設置為text/html,郵件中的圖片將以附件形式加載,另外還要指定一個內部ID以供調用;
????? MimeMultipart multipart = new MimeMultipart();
????? BodyPart msgBodyPart = new MimeBodyPart();
????? //設置格式為"text/html"
????? msgBodyPart.setContent("<H1>Hi! From HtmlJavaMail</H1> <img src=\"cid:logo\"/>", "text/html");
????? BodyPart embedImage = new MimeBodyPart();
????? DataSource ds = new URLDataSource(new URL("????? embedImage.setDataHandler(new DataHandler(ds));
????? //設置表頭的內部ID,注意,所設置內容必須與前文對應,在此處,前文的引用為<img src=\"cid:logo\"/>,因此Content-ID表頭對應
????? //的應該是<logo>
????? embedImage.setHeader("Content-ID", "<logo>");
????? multipart.addBodyPart(msgBodyPart);
????? multipart.addBodyPart(embedImage);
????? msg.setContent(multipart);
?? 這樣,一封HTML格式的郵件便完成了
????? Transport.send(msg);
二.郵件的收取
? 同樣,第一步還是要獲得服務器的名字.我們還是以網易為例.
? 接收郵件包含兩個協議,即POP3和IMAP。POP3是老協議,它提供一個單一收信箱,以存放一定順序的郵件信息。IMAP相對比較新,它為郵件提供連接到一個層次關系的文件入口,其中一個入口即為收信箱。當然還有其它可以使用的協議,POP3和IMAP只是其中一種安全性很好的協議。JavaMail將這些協議提煉為一種郵件倉庫(Store)的概念,這一倉庫為文件等級的集合。這種提煉意味著倉庫包含很多內容,但我們只需要弄清楚在一個服務文件夾中瀏覽與導航郵件信息的過程,而實際處理郵件協議的任務則通過JavaMail調用Provider來完成,不同的協議對應不同的provider。
? JavaMail的Provider操作服務于POP3和IMAP,你也可以將另外的Providers嵌入到JavaMail API以處理其它諸如NNTP或本地存儲郵件的協議。在Sun主頁上列出這方面的第三方Providers。
? 在Javamail中,store和folder類是用來存儲和接受消息.store由具體的session得到,它使用可帶參數的connect方法與服務器連接,folder則和File有點類似,可以將其比作windows下的文件夾.客戶由store類中取得folder,再對folder進行操作--進入特定的folder,讀取folder中的消息.
???? Session session;
???? Store store = null;
???? Folder folder = null;
???? Folder inboxfolder = null;
????
???? Properties props = System.getProperties();
???? props.setProperty("mail.pop3s.rsetbeforequit", "true");//這樣讀取郵件時服務器不會刪除原有的郵件
???? props.setProperty("mail.pop3.rsetbeforequit", "true");
???? session = Session.getInstance(props, null);
???? store = session.getStore("pop3");//通過"pop3"得到適當的provider
???? store.connect(emailserver, emailuser, emailpassword);
???? folder = store.getDefaultFolder();//得到默認的頂級文件夾
? 每一Folder可以包含很多其它的文件夾,通過getFolder方法來瀏覽特定文件夾。INBOX是郵件服務器表示的主文件夾保留的名稱,pop3和imap存儲都會提供一個INBOX文件夾.
???? inboxfolder = folder.getFolder("INBOX");
???? inboxfolder.open(Folder.READ_ONLY);//只讀模式
???? Message[] msgs = inboxfolder.getMessages();
? 需要注意的是,此時真正的消息對象并沒有存儲到數組(msgs)中,數組保存的只是這些消息對象的引用,只有在調用了具體的Message.getXX()方法時,程序才會再次連接服務器并取得真正的結果.如果要進行對郵件的篩選工作,不停的調用每個消息對象的getXX方法無疑會影響性能.因此我們可以用FetchProfile類來預先抓取感興趣的部分內容(如各表頭內容),這就不需要每次調用getXX方法時都再連接服務器了.
???? FetchProfile fp = new FetchProfile();
???? fp.add("Subject");//即只讀取主題信息
???? inboxfolder.fetch(msgs, fp);//預讀取每個消息的主題
???? for (int j = 0 ;j <msgs.length; j++) {
??????? if (msgs[j].getSubject().startsWith("^_^")) {//只對標題為"^_^"的郵件進行操作
????????? .......
??????? }
? 接下來,就可以調用Message的各種方法對郵件進行操作了.對不同格式的郵件,具體的操作當然也略微不同.一個做法是對每個具體的BodyPart進行操作,通過下列的遞歸方法可以獲得每個BodyPart的引用
???? private void extractPart(final Part part) throws MessagingException,IOException {
?????? if(part.getContent() instanceof Multipart) {
?????????? Multipart mp=(Multipart)part.getContent();
?????????? for (int i = 0; i < mp.getCount(); i++) {
?????????????? extractPart(mp.getBodyPart(i));
?????????? }
????? return;
??? }
? Part的getContentType方法可以為我們對其采取何種方法處理提供依據
???? part.getContentType().startsWith("text/plain").....
? 如果是以"text/plain"或者"text/html"開頭,通常我們可以直接取出其內容
??? bodytext = (String) part.getContent();
? 如果兩者都不是,我們將其視之為附件,通過IO流來讀取:
????? InputStream in = part.getInputStream();//讀取part中的附件
????? ByteArrayOutputStream bos = new ByteArrayOutputStream();
????? byte[] buffer = new byte[8192];
????? int count = 0;
????? while ( (count = in.read(buffer)) >= 0) {
??????? bos.write(buffer, 0, count);
????? }
????? in.close();
? 接下來,就可以對流進行操作了.
? 那么,關于Javamail的研究也就進行到這兒了.
參考文獻:
? DJ Walker-Morgan????????? Getting the mail in: receiving in JavaMail
?????????????????????????????????????Sending email in Java: There's more than one way
? 趙強,喬新亮???????????????????J2EE應用開發