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返回結果纔會結束,因而堵塞了線程而已~
在這裏插入圖片描述
使用Runnable方式的運行結果,我們可以看到多線程的效果拉~
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章