Java學(xué)習(xí)

          java,spring,structs,hibernate,jsf,ireport,jfreechart,jasperreport,tomcat,jboss -----本博客已經(jīng)搬家了,新的地址是 http://www.javaly.cn 如果有對(duì)文章有任何疑問或者有任何不懂的地方,歡迎到www.javaly.cn (Java樂園)指出,我會(huì)盡力幫助解決。一起進(jìn)步

           

          java郵件:在簡單和復(fù)雜之間的方案

          /**
          *作者:張榮華(ahuaxuan)
          *2007-07-11
          *轉(zhuǎn)載請(qǐng)注明出處及作者
          */

          Javamail,論壇上由已經(jīng)有很多的討論,但是俺覺得還是不夠完整,不完整不是說講的不細(xì)致,而是指不全面,而是缺少high level的全面論述,所以俺來補(bǔ)充一下。

          這篇文章的名字起得很古怪(估計(jì)還有人暗地里說文章名字取得如何如何,文章實(shí)質(zhì)卻是水貨等等了,先不忙下結(jié)論,各位看官接著往下看便知),叫簡單和復(fù)雜之 間,為什么要取這么個(gè)奇怪的名字,搞得人一頭霧水,其實(shí)我想要表達(dá)的意思是這樣的,之前壇子上有很多人討論過如何使用javamail(包括spring 對(duì)其的封裝),也有人討論過如何通過jms發(fā)送emal,一個(gè)是簡單的api介紹,一個(gè)是比較復(fù)雜的異步方案,但是試問除了簡單使用其api難道就只能使 用jms來進(jìn)行異步發(fā)送了嗎,我們可以再找到一種介于這兩者之間的方案,就是concurrent(我的建議是在普通的web應(yīng)用中郵件發(fā)送不需要用 jms,但是最好也不要使用同步發(fā)送,所以普通的web應(yīng)該使用concurrent來進(jìn)行異步郵件發(fā)送應(yīng)該是比較好的選擇)。

          在普通的web應(yīng)用中,發(fā)送郵件應(yīng)該只能算小任務(wù),而使用jms來發(fā)送郵件有點(diǎn)殺雞用牛刀的味道,那么如果能建立一個(gè)線程池來管理這些小線程并重復(fù)使用他 們,應(yīng)該來說是一個(gè)簡單有效的方案,我們可以使用concurrent包中的Executors來建立線程池,Executors是一個(gè)工廠,也是一個(gè)工 具類,我把它的api的介紹簡單的翻譯了一下(如果翻譯有誤請(qǐng)大家不要吝嗇手中的磚頭)

          方  法 說  明
          newCachedThreadPool() 創(chuàng)建一個(gè)包含新線程的線程池,池中線程的數(shù)量需要預(yù)先指定,該線程池會(huì)復(fù)用之前創(chuàng)建的線程(前提是該線程還是有效線程)。如果你的要執(zhí)行的任務(wù)是短生命周期的任務(wù)的話,使用這種池提高性能是很具代表性的。這個(gè)方法有一個(gè)重載
          newFixedThreadPool() 創(chuàng)建一個(gè)線程池以復(fù)用指定數(shù)量的線程,如果當(dāng)所有線程都是活動(dòng)狀態(tài)時(shí)(指這些線程都在運(yùn)行),那么新的任務(wù)將會(huì)等待,知道有空余的線程。如果有任何一個(gè)線 程因?yàn)樵谶\(yùn)行中發(fā)生錯(cuò)誤而終結(jié)(非正常shutdown),那么如果有新的任務(wù)要并發(fā)處理,concurrent就會(huì)創(chuàng)建一個(gè)新的線程放入池中。
          newSingleThreadExecutor() 創(chuàng)建一個(gè)使用單工作線程的executor,
          newScheduledThreadPool() 可調(diào)度的線程池,池中的線程可以在某一時(shí)間延遲之后執(zhí)行,也可以周期性執(zhí)行
          newSingleThreadScheduledExecutor() 單一可調(diào)度的線程


          上面我重點(diǎn)解釋了newFixedThreadPool(),因?yàn)槲覀儗⑹褂胣ewFixedThreadPool方法來創(chuàng)建一個(gè)線程池,這個(gè)線程池中存放的線程就是我們用來發(fā)送郵件的。代碼如下:
          Java代碼 復(fù)制代碼
          1. /** 
          2.  * 由spring管理的線程池類,返回的ExecutorService就是給我們來執(zhí)行線程的 
          3. *如果不交給spring管理也是可以的,可以使用單例模式來實(shí)現(xiàn)同樣功能,但是poolSize   *要hardcode了 
          4.  * @author 張榮華(ahuaxuan) 
          5. * @version $Id$ 
          6.  */  
          7. public class EasyMailExecutorPool implements InitializingBean {  
          8.   
          9.     //線程池大小,spring配置文件中配置  
          10.     private int poolSize;  
          11.     private ExecutorService service;  
          12.   
          13.     public ExecutorService getService() {  
          14.         return service;  
          15.     }  
          16.   
          17.     public int getPoolSize() {  
          18.         return poolSize;  
          19.     }  
          20.   
          21.     public void setPoolSize(int poolSize) {  
          22.         this.poolSize = poolSize;  
          23.     }  
          24.   
          25.     /** 
          26.      * 在 bean 被初始化成功之后初始化線程池大小 
          27.      */  
          28.     public void afterPropertiesSet() throws Exception {  
          29.         service = Executors.newFixedThreadPool(poolSize);  
          30.     }  
          31. }  



          這樣我們就初始化了線程池的大小,接下來就是如何使用這個(gè)線程池中的線程了,我們看看MailService是如何來使用線程池中的線程的,這個(gè)類中的代碼我已經(jīng)作了詳細(xì)的解釋
          Java代碼 復(fù)制代碼
          1. /** 
          2.  * 用來發(fā)送 mail 的 service, 其中有一個(gè)內(nèi)部類專門用來供線程使用 
          3.  * @author 張榮華(ahuaxuan) 
          4.  * @since 2007-7-11 
          5.  * @version $Id$ 
          6.  */  
          7. public class EasyMailServieImpl implements EasyMailService{  
          8.     private static transient Log logger = LogFactory.getLog(EasyMailServieImpl.class);   
          9.       
          10.     //注入MailSender  
          11.     private JavaMailSender javaMailSender;  
          12.       
          13.     //注入線程池  
          14.     private EasyMailExecutorPool easyMailExecutorPool;  
          15.       
          16.     //設(shè)置發(fā)件人  
          17.     private String from;  
          18.       
          19.     public void setEasyMailExecutorPool(EasyMailExecutorPool easyMailExecutorPool) {  
          20.         this.easyMailExecutorPool = easyMailExecutorPool;  
          21.     }  
          22.   
          23.     public void setJavaMailSender(JavaMailSender javaMailSender) {  
          24.         this.javaMailSender = javaMailSender;  
          25.     }  
          26.       
          27.     public void setFrom(String from) {  
          28.         this.from = from;  
          29.     }  
          30.   
          31.     /** 
          32.      * 簡單的郵件發(fā)送接口,感興趣的同學(xué)可以在這個(gè)基礎(chǔ)上繼續(xù)添加 
          33.      * @param to 
          34.      * @param subject 
          35.      * @param text 
          36.      */  
          37.     public void sendMessage(EmailEntity email){  
          38.         if (null == email) {  
          39.             if (logger.isDebugEnabled()) {  
          40.                 logger.debug("something you need to tell here");  
          41.             }  
          42.             return;  
          43.         }  
          44.         SimpleMailMessage simpleMailMessage = new SimpleMailMessage();  
          45.           
          46.         simpleMailMessage.setTo(email.getTo());  
          47.         simpleMailMessage.setSubject(email.getSubject());  
          48.         simpleMailMessage.setText(email.getText());  
          49.         simpleMailMessage.setFrom(from);  
          50.           
          51.         easyMailExecutorPool.getService().execute(new MailRunner(simpleMailMessage));  
          52.     }  
          53.       
          54.     /** 
          55.      * 發(fā)送復(fù)雜格式郵件的接口,可以添加附件,圖片,等等,但是需要修改這個(gè)方法, 
          56.      * 如何做到添加附件和圖片論壇上有例子了,需要的同學(xué)搜一下, 
          57.      * 事實(shí)上這里的text參數(shù)最好是來自于模板,用模板生成html頁面,然后交給javamail去發(fā)送, 
          58.      * 如何使用模板來生成html見 {@link http://www.javaeye.com/topic/71430 } 
          59.      *  
          60.      * @param to 
          61.      * @param subject 
          62.      * @param text 
          63.      * @throws MessagingException 
          64.      */  
          65.     public void sendMimeMessage(EmailEntity email) throws MessagingException {  
          66.         if (null == email) {  
          67.             if (logger.isDebugEnabled()) {  
          68.                 logger.debug("something you need to tell here");  
          69.             }  
          70.             return;  
          71.         }  
          72.         MimeMessage message = javaMailSender.createMimeMessage();  
          73.         MimeMessageHelper helper = new MimeMessageHelper(message);  
          74.           
          75.         helper.setTo(email.getTo());  
          76.         helper.setFrom(from);  
          77.         helper.setSubject(email.getSubject());  
          78.           
          79.         this.addAttachmentOrImg(helper, email.getAttachment(), true);  
          80.         this.addAttachmentOrImg(helper, email.getImg(), false);  
          81.           
          82.         //這里的text是html格式的, 可以使用模板引擎來生成html模板, velocity或者freemarker都可以做到  
          83.         helper.setText(email.getText(),true);  
          84.           
          85.         easyMailExecutorPool.getService().execute(new MailRunner(message));  
          86.     }  
          87.       
          88.     /** 
          89.      * 添加附件或者是圖片 
          90.      * @param helper 
          91.      * @param map 
          92.      * @param isAttachment 
          93.      * @throws MessagingException 
          94.      */  
          95.     private void addAttachmentOrImg(MimeMessageHelper helper, Map map, boolean isAttachment) throws MessagingException {  
          96.         for (Iterator it = map.entrySet().iterator(); it.hasNext();) {  
          97.             Map.Entry entry = (Map.Entry) it.next();  
          98.             String key = (String) entry.getKey();  
          99.             String value = (String) entry.getValue();  
          100.             if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {  
          101.                 FileSystemResource file = new FileSystemResource(new File(value));  
          102.                 if (!file.exists()) continue;  
          103.                 if (isAttachment) {  
          104.                     helper.addAttachment(key, file);  
          105.                 } else {  
          106.                     helper.addInline(key, file);  
          107.                 }  
          108.             }  
          109.         }  
          110.     }  
          111.       
          112.     /** 
          113.      * 用來發(fā)送郵件的 Runnable, 該類是一個(gè)內(nèi)部類,之所以使用內(nèi)部類,而沒有使用嵌套類(靜態(tài)內(nèi)部類), 
          114.      * 是因?yàn)閮?nèi)部類可以之間得到 service 的 javaMailSender 
          115.      * 每次發(fā)送郵件都會(huì)從線程池中取一個(gè)線程, 然后進(jìn)行發(fā)郵件操作 
          116.      * @author ahuaxuan 
          117.      */  
          118.     private class MailRunner implements Runnable {  
          119.         SimpleMailMessage simpleMailMessage;  
          120.         MimeMessage mimeMessage;   
          121.           
          122.         /** 
          123.          * 構(gòu)造簡單文本郵件 
          124.          * @param simpleMailMessage 
          125.          */  
          126.         public MailRunner(SimpleMailMessage simpleMailMessage) {  
          127.             if (mimeMessage == null) {  
          128.                 this.simpleMailMessage = simpleMailMessage;  
          129.             }  
          130.         }  
          131.           
          132.         /** 
          133.          * 構(gòu)造復(fù)雜郵件,可以添加附近,圖片,等等 
          134.          * @param mimeMessage 
          135.          */  
          136.         public MailRunner(MimeMessage mimeMessage) {  
          137.             if (simpleMailMessage == null) {  
          138.                 this.mimeMessage = mimeMessage;  
          139.             }  
          140.         }  
          141.           
          142.         /** 
          143.          * 該方法將在線程池中的線程中執(zhí)行 
          144.          */  
          145.         public void run() {  
          146.             try {  
          147.                 if (simpleMailMessage != null) {  
          148.                     javaMailSender.send(this.simpleMailMessage);  
          149.                 } else if (mimeMessage != null) {  
          150.                     javaMailSender.send(this.mimeMessage);  
          151.                 }  
          152.                   
          153.             } catch (Exception e) {  
          154.                 if (logger.isDebugEnabled()) {  
          155.                     logger.debug("logger something here", e);  
          156.                 }  
          157.             }       
          158.         }  
          159.     }  
          160. }  


          MailService中的EmailEntity是對(duì)郵件的抽象(我只使用了失血模型,事實(shí)上我們也可以讓這個(gè)EmailEntity來實(shí)現(xiàn) Runnable接口,這樣Service中的內(nèi)部類就可以去掉了,同時(shí)service中的大部分代碼就要搬到EmailEntity及其父類里了,大家 更傾向于怎么做呢?),代碼如下:
          Java代碼 復(fù)制代碼
          1. /** 
          2.  * 該類是對(duì)郵件的抽象,郵件有哪些屬性,這個(gè)類就有哪些屬性 顯然這個(gè)只是一個(gè)例子, 
          3.  * 這個(gè)例子中附帶mimemessage發(fā)送所需的附件或者圖片(如果有的話) 
          4.  * 需要使用的同學(xué)自己擴(kuò)展 
          5.  *  
          6.  * @author 張榮華(ahuaxuan) 
          7. * @version $Id$ 
          8.  */  
          9. public class EmailEntity {  
          10.   
          11.     String to;  
          12.   
          13.     String subject;  
          14.   
          15.     String text;  
          16.   
          17.     //郵件附件  
          18.     Map<String, String> attachment = new HashMap<String, String>();  
          19.   
          20.     //郵件圖片  
          21.     Map<String, String> img = new HashMap<String, String>();  
          22.     //這里省去大段的getter和setter方法  
          23. }  

          接下來就是在spring的配置文件中配置這些類了,我相信對(duì)熟悉spring的人來說這不是什么大問題:
          Java代碼 復(fù)制代碼
          1. <beans>  
          2.     <bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl" autowire="byName">  
          3.         <property name="host" value="${mail.host}"/>  
          4.         <property name="username" value="${mail.username}"/>  
          5.         <property name="password" value="${mail.password}"/>  
          6.     </bean>  
          7.   
          8.     <bean id="easyMailExecutorPool" class="org.zhangronghua.easymail.EasyMailExecutorPool" autowire="byName">  
          9.         <property name="poolSize">  
          10.             <value>5</value>  
          11.         </property>  
          12.     </bean>  
          13.       
          14.     <bean id="easyMailService" class="org.zhangronghua.easymail.EasyMailServieImpl" autowire="byName">  
          15.         <property name="from" value="${mail.default.from}"/>  
          16.     </bean>  
          17. </beans>  


          經(jīng)過這么一番折騰之后,一個(gè)郵件發(fā)送的雛形就完成了,接著需要什么樣的郵件發(fā)送功能就可以隨意往MailService里添加內(nèi)容了, 而如果需要用模板來生成html格式的郵件真的需要看http://www.javaeye.com/topic/71430這個(gè)貼了,無論你是想用velocity還是想用freemarker來做模板引擎,這個(gè)貼中的例子都是可以直接拿來使用的

          總結(jié),如果自己起線程來發(fā)送郵件是一個(gè)非常危險(xiǎn)的事情,如果并發(fā)一高(比如超過20),服務(wù)器估計(jì)就快撐不住了,而如果使用jms來異步發(fā)送郵件,學(xué)習(xí)的 曲線高,成本也高,我不建議為了一個(gè)小小的郵件發(fā)送就在項(xiàng)目中導(dǎo)入jms(之所以這樣說是因?yàn)檫€有很多項(xiàng)目就是基于webservice的,那么使用 jms來調(diào)度webservice是一個(gè)不錯(cuò)的選擇),所以使用線程池來實(shí)現(xiàn)這個(gè)異步的功能既安全又簡單,這個(gè)例子是開源的,大家可以在自己的項(xiàng)目中隨意 修改,隨意封裝。

          要注意的是,concurrent在jdk5.0以上版本中才有,如果你使用的是1.4的jdk需要單獨(dú)下載concurrent包。

          作者:張榮華,未經(jīng)作者同意不得隨意轉(zhuǎn)載!

          posted on 2008-10-16 08:54 找個(gè)美女做老婆 閱讀(763) 評(píng)論(0)  編輯  收藏


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           

          導(dǎo)航

          統(tǒng)計(jì)

          公告

          本blog已經(jīng)搬到新家了, 新家:www.javaly.cn
           http://www.javaly.cn

          常用鏈接

          留言簿(6)

          隨筆檔案

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 阳谷县| 红河县| 扎赉特旗| 左权县| 芜湖县| 连平县| 江陵县| 乌兰浩特市| 梅河口市| 黔西| 南安市| 咸宁市| 佛学| 天水市| 镇江市| 长岛县| 苍溪县| 四会市| 山东省| 监利县| 苍山县| 红安县| 缙云县| 闸北区| 黑山县| 察哈| 清远市| 闵行区| 稻城县| 包头市| 宕昌县| 都昌县| 元谋县| 东台市| 龙山县| 昭苏县| 浑源县| 阿拉善左旗| 和静县| 丰镇市| 宝应县|