天天看點

springboot使用線程池發送郵件demo

場景介紹

場景

需要批量向使用者發送郵件,并且儲存郵件發送狀态到資料庫

場景分析

因為需要批量發送郵件,而我們知道發送郵件其實是一個耗時操作,如果我們讓接口等待發送郵件完成後再傳回的話,該接口的效率就會非常慢啦~是以說,我們可使用線程池來批量發送郵件。

詳細代碼

整個代碼基于spring boot實作,實作兩種線程工作類,第一種是實作Runnable的線程,這種方式不可以拿到傳回值,也不可以抛出異常,第二種是實作Callable的線程,這種方式可以拿到傳回值,也可以抛出異常。

因為想分析一些其他的内容,是以代碼寫的略複雜~

詳細代碼github位址

線程池配置類

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傳回結果才會結束,因而堵塞了線程而已~

springboot使用線程池發送郵件demo

使用Runnable方式的運作結果,我們可以看到多線程的效果拉~

springboot使用線程池發送郵件demo