spring項目實現線程池異步發送郵件功能

1、自定義線程池

使用TaskExecutor实现自定義線程池

ThreadPoolTaskExecutor
      该实现只能在Java 5环境中使用,其也是该环境中最常用的实现。它公开了bean属性,用于配置java.util.concurrent.ThreadPoolExecutor并将其包装在TaskExecutor中。如果你需要一些高级的接口,例如ScheduledThreadPoolExecutor,建议使用Concurrent TaskExecutor。

      在Spring中TaskExecutor的实现类是以Bean的方式提供服务的,比如下面这个例子,我们通过xml配置文件方式向Spring容器中注入了TaskExecutor的实现者ThreadPoolTaskExecutor的实例。

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:f="http://www.vivebest.com/schema/finchas/i/fcsi-core"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
						http://www.vivebest.com/schema/finchas/i/fcsi-core http://www.vivebest.com/schema/finchas/i/fcsi-core-1.0.xsd
						http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">

<!-- 1、開啟異步支持-->
<!-- 重點註意: 需要添加該配置開啟異步執行,否知異步不生效,與@EnableAsync同作用-->
<task:annotation-driven executor="taskExecutor" proxy-target-class="true"/>

<!-- 2、注入線程池對象-->
	<!-- spring thread pool executor -->
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <!-- 线程池维护线程的最少数量 -->
		<property name="corePoolSize" value="5" />
		<!-- 允许的空闲时间 -->
		<property name="keepAliveSeconds" value="200" />
		<!-- 线程池维护线程的最大数量 -->
		<property name="maxPoolSize" value="10" />
		<!-- 缓存队列 -->
		<property name="queueCapacity" value="20" />
		<!-- 对拒绝task的处理策略 -->
		<property name="rejectedExecutionHandler">
			<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
		</property>
	</bean>
</beans>

首先配置文件頭要存在:

xmlns:task="http://www.springframework.org/schema/task"

配置文件約束需要添加:

xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"

<task:annotation-driven executor=“taskExecutor” proxy-target-class=“true”/>是开启异步,等同于@EnableAsync注解的作用:其中executor属性用来指定异步线程池,proxy-target-class属性值决定是基于接口的还是基于类的代理被创建

這一步就把自定義線程池創建好了,並且開啟了異步執行支持

2、實現發送郵件功能

2.1、在pom.xml文件中引入發送郵件所需依賴

<!--spring mail-->
<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>4.3.12.RELEASE</version>
    	</dependency>
		<dependency>
			<groupId>javax.mail</groupId>
			<artifactId>mail</artifactId>
			<version>1.4.7</version>
		</dependency>

2.2、在工程resource目录下创建mailConfig.properties文件,并添加相关配置

#發送郵件的服務器
mail.host=smtp.vivebest.com
#郵件發送人
mail.sender=xxx@qq.com
#郵箱地址
mail.username=xxx@qq.com
#郵箱登錄密碼或授權碼
mail.password=xxxxx
#郵箱端口
mail.port=25
#郵箱內容字符集
mail.defaultEncoding=utf-8

# 设置是否需要认证,如果为true,那么用户名和密码就必须的,
# 如果设置false,可以不设置用户名和密码,当然也得看你的对接的平台是否支持无密码进行访问的。
mail.properties.mail.smtp.auth= true

# STARTTLS[1]  是对纯文本通信协议的扩展。它提供一种方式将纯文本连接升级为加密连接(TLS或SSL),而不是另外使用一个端口作加密通信。
#通讯是否加密,true开启,false不开启
mail.properties.mail.smtp.starttls.enable=true
#是否必须通过使用加密通讯进行通讯,true开启,false不开启
mail.properties.mail.smtp.starttls.required=true
#發送超時時間,暫時不設置
#mail.properties.smtp.timeout=5000
#SMTP(Simple Mail Transfer Protocol):简单邮件传输协议,用于发送电子邮件的传输协议。
#默認為smtp,不需要配置
#mail.transport.protocol=smtp

2.3、將配置文件引入spring容器

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
	<context:annotation-config/>
	<context:component-scan base-package="com.vivebest, com.app"/>
	<bean id="appSpringConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	    <property name="ignoreUnresolvablePlaceholders" value="true" />
		<property name="locations">
			<list>
				<value>classpath:META-INF/config/mailConfig.properties</value>
			</list>
		</property>
		<property name="fileEncoding" value="UTF-8" />
	</bean>
</beans>

2.4、編寫郵件發送功能實現代碼

package com.app.core.mail;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
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.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.mail.internet.MimeMessage;

@Component("mailService") //没有这个注解,发送邮件时可能会引发空指针异常
public class MailService {

    private final Logger logger = LoggerFactory.getLogger(MailService.class);

//    private final static Logger logger = LoggerFactory.getLogger(com.transportation.returns.service.impl.MailServiceImpl.class);

    @Autowired
    private JavaMailSender javaMailSender;

    @Value("${mail.sender}")
    private String sender;
    /*
     * 純文本郵件內容
     * param:to:邮件发送目标
     * param:subject:邮件标题
     * param:content:邮件内容
     * */


    //使用@Async 异步执行的方法,如果有返回值,返回值不要用原始数据类型(比如int),最好使用包装类(比如Integer)
    //因为使用原始类型的话,会报错  Null return value from advice does not match primitive return type for
    //改为无返回值 void
    //改为使用包装类
    //不使用异步注解
    @Async("taskExecutor")
    public void sendSimpleText(String[] to, String subject, String content) {

        logger.info("Async================================================"+Thread.currentThread().getName());
        logger.info("## Ready to send mail ...");
        long startDate = System.currentTimeMillis();
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        // 邮件发送来源
        simpleMailMessage.setFrom(sender);


        // 邮件发送目标
        simpleMailMessage.setTo(to);
        // 设置标题
        simpleMailMessage.setSubject(subject);
        // 设置内容
        simpleMailMessage.setText(content);
        String emailRecievers ="emailRecievers: ";
        for (String s : toArr) {
            emailRecievers += s;
        }
        logger.info("emailSender: "+sender);
        logger.info(emailRecievers);
        try {
            // 发送
            javaMailSender.send(simpleMailMessage);
            long startEndDate = System.currentTimeMillis() - startDate;
            logger.info("## Send the mail success ... spend: " + startEndDate);
        } catch (Exception e) {
            logger.info("Send email error: ", e);

        }
    }


    /*
     * 純html郵件內容
     * param:to:邮件发送目标
     * param:subject:邮件标题
     * param:html:邮件内容 内容為html 格式
     * */
    @Async("taskExecutor")
    public void sendWithHtml(String[] to, String subject, String html) {
        logger.info("## Ready to send mail ...");
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();

        MimeMessageHelper mimeMessageHelper = null;
        try {
            mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
            // 邮件发送来源
            mimeMessageHelper.setFrom(sender);
            // 邮件发送目标
            mimeMessageHelper.setTo(to);
            // 设置标题
            mimeMessageHelper.setSubject(subject);
            // 设置内容,并设置内容 html 格式为 true
            mimeMessageHelper.setText(html, true);

            javaMailSender.send(mimeMessage);
            logger.info("## Send the mail with html success ...");
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("Send html mail error: ", e);
        }
    }

    /*
     * html與 Image 郵件內容
     * param:to:邮件发送目标
     * param:subject:邮件标题
     * param:html:邮件内容 内容為html 格式
     * param:cids:郵件中Image的佔位符
     * param:filePaths:郵件中Image
     * */
    @Async("taskExecutor")
    public void sendWithImageHtml(String[] to, String subject, String html, String[] cids, String[] filePaths) {
        logger.info("## Ready to send mail ...");
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();

        MimeMessageHelper mimeMessageHelper = null;
        try {
            mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
            // 邮件发送来源
            mimeMessageHelper.setFrom(sender);
            // 邮件发送目标
            mimeMessageHelper.setTo(to);
            // 设置标题
            mimeMessageHelper.setSubject(subject);
            // 设置内容,并设置内容 html 格式为 true
            mimeMessageHelper.setText(html, true);

            // 设置 html 中内联的图片
            for (int i = 0; i < cids.length; i++) {
                FileSystemResource file = new FileSystemResource(filePaths[i]);
                // addInline() 方法 cid 需要 html 中的 cid (Content ID) 对应,才能设置图片成功,
                // 具体可以参见,下面 4.3.3 单元测试的参数设置
                mimeMessageHelper.addInline(cids[i], file);
            }

            javaMailSender.send(mimeMessage);
            logger.info("## Send the mail with image success ...");
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("Send html mail error: ", e);
        }
    }

    /*
     * 文本 與 附件 郵件內容
     * param:to:邮件发送目标
     * param:subject:邮件标题
     * param:content:邮件内容
     * param:filePaths:附件
     * */
    @Async("taskExecutor")
    public void sendWithWithEnclosure(String[] to, String subject, String content, String[] filePaths) {
        logger.info("## Ready to send mail ...");
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();

        MimeMessageHelper mimeMessageHelper = null;
        try {
            // 设置为true,代表要支持附件
            mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
            // 邮件发送来源
            mimeMessageHelper.setFrom(sender);
            // 邮件发送目标
            mimeMessageHelper.setTo(to);
            // 设置标题
            mimeMessageHelper.setSubject(subject);
            // 设置内容
            mimeMessageHelper.setText(content);

            // 添加附件
            for (int i = 0; i < filePaths.length; i++) {
                FileSystemResource file = new FileSystemResource(filePaths[i]);
                String attachementFileName = "附件" + (i + 1) + "_" + file.getFilename();
                mimeMessageHelper.addAttachment(attachementFileName, file);
            }

            javaMailSender.send(mimeMessage);
            logger.info("## Send the mail with enclosure success ...");
        } catch (Exception e) {
            logger.info("Send html mail error: ", e);
        }
    }
}

2.5、測試,這裡只有html+圖片、text+附件測試demo

@Test
    public void sendWithImageHtml(){
        String to="xxxxx@qq.com";
        String title="标题:带有图片的Html发送测试";
        String htmlContent="<html><body>" +
                "<h1>欢迎来到 Spring boot 的世界</h1>" +
                "<image width='50' height='60' src='cid:test1'>图片1 </image>" +//cid:是约定好的固定格式,只需要修改后面的变量
                "<image width='50' height='60' src='cid:test2'>图片1 </image>" +
                "</body></html>";
        //数组中的cid要和上面html中image中的cid一致,否则图片将设置失败
        String[] cids=new String[]{"test1","test2"};
        String[] filePaths=new String[]{
                "D:\\Documents\\ioc\\MyIco\\pao1.ico",
                "D:\\Documents\\ioc\\MyIco\\xiang2.ico"
        };
        Assertions.assertTrue(mailService.sendWithImageHtml(to,title,htmlContent,cids,filePaths));

}



@Test
    public void sendWithWithEnclosure(){
        String to="xxxxx@qq.com";
        String title="标题:带有附件的邮件发送测试";
        String content="欢迎来到 Spring boot 的世界";
        String[] filePaths=new String[]{
                "D:\\Documents\\ioc\\MyIco\\pao1.ico",
                "D:\\Documents\\ioc\\MyIco\\expect5.45.tar.gz"
        };
        Assert.assertTrue(mailService.sendWithWithEnclosure(to,title,content,filePaths));

}

3、實現異步發送郵件,只需要在需要異步執行的方法上加上@Async註解即可

    @Async("taskExecutor")
    public void sendSimpleText(String[] to, String subject, String content) {
}
3.1异步说明和原理
使用地方说明:

在方法上使用该@Async注解,申明该方法是一个异步任务;
在类上面使用该@Async注解,申明该类中的所有方法都是异步任务;
使用此注解的方法的类对象,必须是spring管理下的bean对象;
要想使用异步任务,需要在主类上开启异步配置,即,配置上@EnableAsync注解;

@Async的原理概括:
@Async的原理是通过 Spring AOP 动态代理 的方式来实现的。
Spring 容器启动初始化bean时,判断类中是否使用了@Async注解,如果使用了则为其创建切入点和切入点处理器,根据切入点创建代理,在线程调用@Async注解标注的方法时,会调用代理,执行切入点处理器invoke方法,将方法的执行提交给线程池中的另外一个线程来处理,从而实现了异步执行。
所以,需要注意的一个错误用法是,如果a方法调用它同类中的标注@Async的b方法,是不会异步执行的,因为从a方法进入调用的都是该类对象本身,不会进入代理类。
因此,相同类中的方法调用带@Async的方法是无法异步的,这种情况仍然是同步

@Async使用
在Spring中启用@Async:

@Async注解在使用时,如果不指定线程池的名称,则使用Spring默认的线程池,Spring默认的线程池为SimpleAsyncTaskExecutor。(这种实现不会重用任何线程,每次调用都会创建一个新的线程)
方法上一旦标记了这个@Async注解,当其它线程调用这个方法时,就会开启一个新的子线程去异步处理该业务逻辑。

3.2、@Async注解失效情况
  • 1.注解@Async的方法不是public方法
  • 2.注解@Async的返回值只能为void或Future
  • 3.注解@Async方法使用static修饰也会失效
  • 4.spring无法扫描到异步类,没加注解@Async或@EnableAsync注解
  • 5.调用方与被调用方不能在同一个类
  • 6.类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
  • 7.在Async方法上标注@Transactional是没用的.但在Async方法调用的方法上标注@Transcational是有效的

1.在当前类直接使用@Async失效是因为没有经过代理类,没有走拦截。如果要生效,要把生成好的代理类对象传给目标对象,再通过目标对象.addOrderLog()方法,这时候才会经过代理对象走invoke方法做拦截。

2.官方建议最好单独新建一类,专门处理异步操作

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值