ActiveMQ入門系列之應用:Springboot+ActiveMQ+JavaMail實現非同步郵件傳送
現在郵件傳送功能已經是幾乎每個系統或網址必備的功能了,從使用者註冊的確認到找回密碼再到訊息提醒,這些功能普遍的會用到郵件傳送功能。我們都買過火車票,買完後會有郵件提醒,有時候郵件並不是買完票立馬就能收到郵件通知,這個就用到了非同步郵件傳送。
那怎麼實現郵件的非同步傳送呢?
很顯然,引入MQ是一個不錯的選擇。剛好這段時間在練習ActiveMQ,那就拿activemq來實現非同步傳送郵件吧。
一、springboot整合JavaMailSender
在傳送非同步郵件之前,先來簡單介紹下郵件傳送的基本內容,瞭解郵件是怎麼傳送的,然後再在此基礎上新增activemq。
要傳送郵件就要用到JavaMail,它是Java官方為方便Java開發人員在應用程式中實現郵件傳送和接收功能而提供的一套標準開發包,它支援常見的郵件協議:SMTP/POP3/IMAP/MIME等。想要傳送郵件只需要呼叫JavaMail的API即可。後來,Spring對於JavaMail進行了封裝,然後springboot又進一步封裝,現在使用起來非常方便。請看程式碼:
- 新建springboot工程:mail-sender
- 新增配置檔案:application.properties
###mail config ### spring.mail.host=smtp.qq.com(配置郵件傳送協議) [email protected](發件人,具體配成你需要的郵箱) spring.mail.password=對於qq郵箱來說,這裡不是密碼,而是授權碼 spring.mail.default-encoding=utf-8 [email protected] (為了方便,我這裡將收件人統一配置成一個,實際業務中肯定按照實際情況傳送的)
至於授權碼的獲取,需要到qq郵箱裡面 設定->賬戶,然後到圖示的地方,開啟服務,然後根據提示獲取授權碼
- 接下來實現傳送郵件的程式碼
package com.mail.service.impl; import com.mail.service.MailService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.mail.internet.MimeMessage; import java.io.File; @Service public class MailServiceImpl implements MailService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private JavaMailSender mailSender;//注入JavaMailSender,具體傳送工作需要它完成 @Value("${spring.mail.username}")//從配置檔案中獲取發件人郵箱 public String from; /** * 傳送普通文字郵件 */ @Override public void sendSimpleMail(String to, String subject, String context){ SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setFrom(from);//發件人 mailMessage.setTo(to);//收件人 mailMessage.setSubject(subject);//郵件主題 mailMessage.setText(context);//郵件正文 mailSender.send(mailMessage);//傳送郵件 logger.info("郵件傳送成功"); } /** * 傳送HTML郵件 */ @Override public void sendMimeMail(String to, String subject, String context){ MimeMessage mailMessage = mailSender.createMimeMessage(); try{//傳送非純文字的郵件都需要用的helper來解析 MimeMessageHelper helper = new MimeMessageHelper(mailMessage); helper.setFrom(from); helper.setTo(to); // helper.setBcc("[email protected]");//抄送人 helper.setSubject(subject); helper.setText(context,true);//這裡的第二個引數要為true才會解析html內容 mailSender.send(mailMessage); logger.info("郵件傳送成功"); } catch(Exception ex){ logger.error("郵件傳送失敗",ex); } } /** * 傳送帶附件的郵件 */ @Override public void sendAttachMail(String[] to, String subject, String context, String filePath) { MimeMessage message = mailSender.createMimeMessage(); try{ MimeMessageHelper helper = new MimeMessageHelper(message,true); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); helper.setText(context); FileSystemResource file = new FileSystemResource(new File(filePath)); helper.addAttachment(file.getFilename(),file);//新增附件,需要用到FileStstemResource mailSender.send(message); logger.info("帶郵件的附件傳送成功"); }catch(Exception ex){ logger.error("帶附件的郵件傳送失敗",ex); } } /** * 傳送正文帶圖片的郵件 */ @Override public void sendInlineMail(String to, String subject, String context, String filePath, String resId) { MimeMessage message = mailSender.createMimeMessage(); try{ MimeMessageHelper helper = new MimeMessageHelper(message,true); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); helper.setText(context,true); FileSystemResource res = new FileSystemResource(new File(filePath)); helper.addInline(resId, res); mailSender.send(message); logger.info("郵件傳送成功"); } catch (Exception ex){ logger.error("郵件傳送失敗",ex); } } }
程式碼中分別對傳送普通文字郵件、HTML郵件、程式碼附件的郵件、帶圖片的郵件進行了示範
- 編寫測試類
package com.mail; import com.mail.service.MailService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class MailServiceTest { @Autowired MailService mailService; @Value("${mail.to}") private String mailTo; @Test public void testSimpleMail(){ mailService.sendSimpleMail(mailTo,"純文字郵件","你好,這是一封測試郵件"); } @Test public void testMimeMail(){ String context = "<html>\n" + "<body>\n" + "你好,<br>" + "這是一封HTML郵件\n" + "</body>\n" + "</html>"; mailService.sendMimeMail(mailTo,"HTML郵件",context); } @Test public void testSendAttachMail(){ String[] to = {mailTo,"[email protected]"}; mailService.sendAttachMail(to,"帶附件的郵件","你好,這是一封帶附件的郵件","D:\\1.jpg"); } @Test public void testSendInlineMail(){ String resId = "1"; String context = "<html><body>你好,<br>這是一封帶靜態資源的郵件<br><img src=\'cid:"+resId+"\'></body></html>"; mailService.sendInlineMail(mailTo,"帶靜態圖片的郵件",context,"D:\\1.jpg",resId); } }
- 分別執行以上@Test方法
郵件傳送的程式碼基本實現瞭解了,接下來引入activemq的實現。
二、springboot整合ActiveMQ實現非同步郵件傳送
springboot整合ActiveMQ其實也比較簡單,首先配置檔案中需要新增ActiveMQ的相關配置,然後生產者通過注入JmsTemplate傳送訊息,消費者實現監聽消費。
實現功能後,最終程式碼結構:
controller+ActiveMQService扮演生產者角色,傳送訊息給消費者;
listener扮演消費者角色,接收到訊息後呼叫MailService的介面執行郵件傳送。
具體程式碼如下:
- 修改application.properties,新增如下內容
###queue name### com.sam.mail.queue=com.sam.mail.queue ###activemq config### #mq服務地址 spring.activemq.broker-url=tcp://localhost:61616 spring.activemq.pool.enabled=false #mq使用者名稱和密碼 spring.activemq.user=admin spring.activemq.password=admin #處理序列化物件需要用到的配置 spring.activemq.packages.trusted=true spring.activemq.packages.trust-all=true
- 實現MQ傳送的service
package com.mail.service.impl; import com.alibaba.fastjson.JSON; import com.mail.model.MailBean; import com.mail.service.ActiveMQService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.jms.core.JmsTemplate; import org.springframework.stereotype.Service; @Service public class ActiveMQServiceImpl implements ActiveMQService { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired JmsTemplate template; @Value("${com.sam.mail.queue}") private String queueName; @Override public void sendMQ(String[] to, String subject, String content) { this.sendMQ(to,subject,content,null); } @Override public void sendMQ(String[] to, String subject, String content, String filePath) { this.sendMQ(to,subject,content,filePath,null); } @Override public void sendMQ(String[] to, String subject, String content, String filePath, String srcId) { MailBean bean = new MailBean(); bean.setTo(to); bean.setSubject(subject); bean.setContent(content); bean.setFilePath(filePath); bean.setSrcId(srcId); template.convertAndSend(queueName,bean); logger.info("郵件已經發送到MQ:"+ JSON.toJSONString(bean)); } }
- 實現訊息傳送的controller
package com.mail.controller; import com.mail.service.ActiveMQService; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @author JAVA開發老菜鳥 */ @RestController public class MailSenderController { @Resource ActiveMQService activeMQService; @Value("${mail.to}") private String mailTo; @RequestMapping("/sendSimpleMail.do") public void sendSimpleMail(){ String[] to = {mailTo}; String subject = "普通郵件"; String context = "你好,這是一封普通郵件"; activeMQService.sendMQ(to, subject, context); } @RequestMapping("/sendAttachMail.do") public void sendAttachMail(){ String[] to = {mailTo}; String subject = "帶附件的郵件"; String context = "<html><body>你好,<br>這是一封帶附件的郵件,<br>具體請見附件</body></html>"; String filePath = "D:\\1.jpg"; activeMQService.sendMQ(to, subject, context, filePath); } @RequestMapping("/sendMimeMail.do") public void sendMimeMail(){ String[] to = {mailTo}; String subject = "普通郵件"; String filePath = "D:\\1.jpg"; String resId = "1.jpg"; String context = "<html><body>你好,<br>這是一封帶圖片的郵件,<br>請見圖片<br><img src=\'cid:"+resId+"\'></body></html>"; activeMQService.sendMQ(to, subject, context, filePath, resId); } }
- MailBean的具體實現
public class MailBean implements Serializable { private String from;//發件人 private String[] to;//收件人列表 private String subject;//郵件主題 private String content;//郵件正文 private String filePath;//檔案(圖片)路徑 private String srcId;//圖片名 ...... getter/setter略 ...... }
- 消費者監聽實現
package com.mail.listener; import com.mail.model.MailBean; import com.mail.service.MailService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Service; import javax.jms.ObjectMessage; import java.io.Serializable; /** * 監聽到MQ後呼叫mailService執行郵件傳送操作 */ @Service public class SendMailMQListener { Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired MailService mailService; /** * 通過監聽目標佇列實現功能 */ @JmsListener(destination = "${com.sam.mail.queue}") public void dealSenderMailMQ(ObjectMessage message){ try{ Serializable object = message.getObject(); MailBean bean = (MailBean) object; mailService.sendMail(bean.getTo(),bean.getSubject(),bean.getContent(),bean.getFilePath(),bean.getSrcId()); logger.error("消費者消費郵件資訊成功"); } catch (Exception ex){ logger.error("消費者消費郵件資訊失敗:"+ ex); } } }
- 監聽器呼叫的傳送介面在前面沒有,是新加的
@Override public void sendMail(String[] to, String subject, String context, String filePath, String resId ){ MimeMessage message = mailSender.createMimeMessage(); try{ MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); helper.setText(context, true); if(!StringUtils.isEmpty(filePath) && !StringUtils.isEmpty(resId)){//檔案路徑和resId都不為空,視為靜態圖片 FileSystemResource resource = new FileSystemResource(new File(filePath)); helper.addInline(resId, resource); } else if(!StringUtils.isEmpty(filePath)){//只有檔案路徑不為空,視為附件 FileSystemResource resource = new FileSystemResource(new File(filePath)); helper.addAttachment(resource.getFilename(),resource); } mailSender.send(message); logger.info("郵件傳送成功"); } catch (Exception ex){ logger.error("郵件傳送錯誤:", ex); }
- 啟動工程,分別呼叫controller中的uri,檢視結果
-
檢視下mq的頁面控制檯
至此,功能已經實現。
三、遇到過的問題
在實現這個demo的時候,遇到了一些問題,也把它們列出來,給別人一個參考
第一個問題:
消費者消費郵件資訊失敗:javax.jms.JMSException: Failed to build body from content. Serializable class not available to broker. Reason: java.lang.ClassNotFoundException: Forbidden class com.mail.model.MailBean! This class is not trusted to be serialized as ObjectMessage payload. Please take a look at http://activemq.apache.org/objectmessage.html for more information on how to configure trusted classes.
This class is not trusted to be serialized as ObjectMessage payload,是說我的MailBean物件不是可以新人的序列化物件,
原因:
傳遞物件訊息時 ,ActiveMQ的ObjectMessage依賴於Java的序列化和反序列化,但是這個過程被認為是不安全的。具體資訊檢視報錯後面的那個網址:
http://activemq.apache.org/objectmessage.html
解決方法:
在application.properties檔案中追加下面的配置即可
spring.activemq.packages.trust-all=true
第二個問題:
*************************** APPLICATION FAILED TO START *************************** Description: A component required a bean of type 'com.mail.service.ActiveMQService' that could not be found. Action: Consider defining a bean of type 'com.mail.service.ActiveMQService' in your configuration.
原因:
ActiveMQService沒有被spring掃描並初始化,然後我在程式碼用通過@Autowaired註解使用獲取不到。 找了之後發現是我的@Service註解放到了interface上,應該放到service的impl類上。
解決方法:
將@Service註解放到impl類上
好,以上就是Springboot+ActiveMQ+JavaMail實現非同步郵件傳送的全部內容了,
覺得有幫助的話,記得點贊哦~~
&n