從spring boot發郵件聊到開發的友好性
前些天幫一個朋友做網站,全站都是靜態頁面,唯一需要用到後端開發的是他需要一個留言板。傳統的留言板一般都是提交後儲存到資料庫,然後提供一個後臺的留言列表給管理人員看,我嫌麻煩,就決定留言提交到後臺直接發郵件出去,這樣就不用開發後臺頁面了,他也不需要登入一個什麼後臺才能看留言,兩全其美,豈不美哉。
1、最簡版spring boot發郵件
spring boot發郵件還是挺簡單的,首先把發郵件的start加到pom裡面:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>複製程式碼
然後在application.properties裡面配置好關於發郵件的引數
spring.mail.host=smtp.163.com
spring.mail.port=25
[email protected]
spring.mail.password=yourpassword複製程式碼
其中spring.mail.host和spring.mail.username是一一對應的,哪裡的郵箱就要用哪裡的smtp伺服器
然後我寫了一個controller來接收留言板的內容:
@Value("${spring.mail.username}")
private String fromMail;
@Autowired
private JavaMailSender mailSender;
@RequestMapping(value = "/getNote" ,method = RequestMethod.POST)
public String getNote(Note note) {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(mimeMessage,true);
//發件人
helper.setFrom(fromMail,note.yourName);
//收件人(留言內容最終發往的郵箱地址)
helper.setTo("[email protected]" );
//標題
helper.setSubject(note.yourSubject);
//文字
helper.setText("from email:"+note.yourEmail+"\n"+note.yourMessage);
mailSender.send(mimeMessage);
} catch (MessagingException | UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "redirect:return.htm";
}複製程式碼
public class Note {
String yourName;
String yourEmail;
String yourSubject;
String yourMessage;
//getter,setter省略
}複製程式碼
- note物件是留言板的內容
- JavaMailSender和MimeMessageHelper是官方推薦的好基友,一般配套使用
- fromMail就是在application.properties裡面配置的spring.mail.username屬性,也就是發郵件的一方,用helper.setTo(...)配置
- 額外說一下,由於填留言板時一般也會留郵件地址,但那個郵件地址和這裡的任何一個設定郵件的地方毫無關係,只需要在郵件的文本里面做記錄就好了,我之前就弱智了一把,把留言板裡面的郵件地址填到helper.setTo(...),結果浪費了我半個小時查這種bug,真是一言難盡...
2、授權碼發郵件
很多時候,按照上面的方法發郵件會出現下面的錯誤:
javax.mail.AuthenticationFailedException: 535 Error: authentication failed複製程式碼
其中的一個原因是郵件伺服器使用了授權碼登入方式,也就是第三方登入不能直接使用賬號密碼,而需要使用一種授權碼的方式,比如上面的163.com郵箱,就可以設定授權碼登入,設定介面如下:
QQ郵箱更是預設就需要用授權碼登入,QQ郵箱的授權登入操作檔案請看這裡
使用授權碼登入後,需要把之前application.properties裡面spring.mail.password的內容從密碼換成授權碼,就能夠正常發郵件了。
3、簡單聊下JavaMailSender和MimeMessageHelper
我們查下JavaMailSender的程式碼就知道,它其實背景比較複雜。首先它繼承了org.springframework.mail.MailSender介面。
public interface JavaMailSender extends MailSender {
...
}複製程式碼
而它本身也是一個介面,實現類只有一個,JavaMailSenderImpl
我們再來翻JavaMailSenderImpl的程式碼,發現spring並沒有自己來做傳送郵件的功能,而是直接用了java自身的郵件傳送功能,核心是這一段
protected void doSend(MimeMessage[] mimeMessages,@Nullable Object[] originalMessages) throws MailException {
Map<Object,Exception> failedMessages = new LinkedHashMap<>();
Transport transport = null;
try {
for (int i = 0; i < mimeMessages.length; i++) {
// Check transport connection first...
if (transport == null || !transport.isConnected()) {
if (transport != null) {
try {
transport.close();
}
catch (Exception ex) {
// Ignore - we're reconnecting anyway
}
transport = null;
}
try {
transport = connectTransport();
}
catch (AuthenticationFailedException ex) {
throw new MailAuthenticationException(ex);
}
catch (Exception ex) {
// Effectively,all remaining messages failed...
for (int j = i; j < mimeMessages.length; j++) {
Object original = (originalMessages != null ? originalMessages[j] : mimeMessages[j]);
failedMessages.put(original,ex);
}
throw new MailSendException("Mail server connection failed",ex,failedMessages);
}
}
// Send message via current transport...
MimeMessage mimeMessage = mimeMessages[i];
try {
if (mimeMessage.getSentDate() == null) {
mimeMessage.setSentDate(new Date());
}
String messageId = mimeMessage.getMessageID();
mimeMessage.saveChanges();
if (messageId != null) {
// Preserve explicitly specified message id...
mimeMessage.setHeader(HEADER_MESSAGE_ID,messageId);
}
Address[] addresses = mimeMessage.getAllRecipients();
transport.sendMessage(mimeMessage,(addresses != null ? addresses : new Address[0]));
}
catch (Exception ex) {
Object original = (originalMessages != null ? originalMessages[i] : mimeMessage);
failedMessages.put(original,ex);
}
}
}
finally {
try {
if (transport != null) {
transport.close();
}
}
catch (Exception ex) {
if (!failedMessages.isEmpty()) {
throw new MailSendException("Failed to close server connection after message failures",failedMessages);
}
else {
throw new MailSendException("Failed to close server connection after message sending",ex);
}
}
}
if (!failedMessages.isEmpty()) {
throw new MailSendException(failedMessages);
}
}複製程式碼
doSend方法中呼叫的核心類就是Transport類,這個類的包名是javax.mail。spring不愧是整合大師,java自帶的mail功能經過spring的標準化包裝就成了spring自身功能的一部分,再通過spring boot的包裝,用starter的方式再次做簡化,我們就能夠直接通過極簡的方式使用了。
當然,簡化的方法多種多樣,另外的一種形式的包裝就是使用helper類的方法,spring使用的就是MimeMessageHelper。在javax.mail在處理郵件的方式上,使用的是分而治之的辦法,不同的類處理不同的問題,所以看到很多的類在處理各種問題和情況。
這種做法在實現功能上是很好的,把一個複雜的問題分解成若干個小問題,分別實現。但對使用的開發人員就談不上友好了,容易出現以下幾個問題:
- 不直觀,呼叫者不知道從何下手
- 查詢麻煩,類太多而且功能分散,不容易找到對應的功能類
- 關係複雜,經常對要引用哪個類會很沒有把握,因為太多處理單一問題的類
- 沒有統一的入口,上手難,很難脫離檔案直接使用
針對上述情況,spring通過MimeMessageHelper,把幾乎所有郵件傳送需要處理的問題就集中到了這個類裡面,使用方便又好找。下面是這個類所有的方法。
這個類不可謂不復雜,基本上涵蓋了所有發郵件方面的功能,但因為都整合在一個類裡面,非常方便好用,可以說是一個對程式設計師友好的典範了,值得大家在開發時做借鑑。