場景介紹
場景
需要批量向用戶發送郵件,並且保存郵件發送狀態到數據庫
場景分析
因爲需要批量發送郵件,而我們知道發送郵件其實是一個耗時操作,如果我們讓接口等待發送郵件完成後再返回的話,該接口的效率就會非常慢啦~所以說,我們可使用線程池來批量發送郵件。
詳細代碼
整個代碼基於spring boot實現,實現兩種線程工作類,第一種是實現Runnable的線程,這種方式不可以拿到返回值,也不可以拋出異常,第二種是實現Callable的線程,這種方式可以拿到返回值,也可以拋出異常。
因爲想分析一些其他的內容,所以代碼寫的略複雜~
線程池配置類
ExecutorConfig.java
package com.example.demo.config;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
public class ExecutorConfig extends AsyncConfigurerSupport {
/**
* 設置線程池大小
**/
private int corePoolSize = 4;
/**
* 設置線程池最大數量
**/
private int maxPoolSize = 16;
/**
* 線程池阻塞隊列的容量
**/
private int queueCapacity = 10;
// private String threadNamePrefix = "omsAsyncExecutor-";
@Bean
@Override
public ThreadPoolTaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
// executor.setThreadNamePrefix(threadNamePrefix);
// rejection-policy:當pool已經達到max size的時候,如何處理新任務
// CALLER_RUNS:不在新線程中執行任務,而是由調用者所在的線程來執行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//一定要等線程執行完成的時候纔去關閉線程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//最大等待時間60s
executor.setAwaitTerminationSeconds(60);
//項目啓動的時候就初始化線程池,避免到調用的時候才初始化
executor.initialize();
return executor;
}
}
郵件相關信息實體類
EmailModel.java
package com.example.demo;
import java.io.Serializable;
public class EmailModel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1404185636399251685L;
private String email;
private String subject;
private String content;
private String attachFilePath;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getAttachFilePath() {
return attachFilePath;
}
public void setAttachFilePath(String attachFilePath) {
this.attachFilePath = attachFilePath;
}
}
實現Runnable的線程工作類
EmailNoticeThreadPoolTask.java
package com.example.demo.util;
import org.slf4j.LoggerFactory;
import com.example.demo.service.MailService;
/**
*
* @author zhangyuxuan 2019年7月23日
*/
public class EmailNoticeThreadPoolTask implements Runnable {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(EmailNoticeThreadPoolTask.class);
private MailService mailService;
private String email;
private String content;
private String subject;
private String filePath;
/**
* @param mailService
* @param email
* @param content
* @param subject
* @param part
*/
public EmailNoticeThreadPoolTask(MailService mailService, String email, String subject,
String content, String filePath) {
this.mailService = mailService;
this.email = email;
this.content = content;
this.subject = subject;
this.filePath = filePath;
}
@Override
public void run() {
logger.info("run開始");
mailService.sendSimpleMail(email, subject, content);
logger.info("run結束");
// mailService.sendAttachmentMail(email, subject, content, filePath);
}
}
批量發送郵件的Service
package com.example.demo.service.impl;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import com.example.demo.EmailModel;
import com.example.demo.service.BatchMailService;
import com.example.demo.service.MailService;
import com.example.demo.util.EmailNoticeThreadPoolTask;
import com.example.demo.util.EmailThreadPoolTask;
@Service
public class BatchMailServiceImpl implements BatchMailService {
@Autowired
private ThreadPoolTaskExecutor executor;
@Autowired
private MailService mailService;
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(BatchMailServiceImpl.class);
@Override
public void batchSendReturnEmail(List<EmailModel> emails) {
for (EmailModel emailModel : emails) {
logger.info("向" + emailModel.getEmail() + "發送郵件開始");
Future<Boolean> statusFuture = executor.submit(new EmailThreadPoolTask(mailService, emailModel.getEmail(), emailModel.getSubject(), emailModel.getContent(), emailModel.getAttachFilePath()));
// 根據返回值來進行判斷下一步操作,注意,future中的get方法是一個阻塞的方法,會一直等到future返回結果纔會結束
try {
boolean status = statusFuture.get();
// 根據結果可以進行存入數據庫等操作 這邊暫時只做輸出操作
logger.info("狀態:" + status);
logger.info("向" + emailModel.getEmail() + "發送郵件結束");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
@Override
public void batchSendEmail(List<EmailModel> emails) {
for (EmailModel emailModel : emails) {
logger.info("向" + emailModel.getEmail() + "發送郵件開始");
try {
executor.execute(new EmailNoticeThreadPoolTask(mailService, emailModel.getEmail(), emailModel.getSubject(), emailModel.getContent(), emailModel.getAttachFilePath()));
logger.info("向" + emailModel.getEmail() + "發送郵件結束");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
發送郵件的Service
package com.example.demo.service.impl;
import java.io.File;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
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 com.example.demo.service.MailService;
@Service
public class MailServiceImpl implements MailService {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(MailServiceImpl.class);
@Value("${spring.mail.username}")
//使用@Value注入application.properties中指定的用戶名
private String from;
@Autowired
//用於發送文件
private JavaMailSender mailSender;
public boolean sendSimpleMail(String to, String subject, String content) {
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);//收信人
message.setSubject(subject);//主題
message.setText(content);//內容
message.setFrom(from);//發信人
mailSender.send(message);
} catch (Exception e) {
e.printStackTrace();
logger.error("發送HTML郵件失敗");
return false;
}
return true;
}
public boolean sendHtmlMail(String to, String subject, String content) {
logger.info("發送HTML郵件開始:{},{},{}", to, subject, content);
//使用MimeMessage,MIME協議
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper;
//MimeMessageHelper幫助我們設置更豐富的內容
try {
helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);//true代表支持html
mailSender.send(message);
logger.info("發送HTML郵件成功");
} catch (MessagingException e) {
logger.error("發送HTML郵件失敗:", e);
return false;
}
return true;
}
public boolean sendAttachmentMail(String to, String subject, String content, String filePath) {
logger.info("發送帶附件郵件開始:{},{},{},{}", to, subject, content, filePath);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(message, true);
//true代表支持多組件,如附件,圖片等
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = file.getFilename();
helper.addAttachment(fileName, file);//添加附件,可多次調用該方法添加多個附件
mailSender.send(message);
logger.info("發送帶附件郵件成功");
} catch (MessagingException e) {
logger.error("發送帶附件郵件失敗", e);
return false;
}
return true;
}
public boolean sendInlineResourceMail(String to, String subject, String content, String rscPath, String rscId) {
logger.info("發送帶圖片郵件開始:{},{},{},{},{}", to, subject, content, rscPath, rscId);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
// 以絕對路徑的方式讀取文件
FileSystemResource res = new FileSystemResource(new File(rscPath));
helper.addInline(rscId, res);//重複使用添加多個圖片
mailSender.send(message);
logger.info("發送帶圖片郵件成功");
} catch (MessagingException e) {
logger.error("發送帶圖片郵件失敗", e);
return false;
}
return true;
}
public void sendHtmlImageMail(String to, String subject, String content, String rscPath) {
logger.info("發送帶圖片郵件開始:{},{},{},{}", to, subject, content, rscPath);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
// cid是固定寫法
helper.setText(
"<html><head></head><body><h1>hello!!spring image html mail</h1>"
+ "<img src=\"cid:aaa\"/></body></html>", true);
FileSystemResource img = new FileSystemResource(new File(rscPath));
helper.addInline("aaa", img);
mailSender.send(message);
logger.info("發送帶圖片郵件成功");
} catch (MessagingException e) {
logger.error("發送帶圖片郵件失敗", e);
}
}
}
測試Controller
package com.example.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.demo.EmailModel;
import com.example.demo.service.BatchMailService;
import com.example.demo.service.MailService;
@Controller
@RequestMapping(value="/mail")
public class MailController {
@Autowired
private MailService mailService;
@Autowired
private BatchMailService batchMailService;
@RequestMapping(value="/simple")
@ResponseBody
public void sendMail() {
mailService.sendSimpleMail("[email protected]", "test", "我是一封測試郵件");
}
@RequestMapping(value="/attach", method = RequestMethod.POST)
@ResponseBody
public void sendAttachMail(List<EmailModel> emailModels) {
mailService.sendSimpleMail("[email protected]", "test", "我是一封測試郵件");
}
@RequestMapping(value="/batch", method = RequestMethod.POST)
@ResponseBody
public void batchSendMail(@RequestBody List<EmailModel> emails) {
batchMailService.batchSendEmail(emails);
}
@RequestMapping(value="/batchReturn", method = RequestMethod.POST)
@ResponseBody
public void batchSendMailReturn(@RequestBody List<EmailModel> emails) {
batchMailService.batchSendReturnEmail(emails);
}
}
實現Callable的線程工作類
package com.example.demo.util;
import java.util.concurrent.Callable;
import org.slf4j.LoggerFactory;
import com.example.demo.service.MailService;
/**
*
* @author zhangyuxuan 2019年7月23日
*/
public class EmailThreadPoolTask implements Callable<Boolean> {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(EmailNoticeThreadPoolTask.class);
private MailService mailService;
private String email;
private String content;
private String subject;
private String filePath;
/**
* @param mailService
* @param email
* @param content
* @param subject
* @param part
*/
public EmailThreadPoolTask(MailService mailService, String email, String subject, String content, String filePath) {
this.mailService = mailService;
this.email = email;
this.content = content;
this.subject = subject;
this.filePath = filePath;
}
@Override
public Boolean call() throws Exception {
logger.info("call開始");
boolean status = mailService.sendSimpleMail(email, subject, content);
logger.info("call結束");
// mailService.sendAttachmentMail(email, subject, content, filePath);
return status;
}
}
測試結果
使用Callable方式的運行結果,我們可以看到它幾乎是順序執行的,似乎沒有達到多線程的結果,實際上並不是因爲我們沒有使用多線程,而是因爲我們代碼中使用的獲取狀態的 future中的get方法是一個阻塞的方法,會一直等到future返回結果纔會結束,因而堵塞了線程而已~
使用Runnable方式的運行結果,我們可以看到多線程的效果拉~