Spring Boot任务管理

Posted shi_zi_183

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot任务管理相关的知识,希望对你有一定的参考价值。

Spring Boot任务管理

开发Web应用时,多数应用都具备任务调度功能。常用的任务包括异步任务、定时任务和发邮件任务。我们以数据库报表为例看看任务调度如何帮助改善系统设计。报表可能是错综复杂的,用户可能需要很长时间找到需要的报表数据,此时,我们可以在这个报表应用中添加异步任务减少用户等待时间,从而提高用户体验;除此之外,还可以在报表应用中添加定时任务和邮件任务,以便用户可以安排在任何他们需要的时间定时生成报表,并在Email中发送。

异步任务

Web应用开发中,大多数情况都是通过同步方式完成数据交互处理,但是,当任务与第三方系统的交互时,容易造成响应迟缓的情况,之前大部分都是使用多线程完成此类任务,除此之外,还可以使用异步调用的方式完美解决这个问题。根据异步处理方式的不同,可以将异步任务的调用分为无返回值异步任务调用和有返回值异步任务调用,接下来我们在Spring Boot项目中分别针对这两种方式讲解。

无返回值异步任务调用

在实际开发中,项目可能会向新注册用户发送短信验证码,此时,可以考虑使用异步任务调用的方式实现,一方面是因为用户对这个时效性要求不是特别高,另一方面在特定时间范围内没有收到验证码,用户可以点击再次发送验证码。
Spring Boot项目创建
使用Spring Initializr方式创建一个名为chapter09的Spring Boot项目,在Dependencies依赖中选择Web模块中的Web依赖。
需要说明的是,Spring框架提供了对异步任务的支持,Spring Boot框架继承了这一异步任务功能。在Spring Boot中整合异步任务时,只需在项目中引入Web模块中的Web依赖就可以实现这种异步任务功能。

编写异步调用方法
在chapter09项目中创建名为com.example.service的包,并在该包下创建一个业务实现类MyAsyncService,在该类中编写模拟用户短信验证码发送的方法。
MyAsyncService.java

package com.example.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class MyAsyncService {
    @Async
    public void sendSMS() throws InterruptedException {
        System.out.println("调用短信验证码业务方式...");
        Long startTime=System.currentTimeMillis();
        Thread.sleep(5000);
        Long endTime=System.currentTimeMillis();
        System.out.println("短信业务执行完成耗时:" + (endTime - startTime));
    }
}

开启基于注解的异步任务支持
下一步编写的用户短信验证码发送业务方法中,使用@Async注解标记了异步方法,如果在Spring Boot中希望异步方法生效,还需要使用@EnableAsync注解开启基于注解的异步任务支持。@EnableAsync注解通常会添加在项目启动类上。
Chapter09Application.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync  //开启基于注解的异步任务支持
@SpringBootApplication
public class Chapter09Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter09Application.class, args);
    }
}

编写控制层业务调用方法
在chapter09项目中创建名为com.example.controller的包,并在该包下创建类MyAsyncController用于调用异步方法。在该类中模拟编写用户短信验证码发送的处理方法。
MyAsyncController.java

package com.example.controller;

import com.example.service.MyAsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyAsyncController {
    @Autowired
    private MyAsyncService myAsyncService;
    @GetMapping("/sendSMS")
    public String sendSMS() throws InterruptedException {
        Long startTime =System.currentTimeMillis();
        myAsyncService.sendSMS();
        Long endTime = System.currentTimeMillis();
        System.out.println("主流程耗时:" + (endTime - startTime));
        return "success";
    }
}

sendSMS()方法模拟发送短信,专门用于处理请求路径为"/sendSMS"的请求。
异步任务效果测试
启动chapter09项目,项目启动成功后,在浏览器上访问"http://localhost:8080/sendSMS"测试异步任务请求,此时会发现浏览器上会快速响应"success"信息。


上述案例中的异步方法是没有返回值的,这样主流程在执行异步方法时不会阻塞,而是继续向下执行主流程程序,直接向页面响应结果,而调用的异步方法会作为一个子线程单独执行,直到异步方法执行完成。

有返回值异步任务调用

在实际开发中,项目中可能会涉及有返回值的异步任务调用。例如,一个程序中需要调用两个业务方法对相关业务数据统计分析,并将统计结果汇总。
编写异步调用方法
在之前创建的MyAsyncService异步任务业务处理类中,添加两个模拟有返回值的异步任务业务处理方法。

    @Async
    public Future<Integer> processA() throws InterruptedException {
        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 InterruptedException {
        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);
    }

上述代码中,在MyAsyncService异步业务处理类中添加了两个分别处理业务A数据统计的方法processA()和处理业务B数据统计的方法processB(),在方法上都是用了@Async注解标记为异步方法。另外,上述两个异步方法都会有一定的处理时间,并且需要返回统计结果,示例中使用了new AsyncResult<Integer>(count)封装返回的异步结果数据,并将返回值设为Future<Integer>类型。
编写控制层业务调用方法
在之前创建的异步任务业务处理类MyAsyncController中,编写业务数据分析统计的请求处理方法。

	@GetMapping("/statistics")
    public String statistics() throws InterruptedException, ExecutionException {
        Long startTime =System.currentTimeMillis();
        Future<Integer> futureA=myAsyncService.processA();
        Future<Integer> futureB=myAsyncService.processB();
        int total = futureA.get()+futureB.get();
        System.out.println("异步任务数据统计汇总结果:" + total);
        Long endTime = System.currentTimeMillis();
        System.out.println("主流程耗时:" + (endTime - startTime));
        return "success";
    }

上述代码中,statistics()方法处理映射路径为"/statistics"的业务数据统计的请求,通过调用异步方法processA()和processB()输出主流程的耗时时长。
异步任务效果测试

定时任务

在实际开发中,可能会有这样一个需求,需要在每天的某个固定时间或者每隔一段时间让程序去执行某一个任务。例如,服务器数据定时在晚上零点备份。通常我们可以使用Spring框架提供的Scheduling Tasks实现这一定时任务的处理。

定时任务介绍

Spring框架的定时任务调度功能支持配置和注解两种方式,Spring Boot不仅继承了Spring框架定时任务调度功能,而且可以更好地支持注解方式的定时任务。
@EnableScheduling
@EnableScheduling注解是Spring框架提供的,用于开启基于注解方式的定时任务支持,该注解主要用在框架启动类上。
@Scheduled
@Scheduled注解同样是Spring框架提供的,匹配定时任务的执行规则,该注解主要用在定时业务方法上。@Scheduled注解提供有多个属性,精细化配置定时任务执行规则。

属性说明
cron类似于cron的表达式,可以定制定时任务触发的秒、分钟、小时、月中的日、月、周中的日
zone指定cron表达式将被解析的时区。默认情况下,该属性是空字符串(即使用服务器的本地时区)
fixedDelay表示在上一次任务执行结束后在指定时间后继续执行下一次任务(属性值为long类型)
fixedDelayString表示在上一次任务执行结束后在执行时间后继续执行下一次任务(属性值为long类型的字符串形式)
fixedRate表示每个指定时间执行一次任务(属性值为long类型)
fixedRateString表示每个指定时间执行一次任务(属性值为long类型的字符串形式)
initialDelay表示在fixedRate或fixedDelay任务第一次执行之前要延迟的毫秒数(属性值为long类型)
initialDelayString表示在fixedRate或fixedDelay任务第一次执行之前要延迟的毫秒数(属性值为long类型的字符串形式)

1)cron属性
cron属性是@Scheduled定时任务注解中最常用也是最复杂的一个属性,其属性值由类似于cron表达式的6位数组成,可以详细地指定定时任务执行的秒、分、小时、日、月、星期。

@Scheduled(cron = "0 * * * * MON_FRI")

上述代码中,cron = "0 * * * * MON_FRI"表示周一到周五每一分钟执行一次定时任务。第一位表示秒,第二位表示分,第三位表示小时,第四位表示月份中的日,第五位表示月份中的月,第六位星期,*表示的是任意时刻,MON-FRI表示的是周一到周五。

字段可取值允许的特殊字符
0~59, - * /
0~59, - * /
小时0~23, - * /
1-31, - * / ? L
1~12、月份对应英文前3个字母(大小写均可), - * /
星期0~7(0和7表示SUN)、星期对应英文前3个字母(大小写均可), - * / ? L
特殊字符说明示例
,表示枚举@Scheduled(cron=“1,3,5 * * * * *”)表示任意时间的1、3、5秒钟都会执行
-表示区间@Scheduled(cron=“0 0-5 14 * * ?”)表示每天下午2:00~2:05期间,每一分钟触发一次
*表示任意可取值@Scheduled(cron=“0 0 12 * * *”)表示每天中午12:00触发一次
/表示步长@Scheduled(cron=“0/5 * * * * *”)表示从任意时间的整秒开始,每隔5秒都会执行
?日/星期冲突匹配符@Scheduled(cron=“0 * * 26 * ?”)表示每月的26日每一分钟都执行
L最后@Scheduled(cron=“0 0 * L * ?”)表示每月最后一日每一小时都执行

需要注意的是,注解@Scheduled的cron属性与cron的表达式并不完全一致,@Scheduled的cron属性只提供6位字段赋值。cron表达式支持的特殊字符在@Scheduled的cron属性中是不支持的,例如,字符C、W、#可以作为cron表达式的字符,但是无法作为@Scheduled的cron属性值。
2)zone属性
zone属性主要与cron属性配合使用,指定解析cron属性值的时区。通常情况下,不需要指定zone属性,cron属性值会自动以服务器所在区域作为本地时区进行表达式解析。例如,中国地区服务器的时区通常默认为Asia/Shanghai。
3)fixedDelay和fixedDelayString属性
fixedDelay和fixedDelayString属性的作用类似,用于在上一次任务执行完毕后,一旦到达指定时间就继续执行下一次任务。两者的区别在于属性值的类型不同,fixedDelay属性值为long类型,而fixedDelayString属性值是数值字符串。

@Scheduled(fixedDelay=5000)
@Scheduled(fixedDelayString="5000")

4)fixedRate和fixedRateString属性
fixedRate和fixedRateString属性的作用类似,指定每相隔一段时间重复执行一次定时任务。他们的主要区别是属性值的类型不同,其中fixedRate属性值为long类型,fixedRateString属性值为数值字符串。

@Scheduled(fixedRate=5000)
@Scheduled(fixedRateString="5000")

5)initialDelay和initialDelayString属性
initialDelay和initiDelayString属性的作用类似,主要是与fixedRate/fixedRateString或者fixedDelay/fixedDelayString属性配合使用,指定定时任务第一次执行的延迟时间,然后再按照各自相隔时间重复执行任务。

@Scheduled(initialDelay=1000,fixedDelay=5000)
@Scheduled(initialDelay=1000,fixedRate=5000)

上述代码在程序启动后,会延迟1秒后再执行第一次定时任务,然后相隔5秒重复执行定时任务。

定时任务实现

编写定时任务业务处理方法
在项目的com.example.service的包下新建一个定时任务管理的业务处理类ScheduledTaskService,并在该类中编写对应的定时任务处理方法。
ScheduledTaskService.java

package com.example.service;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;

@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.println(String.format("fixedRate第%s次执行,当前时间为:%s", count1++, dateFormat.format(new Date())));
    }
    @Scheduled(fixedDelay = 60000)
    public void scheduledTaskAfterSleep() throws InterruptedException {
        System.out.println(String.format("fixedDelay第%s次执行,当前时间为:%s", count3, dateFormat.format(new Date())));
        Thread.sleep(10000);
    }
    @Scheduled(cron = "0 * * * * *")
    public void scheduledTaskCron(){
        System.out.println(String.format("cron第%s次执行,当前时间为:%s", count3++, dateFormat.format(new Date())));
    }
}

使用@Scheduled注解声明了3个定时任务方法,这3个方法定制的执行规则基本相同,都是每隔1分钟重复执行一次定时任务。在使用fixedDelay属性的方法scheduledTaskAfterSleep()中,使用Thread.sleep(10000)模拟该定时任务处理耗时为10秒。需要说明的是,Spring Boot使用定时任务相关注解时,必须先引入Spring框架依赖。由于这里之前创建的项目引入了Web依赖,Web依赖包含了Spring框架依赖,因此这里可以直接使用相关注解。
开启基于注解的定时任务支持
为了使Spring Boot中基于注解方式的定时任务生效,还需要再项目启动类上使用@EnableScheduling注解开启基于注解的定时任务支持。
Chapter09Application.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableAsync  //开启基于注解的异步任务支持
@EnableScheduling  //开启基于注解的异步任务支持
@SpringBootApplication
public class Chapter09Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter09Application.class, args);
    }
}

定时任务效果测试
启动chapter09项目

邮件任务

再实际开发中,邮件发送服务应该是网站的必备功能之一,例如用户注册验证、忘记密码、给用户发送营销信息等。在早期开发过程中,开发人员通常会使用JavaMail相关API实现邮件发送功能,后来Spring推出JavaMailSender简化了邮件发送的过程和实现,Spring Boot框架对Spring提出的邮件发送服务也进行了整合支持。

发送纯文本邮件

邮件发送任务中,最简单的莫过于纯文本邮件的发送。在定制纯文本邮件时,只需要指定收件人邮件账号、邮件标题和邮件内容即可。
添加邮件服务依赖启动器
打开项目chapter09的pom.xml文件,在该依赖文件中添加Spring Boot整合支持的邮件服务依赖启动器spring-boot-starter-mail。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

当添加上述依赖后,Spring Boot自动配置的邮件服务会生效,在邮件发送任务时,可以直接使用Spring框架提供的JavaMailSender接口或者它的实现类JavaMailSenderImpl。
添加邮件服务配置
在项目中添加邮件服务依赖启动器后,还需要在配置文件中添加邮件服务相关的配置,确保邮件服务正常发送。打开项目的application.properties全局配置文件,在该文件中添加发件人邮箱服务配置和邮件服务超时的相关配置。

# 发件人邮箱服务器相关配置
spring.mail.host=smtp.qq.com
spring.mail.port=587
# 配置个人QQ账户和密码(密码是加密后的授权码)
spring.mail.username=**********@qq.com
spring.mail.password=dza*******gbagc
spring.mail.default-encoding=UTF-8
#邮件服务超时时间配置
spring.mail.properties.mail.smtp.connectiontime=5000
spring.mail.properties.mail.smtp.timeout=3000
spring.mail.properties.mail.smtp.writetimeout=5000

这里需要开启qq邮箱的smtp服务,并生成授权码。
邮件服务超时配置可以灵活更改超时时间,如果没有配置邮件服务超时的话,Spring Boot内部默认超时是无限制的,这可能会造成线程被无响应的邮件服务器长时间阻塞。
定制邮件发送服务
先以发送纯文本邮件为例,在之前创建的com.example.service的包中,新建一个邮件发送任务管理的业务处理类SendEmailService,并在该类中编写一个发送纯文本邮件的业务方法。
SendEmailService.java

package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.stereotype.Service;

@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
        SimpleMailMessage message=new SimpleMailMessage();
        message.setFrom(from);
        message.setTo(to);
        message.setSubject(subject);
        message.setText(text);
        try{
            //发送邮件
            mailSender.send(message);
            System.out.println("纯文本邮件发送成功");
        }catch (MailException e){
            System.out.println("纯文本邮件发送失败" + e.getMessage());
            e.printStackTrace();
        }
    }
}

编写了一个发送纯文本邮件的sendSimpleEmail()方法,在方法中通过SimpleMailMessage类定制了邮件信息的发件人地址(From)、收件人地址(To)、邮件标题(Subject)和邮件内容(Text),最后使用JavaMailSenderImpl的send()方法实现纯文本邮件发送。
纯文本邮件发送效果测试
Chapter09ApplicationTests.java

package com.example;

import com.example.service.SendEmailService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Chapter09ApplicationTests {
    @Autowired
    private SendEmailService sendEmailService;
    @Test
    public void sendSimpleMailTest() {
        String to="********@qq.com";

以上是关于Spring Boot任务管理的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot任务管理之邮件任务

Spring Boot任务管理

Spring boot:thymeleaf 没有正确渲染片段

Quartz+Spring Boot实现动态管理定时任务

Spring Boot与检索任务安全分布式热部署监控管理

解决spring-boot启动中碰到的问题:Cannot determine embedded database driver class for database type NONE(转)(代码片段