Java学习

java,spring,structs,hibernate,jsf,ireport,jfreechart,jasperreport,tomcat,jboss -----本博客已经搬家了,新的地址是 http://www.javaly.cn 如果有对文章有任何疑问或者有任何不懂的地方,欢迎到www.javaly.cn (Java乐园)指出,我会尽力帮助解决。一起进步

 

java邮件:在简单和复杂之间的方案

/**
*作者:张荣华(ahuaxuan)
*2007-07-11
*转载请注明出处及作者
*/

Javamail,论坛上由已经有很多的讨论,但是俺觉得还是不够完整,不完整不是说讲的不细致,而是指不全面,而是缺少high level的全面论述,所以俺来补充一下。

这篇文章的名字起得很古怪(估计还有人暗地里说文章名字取得如何如何,文章实质却是水货等等了,先不忙下结论,各位看官接着往下看便知),叫简单和复杂之 间,为什么要取这么个奇怪的名字,搞得人一头雾水,其实我想要表达的意思是这样的,之前坛子上有很多人讨论过如何使用javamail(包括spring 对其的封装),也有人讨论过如何通过jms发送emal,一个是简单的api介绍,一个是比较复杂的异步方案,但是试问除了简单使用其api难道就只能使 用jms来进行异步发送了吗,我们可以再找到一种介于这两者之间的方案,就是concurrent(我的建议是在普通的web应用中邮件发送不需要用 jms,但是最好也不要使用同步发送,所以普通的web应该使用concurrent来进行异步邮件发送应该是比较好的选择)。

在普通的web应用中,发送邮件应该只能算小任务,而使用jms来发送邮件有点杀鸡用牛刀的味道,那么如果能建立一个线程池来管理这些小线程并重复使用他 们,应该来说是一个简单有效的方案,我们可以使用concurrent包中的Executors来建立线程池,Executors是一个工厂,也是一个工 具类,我把它的api的介绍简单的翻译了一下(如果翻译有误请大家不要吝啬手中的砖头)

方  法 说  明
newCachedThreadPool() 创建一个包含新线程的线程池,池中线程的数量需要预先指定,该线程池会复用之前创建的线程(前提是该线程还是有效线程)。如果你的要执行的任务是短生命周期的任务的话,使用这种池提高性能是很具代表性的。这个方法有一个重载
newFixedThreadPool() 创建一个线程池以复用指定数量的线程,如果当所有线程都是活动状态时(指这些线程都在运行),那么新的任务将会等待,知道有空余的线程。如果有任何一个线 程因为在运行中发生错误而终结(非正常shutdown),那么如果有新的任务要并发处理,concurrent就会创建一个新的线程放入池中。
newSingleThreadExecutor() 创建一个使用单工作线程的executor,
newScheduledThreadPool() 可调度的线程池,池中的线程可以在某一时间延迟之后执行,也可以周期性执行
newSingleThreadScheduledExecutor() 单一可调度的线程


上面我重点解释了newFixedThreadPool(),因为我们将使用newFixedThreadPool方法来创建一个线程池,这个线程池中存放的线程就是我们用来发送邮件的。代码如下:
Java代码 复制代码
  1. /** 
  2.  * 由spring管理的线程池类,返回的ExecutorService就是给我们来执行线程的 
  3. *如果不交给spring管理也是可以的,可以使用单例模式来实现同样功能,但是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. }  



这样我们就初始化了线程池的大小,接下来就是如何使用这个线程池中的线程了,我们看看MailService是如何来使用线程池中的线程的,这个类中的代码我已经作了详细的解释
Java代码 复制代码
  1. /** 
  2.  * 用来发送 mail 的 service, 其中有一个内部类专门用来供线程使用 
  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.     //设置发件人  
  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.      * 简单的邮件发送接口,感兴趣的同学可以在这个基础上继续添加 
  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.      * 发送复杂格式邮件的接口,可以添加附件,图片,等等,但是需要修改这个方法, 
  56.      * 如何做到添加附件和图片论坛上有例子了,需要的同学搜一下, 
  57.      * 事实上这里的text参数最好是来自于模板,用模板生成html页面,然后交给javamail去发送, 
  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.      * 用来发送邮件的 Runnable, 该类是一个内部类,之所以使用内部类,而没有使用嵌套类(静态内部类), 
  114.      * 是因为内部类可以之间得到 service 的 javaMailSender 
  115.      * 每次发送邮件都会从线程池中取一个线程, 然后进行发邮件操作 
  116.      * @author ahuaxuan 
  117.      */  
  118.     private class MailRunner implements Runnable {  
  119.         SimpleMailMessage simpleMailMessage;  
  120.         MimeMessage mimeMessage;   
  121.           
  122.         /** 
  123.          * 构造简单文本邮件 
  124.          * @param simpleMailMessage 
  125.          */  
  126.         public MailRunner(SimpleMailMessage simpleMailMessage) {  
  127.             if (mimeMessage == null) {  
  128.                 this.simpleMailMessage = simpleMailMessage;  
  129.             }  
  130.         }  
  131.           
  132.         /** 
  133.          * 构造复杂邮件,可以添加附近,图片,等等 
  134.          * @param mimeMessage 
  135.          */  
  136.         public MailRunner(MimeMessage mimeMessage) {  
  137.             if (simpleMailMessage == null) {  
  138.                 this.mimeMessage = mimeMessage;  
  139.             }  
  140.         }  
  141.           
  142.         /** 
  143.          * 该方法将在线程池中的线程中执行 
  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是对邮件的抽象(我只使用了失血模型,事实上我们也可以让这个EmailEntity来实现 Runnable接口,这样Service中的内部类就可以去掉了,同时service中的大部分代码就要搬到EmailEntity及其父类里了,大家 更倾向于怎么做呢?),代码如下:
Java代码 复制代码
  1. /** 
  2.  * 该类是对邮件的抽象,邮件有哪些属性,这个类就有哪些属性 显然这个只是一个例子, 
  3.  * 这个例子中附带mimemessage发送所需的附件或者图片(如果有的话) 
  4.  * 需要使用的同学自己扩展 
  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的配置文件中配置这些类了,我相信对熟悉spring的人来说这不是什么大问题:
Java代码 复制代码
  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>  


经过这么一番折腾之后,一个邮件发送的雏形就完成了,接着需要什么样的邮件发送功能就可以随意往MailService里添加内容了, 而如果需要用模板来生成html格式的邮件真的需要看http://www.javaeye.com/topic/71430这个贴了,无论你是想用velocity还是想用freemarker来做模板引擎,这个贴中的例子都是可以直接拿来使用的

总结,如果自己起线程来发送邮件是一个非常危险的事情,如果并发一高(比如超过20),服务器估计就快撑不住了,而如果使用jms来异步发送邮件,学习的 曲线高,成本也高,我不建议为了一个小小的邮件发送就在项目中导入jms(之所以这样说是因为还有很多项目就是基于webservice的,那么使用 jms来调度webservice是一个不错的选择),所以使用线程池来实现这个异步的功能既安全又简单,这个例子是开源的,大家可以在自己的项目中随意 修改,随意封装。

要注意的是,concurrent在jdk5.0以上版本中才有,如果你使用的是1.4的jdk需要单独下载concurrent包。

作者:张荣华,未经作者同意不得随意转载!

posted on 2008-10-16 08:54 找个美女做老婆 阅读(750) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

公告

本blog已经搬到新家了, 新家:www.javaly.cn
 http://www.javaly.cn

常用链接

留言簿(6)

随笔档案

文章档案

搜索

最新评论

阅读排行榜

评论排行榜