/**
*作者:張榮華(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方法來創建一個線程池,這個線程池中存放的線程就是我們用來發送郵件的。代碼如下:
-
-
-
-
-
-
- public class EasyMailExecutorPool implements InitializingBean {
-
-
- private int poolSize;
- private ExecutorService service;
-
- public ExecutorService getService() {
- return service;
- }
-
- public int getPoolSize() {
- return poolSize;
- }
-
- public void setPoolSize(int poolSize) {
- this.poolSize = poolSize;
- }
-
-
-
-
- public void afterPropertiesSet() throws Exception {
- service = Executors.newFixedThreadPool(poolSize);
- }
- }
/**
* 由spring管理的線程池類,返回的ExecutorService就是給我們來執行線程的
*如果不交給spring管理也是可以的,可以使用單例模式來實現同樣功能,但是poolSize *要hardcode了
* @author 張榮華(ahuaxuan)
* @version $Id$
*/
public class EasyMailExecutorPool implements InitializingBean {
//線程池大小,spring配置文件中配置
private int poolSize;
private ExecutorService service;
public ExecutorService getService() {
return service;
}
public int getPoolSize() {
return poolSize;
}
public void setPoolSize(int poolSize) {
this.poolSize = poolSize;
}
/**
* 在 bean 被初始化成功之后初始化線程池大小
*/
public void afterPropertiesSet() throws Exception {
service = Executors.newFixedThreadPool(poolSize);
}
}
這樣我們就初始化了線程池的大小,接下來就是如何使用這個線程池中的線程了,我們看看MailService是如何來使用線程池中的線程的,這個類中的代碼我已經作了詳細的解釋
-
-
-
-
-
-
- public class EasyMailServieImpl implements EasyMailService{
- private static transient Log logger = LogFactory.getLog(EasyMailServieImpl.class);
-
-
- private JavaMailSender javaMailSender;
-
-
- private EasyMailExecutorPool easyMailExecutorPool;
-
-
- private String from;
-
- public void setEasyMailExecutorPool(EasyMailExecutorPool easyMailExecutorPool) {
- this.easyMailExecutorPool = easyMailExecutorPool;
- }
-
- public void setJavaMailSender(JavaMailSender javaMailSender) {
- this.javaMailSender = javaMailSender;
- }
-
- public void setFrom(String from) {
- this.from = from;
- }
-
-
-
-
-
-
-
- public void sendMessage(EmailEntity email){
- if (null == email) {
- if (logger.isDebugEnabled()) {
- logger.debug("something you need to tell here");
- }
- return;
- }
- SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
-
- simpleMailMessage.setTo(email.getTo());
- simpleMailMessage.setSubject(email.getSubject());
- simpleMailMessage.setText(email.getText());
- simpleMailMessage.setFrom(from);
-
- easyMailExecutorPool.getService().execute(new MailRunner(simpleMailMessage));
- }
-
-
-
-
-
-
-
-
-
-
-
-
- public void sendMimeMessage(EmailEntity email) throws MessagingException {
- if (null == email) {
- if (logger.isDebugEnabled()) {
- logger.debug("something you need to tell here");
- }
- return;
- }
- MimeMessage message = javaMailSender.createMimeMessage();
- MimeMessageHelper helper = new MimeMessageHelper(message);
-
- helper.setTo(email.getTo());
- helper.setFrom(from);
- helper.setSubject(email.getSubject());
-
- this.addAttachmentOrImg(helper, email.getAttachment(), true);
- this.addAttachmentOrImg(helper, email.getImg(), false);
-
-
- helper.setText(email.getText(),true);
-
- easyMailExecutorPool.getService().execute(new MailRunner(message));
- }
-
-
-
-
-
-
-
-
- private void addAttachmentOrImg(MimeMessageHelper helper, Map map, boolean isAttachment) throws MessagingException {
- for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
- Map.Entry entry = (Map.Entry) it.next();
- String key = (String) entry.getKey();
- String value = (String) entry.getValue();
- if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
- FileSystemResource file = new FileSystemResource(new File(value));
- if (!file.exists()) continue;
- if (isAttachment) {
- helper.addAttachment(key, file);
- } else {
- helper.addInline(key, file);
- }
- }
- }
- }
-
-
-
-
-
-
-
- private class MailRunner implements Runnable {
- SimpleMailMessage simpleMailMessage;
- MimeMessage mimeMessage;
-
-
-
-
-
- public MailRunner(SimpleMailMessage simpleMailMessage) {
- if (mimeMessage == null) {
- this.simpleMailMessage = simpleMailMessage;
- }
- }
-
-
-
-
-
- public MailRunner(MimeMessage mimeMessage) {
- if (simpleMailMessage == null) {
- this.mimeMessage = mimeMessage;
- }
- }
-
-
-
-
- public void run() {
- try {
- if (simpleMailMessage != null) {
- javaMailSender.send(this.simpleMailMessage);
- } else if (mimeMessage != null) {
- javaMailSender.send(this.mimeMessage);
- }
-
- } catch (Exception e) {
- if (logger.isDebugEnabled()) {
- logger.debug("logger something here", e);
- }
- }
- }
- }
- }
/**
* 用來發送 mail 的 service, 其中有一個內部類專門用來供線程使用
* @author 張榮華(ahuaxuan)
* @since 2007-7-11
* @version $Id$
*/
public class EasyMailServieImpl implements EasyMailService{
private static transient Log logger = LogFactory.getLog(EasyMailServieImpl.class);
//注入MailSender
private JavaMailSender javaMailSender;
//注入線程池
private EasyMailExecutorPool easyMailExecutorPool;
//設置發件人
private String from;
public void setEasyMailExecutorPool(EasyMailExecutorPool easyMailExecutorPool) {
this.easyMailExecutorPool = easyMailExecutorPool;
}
public void setJavaMailSender(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
public void setFrom(String from) {
this.from = from;
}
/**
* 簡單的郵件發送接口,感興趣的同學可以在這個基礎上繼續添加
* @param to
* @param subject
* @param text
*/
public void sendMessage(EmailEntity email){
if (null == email) {
if (logger.isDebugEnabled()) {
logger.debug("something you need to tell here");
}
return;
}
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setTo(email.getTo());
simpleMailMessage.setSubject(email.getSubject());
simpleMailMessage.setText(email.getText());
simpleMailMessage.setFrom(from);
easyMailExecutorPool.getService().execute(new MailRunner(simpleMailMessage));
}
/**
* 發送復雜格式郵件的接口,可以添加附件,圖片,等等,但是需要修改這個方法,
* 如何做到添加附件和圖片論壇上有例子了,需要的同學搜一下,
* 事實上這里的text參數最好是來自于模板,用模板生成html頁面,然后交給javamail去發送,
* 如何使用模板來生成html見 {@link http://www.javaeye.com/topic/71430 }
*
* @param to
* @param subject
* @param text
* @throws MessagingException
*/
public void sendMimeMessage(EmailEntity email) throws MessagingException {
if (null == email) {
if (logger.isDebugEnabled()) {
logger.debug("something you need to tell here");
}
return;
}
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setTo(email.getTo());
helper.setFrom(from);
helper.setSubject(email.getSubject());
this.addAttachmentOrImg(helper, email.getAttachment(), true);
this.addAttachmentOrImg(helper, email.getImg(), false);
//這里的text是html格式的, 可以使用模板引擎來生成html模板, velocity或者freemarker都可以做到
helper.setText(email.getText(),true);
easyMailExecutorPool.getService().execute(new MailRunner(message));
}
/**
* 添加附件或者是圖片
* @param helper
* @param map
* @param isAttachment
* @throws MessagingException
*/
private void addAttachmentOrImg(MimeMessageHelper helper, Map map, boolean isAttachment) throws MessagingException {
for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
FileSystemResource file = new FileSystemResource(new File(value));
if (!file.exists()) continue;
if (isAttachment) {
helper.addAttachment(key, file);
} else {
helper.addInline(key, file);
}
}
}
}
/**
* 用來發送郵件的 Runnable, 該類是一個內部類,之所以使用內部類,而沒有使用嵌套類(靜態內部類),
* 是因為內部類可以之間得到 service 的 javaMailSender
* 每次發送郵件都會從線程池中取一個線程, 然后進行發郵件操作
* @author ahuaxuan
*/
private class MailRunner implements Runnable {
SimpleMailMessage simpleMailMessage;
MimeMessage mimeMessage;
/**
* 構造簡單文本郵件
* @param simpleMailMessage
*/
public MailRunner(SimpleMailMessage simpleMailMessage) {
if (mimeMessage == null) {
this.simpleMailMessage = simpleMailMessage;
}
}
/**
* 構造復雜郵件,可以添加附近,圖片,等等
* @param mimeMessage
*/
public MailRunner(MimeMessage mimeMessage) {
if (simpleMailMessage == null) {
this.mimeMessage = mimeMessage;
}
}
/**
* 該方法將在線程池中的線程中執行
*/
public void run() {
try {
if (simpleMailMessage != null) {
javaMailSender.send(this.simpleMailMessage);
} else if (mimeMessage != null) {
javaMailSender.send(this.mimeMessage);
}
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("logger something here", e);
}
}
}
}
}
MailService中的EmailEntity是對郵件的抽象(我只使用了失血模型,事實上我們也可以讓這個EmailEntity來實現
Runnable接口,這樣Service中的內部類就可以去掉了,同時service中的大部分代碼就要搬到EmailEntity及其父類里了,大家
更傾向于怎么做呢?),代碼如下:
-
-
-
-
-
-
-
-
- public class EmailEntity {
-
- String to;
-
- String subject;
-
- String text;
-
-
- Map<String, String> attachment = new HashMap<String, String>();
-
-
- Map<String, String> img = new HashMap<String, String>();
-
- }
/**
* 該類是對郵件的抽象,郵件有哪些屬性,這個類就有哪些屬性 顯然這個只是一個例子,
* 這個例子中附帶mimemessage發送所需的附件或者圖片(如果有的話)
* 需要使用的同學自己擴展
*
* @author 張榮華(ahuaxuan)
* @version $Id$
*/
public class EmailEntity {
String to;
String subject;
String text;
//郵件附件
Map<String, String> attachment = new HashMap<String, String>();
//郵件圖片
Map<String, String> img = new HashMap<String, String>();
//這里省去大段的getter和setter方法
}
接下來就是在spring的配置文件中配置這些類了,我相信對熟悉spring的人來說這不是什么大問題:
- <beans>
- <bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl" autowire="byName">
- <property name="host" value="${mail.host}"/>
- <property name="username" value="${mail.username}"/>
- <property name="password" value="${mail.password}"/>
- </bean>
-
- <bean id="easyMailExecutorPool" class="org.zhangronghua.easymail.EasyMailExecutorPool" autowire="byName">
- <property name="poolSize">
- <value>5</value>
- </property>
- </bean>
-
- <bean id="easyMailService" class="org.zhangronghua.easymail.EasyMailServieImpl" autowire="byName">
- <property name="from" value="${mail.default.from}"/>
- </bean>
- </beans>
<beans>
<bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl" autowire="byName">
<property name="host" value="${mail.host}"/>
<property name="username" value="${mail.username}"/>
<property name="password" value="${mail.password}"/>
</bean>
<bean id="easyMailExecutorPool" class="org.zhangronghua.easymail.EasyMailExecutorPool" autowire="byName">
<property name="poolSize">
<value>5</value>
</property>
</bean>
<bean id="easyMailService" class="org.zhangronghua.easymail.EasyMailServieImpl" autowire="byName">
<property name="from" value="${mail.default.from}"/>
</bean>
</beans>
經過這么一番折騰之后,一個郵件發送的雛形就完成了,接著需要什么樣的郵件發送功能就可以隨意往MailService里添加內容了, 而如果需要用模板來生成html格式的郵件真的需要看
http://www.javaeye.com/topic/71430這個貼了,無論你是想用velocity還是想用freemarker來做模板引擎,這個貼中的例子都是可以直接拿來使用的
總結,如果自己起線程來發送郵件是一個非常危險的事情,如果并發一高(比如超過20),服務器估計就快撐不住了,而如果使用jms來異步發送郵件,學習的
曲線高,成本也高,我不建議為了一個小小的郵件發送就在項目中導入jms(之所以這樣說是因為還有很多項目就是基于webservice的,那么使用
jms來調度webservice是一個不錯的選擇),所以使用線程池來實現這個異步的功能既安全又簡單,這個例子是開源的,大家可以在自己的項目中隨意
修改,隨意封裝。
要注意的是,concurrent在jdk5.0以上版本中才有,如果你使用的是1.4的jdk需要單獨下載concurrent包。
作者:張榮華,未經作者同意不得隨意轉載!