开发Web应用时,多数应用都具备任务调度功能。常见的任务包括:异步任务、定时任务和发邮件任务。
异步任务
- Web应用开发中,大多数情况都是通过同步方式完成数据交互处理,但是当在处理与第三方系统的交互时,容易造成响应迟缓的情况,之前大部分都是使用多线程完成该类任务,我们还可以使用异步调用的方式解决该问题。
- 根据异步处理方式的不同,可以将异步任务的调用分为无返回值异步任务调用和有返回值异步任务调用。
异步任务——无返回值异步任务调用
模拟向用户发送验证码的过程
- Spring框架提供了对异步任务的支持,SpringBoot框架继承了异步的功能,在创建项目时,只需引入Web依赖即可使用该部分的功能。
-
编写异步调用方法
@Service
public class MyAsyncService {
@Async
public void sendSMS() throws Exception{
System.out.println("调用短信验证码业务服务方法.....");
Long startTime = System.currentTimeMillis();
Thread.sleep(5000);
Long endTime = System.currentTimeMillis();
System.out.println("短信业务执行完成耗时:"+(endTime-startTime));
}
}
-
开启基于注解的异步任务支持
- 在主程序类上加入
@EnableAsync
注解开启异步任务支持
-
编写控制层业务调用方法
@RestController
public class MyAsyncController {
@Autowired
private MyAsyncService myService;
@GetMapping("/sendSMS")
public String sendSMS() throws Exception{
Long startTime = System.currentTimeMillis();
myService.sendSMS();
Long endTime = System.currentTimeMillis();
System.out.println("主流程耗时:"+(endTime-startTime));
return "success";
}
}
- 效果测试:访问/sendSMS请求时,会发现浏览器会快速响应"success"信息
- 该异步方法无返回值,这样主流程在执行异步方法时不会阻塞,而是继续向下执行主流程程序,直接向页面响应结果,而调用的异步方法会调用一个子线程单独执行,直到异步方法执行完成。
异步任务——有返回值异步任务调用
实际开发中,会实际有返回值的异步任务调用
@Async
public Future<Integer> processA() throws Exception{
System.out.println("开始分析并统计业务A数据");
Long startTime = System.currentTimeMillis();
Thread.sleep(4000);
int count = 123456;
Long endTime = System.currentTimeMillis();
System.out.println("业务A数据统计耗时:"+(endTime-startTime));
return new AsyncResult<Integer>(count);
}
@Async
public Future<Integer> processB() throws Exception{
System.out.println("开始分析并统计业务B数据");
Long startTime = System.currentTimeMillis();
Thread.sleep(5000);
int count = 654321;
Long endTime = System.currentTimeMillis();
System.out.println("业务B数据统计耗时:"+(endTime-startTime));
return new AsyncResult<Integer>(count);
}
@GetMapping("/statistics")
public String statistics() throws Exception{
Long startTime = System.currentTimeMillis();
Future<Integer> futureA = myService.processA();
Future<Integer> futureB = myService.processB();
int total = futureA.get()+futureB.get();
System.out.println("异步任务数据统计汇总结果:"+total);
Long endTime = System.currentTimeMillis();
System.out.println("主流程耗时:"+(endTime-startTime));
return "success";
}
- 测试效果分析:对于上述有返回值的异步方法,当返回值较多时主流程在执行异步方法时会有短暂阻塞,需要等待并获取异步方法的返回结果,而调用的两个异步方法会作为两个子线程并行执行,直到异步方法执行完成并返回结果,主线程会在最后一个异步方法返回结果后跳出阻塞状态。
定时任务
开发需求:对于某个方法需要在每天的某个时间或者每隔一段时间让程序去执行某一个任务,例如服务器数据备份.
- Spring框架的定时任务调度功能支持配置和注解两种方式,SpringBoot不仅继承了Spring框架定时任务调度功能,而且可以更好的支持注解方式的定时任务.
- @EnableScheduling注解:用于开启基于注解方式的定时任务支持,用于项目启动类
- @Scheduled注解:同样是Spring框架提供的,配置定时任务的执行规则,该注解主要用在定时业务方法上.
- cron属性:
@Scheduled(cron="0 * * * * MON-FRI")
- 表示周一到周五每一分钟执行一次定时任务,即该注解可以详细指定定时任务执行的秒/分/小时/日/月/星期. - zone属性:与cron配合使用,指定解析cron属性值的时区,默认为服务器所在区域作为本地时区进行解析.
- fixedDelay和fixedDelayString属性:用于上一次任务执行完毕后,一旦到达指定时间就继续执行下一次任务,二者区别为属性值的类型不同,前者为long类型,后者为string类型.
- fixedRate和fixedRateString属性:指定每相隔一段时间重复执行一次定时任务.与fixedDelay和fixedDelayString的区别为
- fixedDelay/fixedDelayString属性的下一次执行时间必须是在上一次任务执行完成后开始计时
- fixedRate/fixedRateString属性的下一次执行时间是在上一次任务执行开始时计时,如果遇到配置相隔时间小于定时任务执行时间,那么下一次任务会在上一次任务执行完成时立即重复执行.
- initialDelay和initialDelayString属性:主要与fixedRate/fixedRateString或fixedDelay/fixedDelayString属性配合使用,指定定时任务第一次执行的延迟时间,然后再按照各自的相隔时间重复执行任务.
定时任务实现
@Service
public class ScheduledTaskService {
private static final SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private Integer count1 = 1;
private Integer count2 = 1;
private Integer count3 = 1;
@Scheduled(fixedRate = 60000)
public void scheduledTaskImmediately(){
System.out.printf("fixedRate第%s次执行,当前时间为:%s%n",count1++,dateFormat.format(new Date()));
}
@Scheduled(fixedDelay = 60000)
public void scheduledTaskAfterSleep() throws InterruptedException{
System.out.printf("fixedDelay第%s次执行,当前时间为:%s%n%n",count2++,dateFormat.format(new Date()));
Thread.sleep(10000);
}
@Scheduled(cron = "0 * * * * *")
public void scheduledTaskCron(){
System.out.printf("cron第%s次执行,当前时间为:%s%n%n",count3++,dateFormat.format(new Date()));
}
}
上述三个方法都是每隔1分钟执行一次定时任务,在使用fixedDelay属性的方法scheduled时,模拟该定时任务耗时处理为10秒来对比观察
-
开启基于注解的定时任务支持- 项目启动类中加入@EnableScheduling注解
- 测试结果如下(符合预期):
邮件任务
- 实际开发中,邮件发送是很重要的功能,SpringBoot框架对Spring提出的邮件发送服务也进行了整合支持.
发送纯文本邮件
- 在定制纯文本邮件时,只需要指定收件人邮箱账号/邮件标题和邮件内容即可.
-
添加邮件服务依赖启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
# 发件人邮箱服务器相关配置
spring.mail.host=smtp.qq.com
spring.mail.port=587
# 配置个人QQ账户和密码(密码是加密后的授权码) - 比如qq邮箱的授权码在设置-账户-打开SMTP服务-发送短信后会收到授权码
spring.mail.username=xxxxx@qq.com
spring.mail.password=xxxxxxxxxxxx
spring.mail.default-encoding=UTF-8
# 邮件服务超时时间配置
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=3000
spring.mail.properties.mail.smtp.writetimeout=5000
@Service
public class SendEmailService {
@Autowired
private JavaMailSenderImpl mailSender;
@Value("@{spring.mail.username}")
private String from;
public void sendSimpleEmail(String to, String subject, String text){
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(to);
message.setSubject(subject);
message.setText(text);
try{
System.out.println("纯文本邮件发送成功");
}catch (MailException e){
System.out.println("纯文本邮件发送失败"+e.getMessage());
e.printStackTrace();
}
}
}
@SpringBootTest
public class Chapter09ApplicationTests {
@Autowired
private SendEmailService sendEmailService;
@Test
public void setSendEmailService(){
String to = "970313930@qq.com";
String subject="[纯文本邮件]标题";
String text="Spring Boot纯文本邮件发送内容测试......";
sendEmailService.sendSimpleEmail(to,subject,text);
}
}
对于其他添加附件及html格式可参考