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("demo@163.com", "test", "我是一封测试邮件");
	}

	@RequestMapping(value="/attach", method = RequestMethod.POST)
	@ResponseBody
	public void sendAttachMail(List<EmailModel> emailModels) {
		
		mailService.sendSimpleMail("demo@163.com", "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方式的运行结果,我们可以看到多线程的效果拉~
在这里插入图片描述

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值