基于SpringBoot的任务管理三种方式

前言

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

一,异步任务

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

1.1 无返回值异步任务调用

  1. 在启动类上添加注解 @EnableAsync(开启基于注解的异步任务支持)
  2. 在指定无返回值的业务方法上添加 @Asyn注解,实现异步请求方法

启动类

@EnableAsync
@SpringBootApplication
public class AsyncApplication {

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

Service 层代码

@Service
public class AsyncService {

    @Async
    public void send(){
        System.out.println("调用短信验证码方法......");
        long start = System.currentTimeMillis();
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("出现异常:"+ e.getMessage());
        }
        long end = System.currentTimeMillis();
        System.out.println("短信验证码方法执行时间:"+(end - start) + "毫秒");
    }
}

Controller 层代码

@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public R testAsync(){
        long start = System.currentTimeMillis();
        asyncService.send();
        long end = System.currentTimeMillis();
        System.out.println("调用结束主流程耗时"+(end-start)+"毫秒");
        return R.ok().put("success", "调用结束主流程耗时"+(end-start)+"毫秒");
    }
}

若没有使用异步处理(注释掉 @Async 注解),访问接口返回数据如下

在这里插入图片描述

控制台输出内容:

在这里插入图片描述

若使用异步处理(使用 @Async 注解),访问接口返回数据如下

在这里插入图片描述

控制台输出内容:

在这里插入图片描述

当未使用异步处理时,访问接口需要等待4s左右才返回数据,而使用异步处理后几乎不需要等待时间,用户体验更佳了。

需要说明的是,无返回值异步方法在被主流程方法调用时,主流程方法不会阻塞,而是继续向下执行主流程方法内容,直接向页面响应结果,而调用的异步方法会作为一个子线程单独执行,直到异步方法执行完成。

1.2 有返回值异步任务调用

  1. 在启动类上添加注解 @EnableAsync(开启基于注解的异步任务支持)
  2. 在指定有返回值的业务方法上添加 @ASync注解,实现异步请求方法
    注意: 返回值必须用Future 泛型封装,例如: new AsyncResult(count),当想获取装的值时用 Futue 对象调用其 get() 方法即可获得。

业务场景设定:假设业务A(耗时3秒)进行数据分析返回一个整数值,业务B(耗时5秒)也进行数据分析返回一个整数值;且两者没有相互影响,计算业务A和业务B的返回整数值相加的结果。

同步方式: 先调用业务A ,再调用B,再将A返回值与B返回值相加,最终将结果返回给用户,总耗时肯定大于8秒。这种方式相对来说整体耗时更长,响应慢,降低了用户体验。

异步方式:

@Async
public Future<Integer> processA(){
    System.out.println("开始分析并统计业务A数据......");
    long start = System.currentTimeMillis();
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
        System.out.println("出现异常:"+ e.getMessage());
    }
    Integer numA = 20;
    long end = System.currentTimeMillis();
    System.out.println("业务A数据统计共耗时:"+(end - start));
    return new AsyncResult<Integer>(numA);
}

@Async
public Future<Integer> processB(){
    System.out.println("开始分析并统计业务B数据......");
    long start = System.currentTimeMillis();
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
        System.out.println("出现异常:"+ e.getMessage());
    }
    Integer numB = 25;
    long end = System.currentTimeMillis();
    System.out.println("业务B数据统计共耗时:"+(end - start));
    return new AsyncResult<Integer>(numB);
}
@GetMapping("/asyncTask")
public R Async() throws Exception {
    long start = System.currentTimeMillis();
    Future<Integer> processA = asyncService.processA();
    Future<Integer> processB = asyncService.processB();
    int sum = processA.get() + processB.get();
    //等待A B结果相加成功后再向下执行,这里阻塞了
    long end = System.currentTimeMillis();
    System.out.println("调用结束主流程耗时"+(end-start)+"毫秒");
    return R.ok().put("data","success");
}

响应结果:小于8秒,提升了系统响应速度

在这里插入图片描述
需要说明的是,有返回值异步方法在被主流程方法调用时,主流程方法是会短暂阻塞的,需要等待并获取异步方法的返回结果,而调用的两个异步方法会作为两个子线程并行执行,直到异步方法执行完成并返回结果,这样主流程会在最后一个异步方法返回结果后跳出阻塞状态。

二、定时任务

2.1 背景介绍

在实际开发中,可能会有这样一个需求,需要在每天的某个固定时间或者每隔一段时间让程序去执行某一个任务。例如,服务器数据定时在晚上零点备份。通常我们可以使用 scheduling Tasks 实现这一定时任务的处理;在时间定时任务时需要了解下几种和定时任务相关的注解,具体如下:

  1. @EnableScheduling 用于开启基于注解方式的定时任务支持,该注解主要用在项目启动类上。
  2. @scheduled 配置定时任务的执行规则,该注解主要用在定时业务方法上。

2.2 cron 表达式详解

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:

1Seconds Minutes Hours DayofMonth Month DayofWeek Year2Seconds Minutes Hours DayofMonth Month DayofWeek

corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份

各字段的含义:

字段允许值允许的特殊字符
秒(Seconds)0~59的整数, - * / 四个字符
秒(Seconds)0~59的整数, - * / 四个字符
小时(Hours)0~23的整数, - * / 四个字符
日期(DayofMonth)1~31的整数(但是你需要考虑你月的天数),- * ? / L W C 八个字符
月份(Month)1~12的整数或者 JAN-DEC, - * / 四个字符
星期(DayofWeek)1~7的整数或者 SUN-SAT (1=SUN), - * ? / L C # 八个字符
年(可选,留空)(Year)1970~2099, - * / 四个字符

2.3 常用表达式示例

(0)0/20 * * * * ? 表示每20秒 调用任务

(1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调用任务

(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15调用任务

(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15调用任务

(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点调用任务

(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时调用任务

(6)0 0 12 ? * WED 表示每个星期三中午12点调用任务

(7)0 0 12 * * ? 每天中午12点调用任务

(8)0 15 10 ? * * 每天上午10:15调用任务

(9)0 15 10 * * ? 每天上午10:15调用任务

(10)0 15 10 * * ? * 每天上午10:15调用任务

(11)0 15 10 * * ? 2005 2005年的每天上午10:15调用任务

(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟调用任务

(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟调用任务

(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟调用任务

(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟调用任务

(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44调用任务

(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15调用任务

(18)0 15 10 15 * ? 每月15日上午10:15调用任务

(19)0 15 10 L * ? 每月最后一日的上午10:15调用任务

(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15调用任务

(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15调用任务

(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15调用任务

2.4 测试代码与结果

//每10秒输出一下当前日期
@Service
public class ScheduleService {

    @Scheduled(cron = "0/10 * * * * ?")
    public void scheduleService(){
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("当前时间: " + dateFormat.format(new Date()));
    }
}

在这里插入图片描述

三、邮箱任务

3.1 SpringBoot 发送邮件

  1. 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
  1. 准备工作,获取邮箱授权码
    在这里插入图片描述
spring:
  # 邮件
  mail:
    username: 1214770129@qq.com
    password: 授权码
    host: smtp.qq.com
    port: 465
    protocol: smtp
    properties:
      mail:
        smtp:
          ssl:
            enable: true
          auth: true
          starttls:
            enable: true
            required: true
          socketFactory:
            port: 465
            class: javax.net.ssl.SSLSocketFactory
@SpringBootTest
public class Boot1ApplicationTests {
    
    @Autowired
    JavaMailSender javaMailSender;

    // 发送普通邮件
    @Test
    public void sendSimpleMail() {
        // 构建一个邮件对象
        SimpleMailMessage message = new SimpleMailMessage();
        // 设置邮件主题
        message.setSubject("这是一封测试邮件");
        // 设置邮件发送者,这个跟application.yml中设置的要一致
        message.setFrom("877058128@qq.com");
        // 设置邮件接收者,可以有多个接收者,中间用逗号隔开,以下类似
        // message.setTo("1*****@qq.com","2*****qq.com");
        message.setTo("877058128@qq.com");
        // 设置邮件发送日期
        message.setSentDate(new Date());
        // 设置邮件的正文
        message.setText("这是测试邮件的正文");
        // 发送邮件
        javaMailSender.send(message);
    }
    
    // 发送富文本邮件
    @Test
    public void sendMimeMail() throws MessagingException {
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        // 构建一个邮件对象
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
        // 设置邮件主题
        helper.setSubject("这是一封测试邮件");
        // 设置邮件发送者,这个跟application.yml中设置的要一致
        helper.setFrom("877058128@qq.com");
        helper.setTo("877058128@qq.com");
        // 设置邮件发送日期
        helper.setSentDate(new Date());
        // 设置邮件的正文 true:是html文件
        helper.setText("<h1 style=\"color:red\">这是测试邮件的正文111</h1>",true);
        // 发送邮件
        javaMailSender.send(mimeMessage);
    }

}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值