利用Spring框架封裝的JavaMail實現同步或非同步郵件傳送
J2EE簡單地講是在JDK上擴充套件了各類應用的標準規範,郵件處理便是其中一個重要的應用。它既然是規範,那麼我們就可以通過JDK遵照郵件協議編寫一個郵件處理系統,但事實上已經有很多廠商和開源組織這樣做了。Apache是J2EE最積極的實現者之一,當然還有我們的老大——SUN。
聊起老大,感慨萬端!他已經加入Oracle——甲骨文(不是刻在烏龜殼上的那種文字嗎?是我中華,也是人類上最早的語言啊,比Java早幾千年哦),其掌門人拉里·埃裡森是個不錯的水手,別以為那只是在帆船上,至少他不至於蓋茨那麼不仁道——開源萬歲。有理由相信Java世界還有一段輝煌的歷程。Google的Android和Chrome OS兩大作業系統,還會首選Java作應用開發基礎語言,即便是推出自己的易語言。同時筆者預感到ChromeOS前景不可估量,它可能是推動雲端計算一個重要組成部分,Andtroid(主用在移動裝置上,未來可能是手機上主流的作業系統),乃至微軟的Windows將來可能都是該系統的小視窗而已。微軟已顯得老態龍鍾了,再與大勢已去的雅虎合作,前進步伐必將大大減緩。投資者此時可以長線買入Google股票(投資建議,必自判斷)。筆者也常用google搜尋引擎、gmail。
好了,閒話少聊,言歸主題。
可能大家如筆者一樣用的最多的是老大的javamail,雖然老大實現了郵件功能,但呼叫起來還是需要較複雜的程式碼來完成,而且初學者呼叫成功率很低(因為它還要與外界伺服器通訊),這就使得初學者對於它越學越迷茫。不過這方面的例子很多,因此筆者不再在此重複這些示例程式碼,而著重利用Spring框架封裝的郵件處理功能。
開工之前,我們先了解下環境。筆者開的是web工程,所需要的基礎配置如下:
▲ JDK 1.6
▲ J2EE 1.5
▲ JavaMail 1.4 稍作說明:J2EE 1.5中已經納入了郵件規範,因此在開發期不要匯入javamail中的jar包,執行期則需要,因此可以將jar包放入到web容器的java庫中(例如Tomcat的lib目錄下),要了解其意可以參考資料庫驅動包的運用。文章結尾會對其進一步說明;
▲ Spring 2.5
▲ 一個郵箱 如上所述,筆者愛用Google的Gmail郵箱。主要檔案清單:
■ MailService.java 郵件處理物件介面
■ MailServiceImpl.java 上述實現
■ Email.java 一個普通的JavaBean,用於封裝郵件資料,並與html頁面中form表單對應
■ MailController.java 動作處理器
■ spring-core-config.xml Spring核心配置
筆者的web工程中,WEB層使用的是Spring @MVC,當然我們僅需要了解其原理,利用servlet或struts框架來做web層動作處理器都能實現。其它的檔案,例如web.xml,WEB層配置均略。
下面開始程式碼:
spring-core-config.xml:<!--①郵件伺服器--> <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"> <property name="protocol" value="smtp"/> <property name="host" value="smtp.gmail.com"/> <property name="port" value="465" /><!--Gmail的SMTP埠居然是這個,去google網站上了解吧--> <property name="username" value="到google註冊一個gmail賬戶"/> <property name="password" value="這裡是密碼"/> <property name="javaMailProperties"> <props> <prop key="mail.smtp.auth">true</prop> <prop key="mail.smtp.starttls.enable">true</prop> <prop key="mail.smtp.socketFactory.class">javax.net.ssl.SSLSocketFactory</prop> <!--gmail要求的ssl連線--> </props> </property> </bean> <!--②非同步執行緒執行器--> <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="10"/> <property name="maxPoolSize" value="30"/> </bean>
這是郵件處理的兩個核心配置,第一個配置(①)是往容器中裝配一個JavaMailSender Bean,它就是JavaMail的封裝,其中最關鍵的是裝配過程的屬性引數,這些屬性既要嚴格遵照JavaMail規範,又要滿足郵件提供商的要求,例如SMTP伺服器埠是多少、傳送時是否要身份驗證、伺服器是否採用安全連線、連線時是否加密以及採用什麼樣的加密方式,郵件服務商提供的這些引數直接影響到上述的配置,這往往是新手最容易忽視的環節,因此配置之前一定要到郵件提供商的站點上詳細瞭解郵箱的技術引數。
同步非同步傳送問題:JavaMail郵件處理是同步的,即使用者觸發事件、與SMTP Server通訊、伺服器返回狀態訊息、程式結束是單執行緒內,這時往往因Socket通訊、伺服器業務處理速度等原因而使得處理時間是個未知數。舉個簡單的應用例項:若使用者在提交註冊的同時傳送一封啟用賬戶郵件,使用者有可能不知道是因為郵件伺服器那兒阻塞致半天沒有反應而以為註冊失敗並放棄,這將是失敗的設計,但非同步方式能解決這些問題。非同步方式簡單地說就是將郵件處理任務交給另外一個執行緒,J2EE有兩種解決方案,一是種利用JMS,JMS可以實現同步和非同步的訊息處理,將郵件作為一個非同步的訊息,就可以實現非同步郵件傳送。JMS屬於J2EE的高階應用,所以對於僅以WEB功能的容器還不支援這種服務,例如Tomcat(當然可以找到外掛來解決),由於篇幅限制,本文不再牽涉到新的模組。另一種方案是利用JDK中Executor的支援,JDK 5.0後繼版本增加了java.util.concurrent一個強大的併發工具包,它包含了執行器、計時器、鎖、執行緒安全佇列、執行緒任務框架等等。Executor——執行器,它可以將任務的“提交”與“執行”分離解耦,我們的郵件處理任務完全可以借用它實現非同步執行。而Spring框架提供了封裝,見②。下面我們來看如何使用它,程式碼如下。
MailServiceImpl.java :
package com.zhangjihao.service.impl;
import java.io.IOException;
import javax.annotation.Resource; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage;
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.task.TaskExecutor; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile;
import com.zhangjihao.bean.Email; import com.zhangjihao.service.MailService; import com.zhangjihao.util.StringUtil;
/** * 說明:<br> * * @author 張紀豪 * @version * Build Time Jul 24, 2009 */ @Service("mailService") public class MailServiceImpl implements MailService {
@Resource JavaMailSender mailSender;//注入Spring封裝的javamail,Spring的xml中已讓框架裝配 @Resource TaskExecutor taskExecutor;//注入Spring封裝的非同步執行器 private Log log = LogFactory.getLog(getClass()); private StringBuffer message = new StringBuffer(); public void sendMail(Email email) throws MessagingException, IOException { if(email.getAddress() == null || email.getAddress().length == 0){ this.message.append("沒有收件人"); return; } if(email.getAddress().length > 5){//收件人大於5封時,採用非同步傳送 sendMailByAsynchronousMode(email); this.message.append("收件人過多,正在採用非同步方式傳送...<br/>"); }else{ sendMailBySynchronizationMode(email); this.message.append("正在同步方式傳送郵件...<br/>"); } } /** * 非同步傳送 * @see com.zhangjihao.service.MailService#sendMailByAsynchronousMode(com.zhangjihao.bean.Email) */ public void sendMailByAsynchronousMode(final Email email){ taskExecutor.execute(new Runnable(){ public void run(){ try { sendMailBySynchronizationMode(email); } catch (Exception e) { log.info(e); } } }); } /** * 同步傳送 * @throws IOException * @see com.zhangjihao.service.MailServiceMode#sendMail(com.zhangjihao.bean.Email) */ public void sendMailBySynchronizationMode(Email email) throws MessagingException, IOException { MimeMessage mime = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mime, true, "utf-8"); helper.setFrom("[email protected]");//發件人 helper.setTo(email.getAddress());//收件人 helper.setBcc("[email protected]");//暗送 if(StringUtil.hasLength(email.getCc())){ String cc[] = email.getCc().split(";"); helper.setCc(cc);//抄送 } helper.setReplyTo("[email protected]");//回覆到 helper.setSubject(email.getSubject());//郵件主題 helper.setText(email.getContent(), true);//true表示設定html格式 //內嵌資源,這種功能很少用,因為大部分資源都在網上,只需在郵件正文中給個URL就足夠了. //helper.addInline("logo", new ClassPathResource("logo.gif")); //處理附件 for(MultipartFile file : email.getAttachment()){ if(file == null || file.isEmpty()){ continue; } String fileName = file.getOriginalFilename(); try { fileName = new String(fileName.getBytes("utf-8"),"ISO-8859-1"); } catch (Exception e) {} helper.addAttachment(fileName, new ByteArrayResource(file.getBytes())); } mailSender.send(mime); }
public StringBuffer getMessage() { return message; }
public void setMessage(StringBuffer message) { this.message = message; } }
此類實現了MailService介面,該介面僅三個方法(介面檔案程式碼省略):一個傳送分流器、一個同步傳送方法、一個非同步傳送方法。通過其實現者MailServiceImpl的程式碼可以看出,郵件傳送僅在同步傳送這個方法中,當需要非同步執行的時候,只需要將其扔進taskExecutor非同步執行器中,就這麼簡單。這三個方法都是public修飾的,所以在上層隨意呼叫哪個都行。以下看一個簡單的呼叫程式碼。
呼叫之前,為讓初學者能更好地接受,先列出Email.java程式碼:
Email.java:package com.zhangjihao.bean;
import java.io.Serializable;
import org.springframework.web.multipart.MultipartFile;
import com.zhangjihao.util.StringUtil;
/** * 說明:<br> * * @author 張紀豪 * @version * Build Time Jul 24, 2009 */ public class Email implements Serializable {
private static final long serialVersionUID = 9063903350324510652L; /**使用者組:可以按使用者組來批量傳送郵件**/ private UserGroups userGroups;
/**收件人**/ private String addressee; /**抄送給**/ private String cc; /**郵件主題**/ private String subject; /**郵件內容**/ private String content; /**附件**/ private MultipartFile[] attachment = new MultipartFile[0]; //////////////////////////解析郵件地址////////////////////////////// public String[] getAddress() { if(!StringUtil.hasLength(this.addressee)) { return null; } addressee = addressee.trim(); addressee.replaceAll(";", ";"); addressee.replaceAll(" ", ";"); addressee.replaceAll(",", ";"); addressee.replaceAll(",", ";"); addressee.replaceAll("|", ";"); return addressee.split(";"); }
/////////////////////////////Getter && Setter///////////////////////////////
...... }
這個類就是一個簡單的JavaBean,用於封裝郵件資料,對於習慣使用Struts框架的讀者,完全可以把它理解為一個ActionForm。但對於MultipartFile型別且是陣列的attachment屬性可能較難理解,熟悉Struts框架的可以看作是FormFile,在Struts2中可能好理解些。筆者使用的是Spring MVC,框架中內建了這種屬性編輯器,因此很容易地將form表單上傳的檔案進行轉換成這個欄位。
我們來看看WEB層呼叫,其實到此為止,就已經完成本文的主題了,因此WEB怎麼呼叫都是圍繞MailService中的三個方法,為便有全面的認識,將程式碼列出,不過最好需要了解Spring @MVC的一些知識。
MailController.java:
package com.zhangjihao.web.controller.system;
import java.util.List;
import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest;
import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam;
import com.zhangjihao.bean.Email; import com.zhangjihao.domain.user.User; import com.zhangjihao.service.MailService; import com.zhangjihao.service.UserService; import com.zhangjihao.util.StringUtil; import com.zhangjihao.web.controller.MasterController; import com.zhangjihao.web.validator.EmailValidator;
/** * 說明:<br> * 郵件傳送處理器 * @author 張紀豪 * @version * Build Time Jul 24, 2009 */ @Controller public class MailController extends MasterController {
@Resource MailService mailService; @Resource UserService userService;
@RequestMapping(value = "/sendEmail", method=RequestMethod.GET) public String sendEmail(@RequestParam(value="email",required=false) String singleEmailAddress , HttpServletRequest request){ Email email = new Email(); if(StringUtil.hasLength(singleEmailAddress)){ email.setAddressee(singleEmailAddress); } request.setAttribute("email", email); return "system/sendMail"; } @RequestMapping(value = "/sendEmail", method=RequestMethod.POST) public String send( @ModelAttribute Email email, //Spring MVC將form表單的資料封裝到這個物件中 BindingResult result, ModelMap model, HttpServletRequest request){ try { new EmailValidator().validate(email, result); if(result.hasErrors()){ throw new RuntimeException("資料填寫不正確"); } if(email.getEmailGroup()!=null){ List<User> users = userService.getUserByUserGroups(email.getEmailGroup(), "userName,email", null, null); StringBuffer sb = new StringBuffer(StringUtil.hasLengthBytrim(email.getAddressee()) ? (email.getAddressee().trim() + ";") : ""); for(User user : users){ sb.append(user.getEmail()).append(";"); } email.setAddressee(sb.toString()); } if(email.getAddress()==null || email.getAddress().length==0){ request.setAttribute("message", "沒有收件人!"); return "message"; } mailService.sendMail(email); //大於5個收件人時,分流器會自動選擇非同步方式傳送 request.setAttribute("message", mailService.getMessage().append("本次共傳送了 ").append(email.getAddress().length).append(" 封郵件.").toString()); } catch (Exception e) { request.setAttribute("message", "Has errors! The info by "+e.getMessage()+"<br/>Can log in to view more detailed information on abnormalities."); log.error(this.getClass().getName()+"中發生異常---------------:\n", e); } return BACK + "message"; } }
當一個get方法請求的連線進來,此控制器會轉向一個html頁面,其頁面中有form表單,表單中的欄位與Email.java對應,當post方法過來後,Spring MVC會把表單中的資料填充到Email物件中,交給MailService處理就ok了。
最後講述下最容易出現的錯誤:
網上很多人都說J2EE5相容性不好,例如典型的javamail1.4中包與J2EE5中包介面包引起衝突,導致單元測試經常報如下錯誤:
java.lang.NoClassDefFoundError: com/sun/mail/util/BEncoderStream
當然這個錯誤是沒有將javamail的實現者引進工程(沒有導包),但導包後,就會出現另外一個錯誤:
java.lang.NoClassDefFoundError: com/sun/mail/util/LineInputStream
此時甚至web容器都無法啟動,經常會有網友們為這兩個異常搞得焦頭爛額,如此更換J2EE1.4,會對工程造成影響。但是一定要把概念弄清楚,問題就好解決。J2EE5中mail.jar包定義的只是介面,沒有實現,是不能真正傳送郵件的,但開發編譯肯定是可以過去的,因為我們是針對J2EE規範編的程式。而執行期用Sun公司的JavaMail1.4的實現才可以開始傳送郵件,但老大為什麼把這兩個弄衝突了?
筆者的解決辦法是:
開發期不要導包,執行期將javamail1.4壓縮檔案中的mail.jar包放入到tomcat\lib目錄下,這樣完全可以通過開發和執行。若要做單元測試則新開一個Java Project,注意,不是web工程,此時可以將javamail1.4壓縮包中的mail.jar放入到工程的classpath下。
相關推薦
利用Spring框架封裝的JavaMail實現同步或非同步郵件傳送
J2EE簡單地講是在JDK上擴充套件了各類應用的標準規範,郵件處理便是其中一個重要的應用。它既然是規範,那麼我們就可以通過JDK遵照郵件協議編寫一個郵件處理系統,但事實上已經有很多廠商和開源組織這樣做了。Apache是J2EE最積極的實現者之一,當然還有我們的老大——SUN
利用Spring框架封裝的JavaMail現實郵件傳送
最近發現使用自己的POP3伺服器給使用者傳送郵件總是有不同型別的郵件收不到,比如我的郵件是地址是[email protected] 傳送給126郵箱可以,傳送給qq郵箱就不行。經過半天的研究和考證最終確定了方案使用qq的POP3/SMTP郵件伺服器,具體設定和配置如下:
利用Spring框架在前端實現對資料庫的增刪改查
在前端頁面上顯示購物資料庫資料,並且可以這增、刪、改、查 1.首先在WEB 配置檔案 <!-- 配置DispatcherServlet --> <servlet> <servlet-name>springmvc</serv
RabbitMQ系列之六 Spring RabbitMQ整合實現案例之 非同步郵件傳送
摘要:給使用者傳送郵件的場景,其實也是比較常見的,比如使用者註冊需要郵箱驗證,使用者異地登入傳送郵件通知等等,在這裡我以 RabbitMQ 實現非同步傳送郵件。 專案git地址:https://github.com/gitcaiqing/RabbitMQ-Email 1.專案結構 2.
6.Spring RabbitMQ整合實現案例之 非同步郵件傳送
摘要:給使用者傳送郵件的場景,其實也是比較常見的,比如使用者註冊需要郵箱驗證,使用者異地登入傳送郵件通知等等,在這裡我以 RabbitMQ 實現非同步傳送郵件。 專案git地址:https://github.com/gitcaiqing/RabbitMQ-Ema
Spring Aop底層原理詳解(利用spring後置處理器實現AOP)
寫在前面:對於一個java程式設計師來說,相信絕大多數都有這樣的面試經歷,面試官問:你知道什麼是aop嗎?談談你是怎麼理解aop的?等等諸如此類關於aop的問題。當然對於一些小白可能會一臉懵逼;對於一些工作一兩年的,可能知道,哦!aop就是面向切面變成,列印日誌啊,什麼什麼的,要是有點學
利用spring的AOP來實現Redis快取
為什麼使用Redis 資料查詢時每次都需要從資料庫查詢資料,資料庫壓力很大,查詢速度慢,因此設定快取層,查詢資料時先從redis中查詢,如果查詢不到,則到資料庫中查詢,然後將資料庫中查詢的資料放到redis中一份,下次查詢時就能直接從redis中查到,不需要查
如何運用Spring框架的@Async實現非同步任務
在此篇文章中,我們根據使用@Async註解進行探索Spring對非同步執行的支援。簡單的把@As
譯文:如何運用Spring框架的@Async實現非同步任務
概要說明 在此篇文章中,我們根據使用@Async註解進行探索Spring對非同步執行的支援。 簡單的把@Async註解放到Bean的方法上就會使用不同的執行緒執行,也就是說,呼叫者執行此方法不用一直等待整個方法執行完畢。 在Spring中比較有趣的一點就是
漫談 GOF 設計模式在 Spring 框架中的實現
原文地址:樑桂釗的部落格 部落格地址:http://blog.720ui.com 歡迎關注公眾號:「服務端思維」。一群同頻者,一
利用BenchmarkDotNet 測試 .Net Core API 同步和非同步方法效能
事由: 這兩天mentor給我佈置了個任務讓我用BenchmarkDotNet工具去測試一下同一個API 用同步和非同步方法寫效能上有什麼差別。 過程: 首先 我們需要在Nuget上安裝BenchMarkDotNet (安裝當前最新版本,當前我已經安裝好了) 但是卻安裝失敗出現兩個error。 其中一個是
Spring中@Async註解實現“方法”的非同步呼叫
簡單介紹: Spring為任務排程與非同步方法執行提供了註解支援。通過在方法上設定@Async註解,可使得方法被非同步呼叫。也就是說呼叫者會在呼叫時立即返回,而被呼叫方法的實際執行是交給Spring的TaskExecutor來完成。 開啟@Async註解: <task:annotation
Spring的事件和監聽器-同步與非同步
Application下抽象子類ApplicationContextEvent的下面有4個已經實現好的事件 ContextClosedEvent(容器關閉時) ContextRefreshedEvent(容器重新整理是) ContextStartedEvent(容器啟動
javamail實現用普通QQ郵箱傳送郵件
本人最近在寫一個Android專案,使用者註冊的時候想用郵箱驗證的方式,於是就需要在伺服器端傳送電子郵件給新註冊使用者,郵件內容中包含一個 連結, 當用戶點選這個連結將 登入到伺服器 的驗證邏輯。本人在網上找了很多程式碼,可能由於是很久以前的了,各大郵箱的規範
ActiveMQ入門系列之應用:Springboot+ActiveMQ+JavaMail實現非同步郵件傳送
現在郵件傳送功能已經是幾乎每個系統或網址必備的功能了,從使用者註冊的確認到找回密碼再到訊息提醒,這些功能普遍的會用到郵件傳送功能。我們都買過火車票,買完後會有郵件提醒,有時候郵件並不是買完票立馬就能收到郵件通知,這個就用到了非同步郵件傳送。 那怎麼實現郵件的非同步傳送呢? 很顯然,引入MQ是一個不錯的選擇
python 實現阿里雲VPS 郵件傳送測試
1.python SMTP 普通傳送郵件 #coding:utf-8 #強制使用utf-8編碼格式 import smtplib #載入smtplib模組 from email.mime.text 
C#實現的自定義郵件傳送類完整例項(支援多人多附件)
本文例項講述了C#實現的自定義郵件傳送類。分享給大家供大家參考,具體如下: /// <summary> /// 傳送郵件類 的摘要說明 /// </summary> class SendMail {
python簡單實現伺服器資訊自動郵件傳送
1、背景 As a tester,維護著測試環境10+臺伺服器,每臺伺服器上部署著若干個web應用,經常會出現伺服器的一些OOM,磁碟佔用100%,雖然有定期的一些指令碼做清理,但是依然會經常出現一些狀況,加之想用些小指令碼解決些問題,於是就開始整。 2、思路
Spring 框架——利用HandlerExceptionResolver實現全域性異常捕獲
一、需求描述 因為在專案中,我們不可否認的會出現異常,而且這些異常並沒有進行捕獲。經常出現的bug如空指標異常等等。在之前的專案中,如果我們沒有進行任何配置,那麼容器會自動列印錯誤的資訊,如果tomcat的404頁面,400頁面等等。如果我們在web.xml
Spring Security4.1.3實現攔截登錄後向登錄頁面跳轉方式(redirect或forward)返回被攔截界面
response href tools 當前 錯誤 界面 sets view 鏈接 一、看下內部原理 簡化後的認證過程分為7步: 用戶訪問網站,打開了一個鏈接(origin url)。 請求發送給服務器,服務器判斷用戶請求了受保護的資源。