SpringBoot下定时任务和异步任务的实现

亡梦爱人 提交于 2019-11-27 02:39:55
  1. 在SpringBoot中使用定时任务非常简单,使用定时任务最常用的三种方式为:静态(基于注解)、动态(基于接口,查询数据库)和多线程定时任务(异步的方式执行定时任务)

  2. 给定时任务设置执行时间的方式最主要的是通过cron表达式的方式设置,比如:0 47 15 * * ?

  3. Cron表达式参数每一位的具体含义如下

    1. 秒(0~59) 例如0/5表示每5秒
    2. 分(0~59)
    3. 时(0~23)
    4. 日(0~31)的某天,需计算
    5. 月(0~11)
    6. 周几( 可填1-7 或 SUN/MON/TUE/WED/THU/FRI/SAT)
  4. 0 47 15 * * ? 表示每天的15点47分执行

  5. 接下来介绍SpringBoot中定时人任务的使用

  6. 新建一个SpringBoot项目

  7. 引入相关的依赖

    <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.3.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
  8. 在SpringBoot启动类上面添加如下的注解

    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.annotation.EnableScheduling;
    
    @SpringBootApplication
    @EnableScheduling    //定时任务注解
    @EnableAsync         //异步任务注解
     //Mybatis的Mapper接口扫描注解
    @MapperScan(basePackages ="com.kangswx.springboottaskscheduled.mapper") 
    public class SpringbootTaskscheduledApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootTaskscheduledApplication.class, args);
        }
    
    }
    
  9. 静态定时任务的使用,新建一个静态定时任务的测试类(需要在测试类上面添加@Component注解)

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    @Component
    public class ScheduledTest {
    
    
        private static final Logger logger = LoggerFactory.getLogger(ScheduledTest.class);
    
        private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @Scheduled(cron = "0/5 * * * * ?")
        public void testTask(){
            logger.info("执行静态定时任务时间: " + sdf.format(new Date()
                    +", 线程名称"+Thread.currentThread().getName()));
        }
    
    }
    
  10. 启动SpringBoot项目,会发现打出如下的日志,表示每次执行的定时任务都是在同一个线程下执行的

    seconds (JVM running for 7.144)
    2019-08-12 09:51:20.004  INFO 15056 --- [   scheduling-1] c.k.s.taskscheduled.ScheduledTest        : 执行静态定时任务时间: 2019-08-12 09:51:20, 线程名称scheduling-1
    2019-08-12 09:51:25.001  INFO 15056 --- [   scheduling-1] c.k.s.taskscheduled.ScheduledTest        : 执行静态定时任务时间: 2019-08-12 09:51:25, 线程名称scheduling-1
    2019-08-12 09:51:30.001  INFO 15056 --- [   scheduling-1] c.k.s.taskscheduled.ScheduledTest        : 执行静态定时任务时间: 2019-08-12 09:51:30, 线程名称scheduling-1
    2019-08-12 09:51:35.001  INFO 15056 --- [   scheduling-1] c.k.s.taskscheduled.ScheduledTest        : 执行静态定时任务时间: 2019-08-12 09:51:35, 线程名称scheduling-1
    
  11. 动态定时任务(执行的时间通过查询数据库获取),每次执行结束后,会查询数据库中下次执行的时间

  12. 在数据库中新建表t_cron并添加一条数据

    DROP TABLE IF EXISTS `t_cron`;
    CREATE TABLE `t_cron` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
      `cron` varchar(20) NOT NULL COMMENT 'cron表达式',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
    
    INSERT INTO `t_cron` VALUES ('1', '0/5 * * * * ?');
    
  13. 添加表对应的实体类Cron

    public class Cron {
        private Integer id;
    
        private String cron;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getCron() {
            return cron;
        }
    
        public void setCron(String cron) {
            this.cron = cron == null ? null : cron.trim();
        }
    }
    
  14. 添加Cron对应的Mapper接口CronMapper

    import com.kangswx.springboottaskscheduled.entity.Cron;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    @Mapper
    @Repository
    public interface CronMapper {
    
        Cron selectByPrimaryKey(Integer id);
    
    }
    
  15. 添加CronMapper对应的Mybatis配置文件CronMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.kangswx.springboottaskscheduled.mapper.CronMapper" >
      <resultMap id="BaseResultMap" type="com.kangswx.springboottaskscheduled.entity.Cron" >
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="cron" property="cron" jdbcType="VARCHAR" />
      </resultMap>
      <sql id="Base_Column_List" >
        id, cron
      </sql>
      <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
        select 
        <include refid="Base_Column_List" />
        from t_cron
        where id = #{id,jdbcType=INTEGER}
      </select>
    </mapper>
    
  16. 编写动态定时任务测试类,在定时任务执行一段时间之后,将数据库中的cron表达式修改为0/10 * * * * ?

    @Component
    public class DynamicScheduleTask implements SchedulingConfigurer {
    
        private static final Logger logger = LoggerFactory.getLogger(DynamicScheduleTask.class);
    
        @Autowired
        private CronMapper cronMapper;
    
        @Override
        public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
    
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
            scheduledTaskRegistrar.addTriggerTask(
                    //1.添加任务内容(Runnable)
                    () -> logger.info("执行动态定时任务"+ sdf.format(new Date())),
                    //2.设置执行周期(Trigger)
                    triggerContext -> {
                        //2.1 从数据库获取执行周期
                        Cron cron = cronMapper.selectByPrimaryKey(1);
                        String cron_str = cron.getCron();
                        //2.2 合法性校验.
                        if(StringUtils.isEmpty(cron_str)){
                            logger.info("获取定时任务执行时间出错");
                        }
                        //2.3 返回执行周期(Date)
                        return new CronTrigger(cron_str).nextExecutionTime(triggerContext);
                    }
            );
        }
    }
    
  17. 启动SpringBoot项目,会发现如下的日志,表示动态定时任务每次执行的时候依然在同一个线程下,但是执行的时间会随着数据库中的数据的改变而改变

    2019-08-12 10:05:30.002  INFO 304 --- [pool-1-thread-1] c.k.s.taskscheduled.DynamicScheduleTask  : 执行动态定时任务2019-08-12 10:05:30, 线程名为: pool-1-thread-1
    2019-08-12 10:05:35.001  INFO 304 --- [pool-1-thread-1] c.k.s.taskscheduled.DynamicScheduleTask  : 执行动态定时任务2019-08-12 10:05:35, 线程名为: pool-1-thread-1
    2019-08-12 10:05:40.001  INFO 304 --- [pool-1-thread-1] c.k.s.taskscheduled.DynamicScheduleTask  : 执行动态定时任务2019-08-12 10:05:40, 线程名为: pool-1-thread-1
    2019-08-12 10:05:50.000  INFO 304 --- [pool-1-thread-1] c.k.s.taskscheduled.DynamicScheduleTask  : 执行动态定时任务2019-08-12 10:05:50, 线程名为: pool-1-thread-1
    2019-08-12 10:06:00.002  INFO 304 --- [pool-1-thread-1] c.k.s.taskscheduled.DynamicScheduleTask  : 执行动态定时任务2019-08-12 10:06:00, 线程名为: pool-1-thread-1
    
  18. 多线程定时任务,新增多线程定时任务测试类

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    @Component
    public class MultithreadScheduleTask {
    
        private static final Logger logger = LoggerFactory.getLogger(MultithreadScheduleTask.class);
    
        private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @Scheduled(cron = "0/2 * * * * ?")
        @Async
        public void first() throws InterruptedException {
            logger.info("第一个定时任务执行时间: " + sdf.format(new Date()) 
                    + "线程名为: " + Thread.currentThread().getName());
            Thread.sleep(1000*10L);
        }
    
    }
    
  19. 启动SpringBoot项目,会看到如下的日志,这表示多线程定时任务字每次执行的时候会新开一个线程

    2019-08-12 10:12:18.015  INFO 16160 --- [         task-1] c.k.s.t.MultithreadScheduleTask          : 第一个定时任务执行时间: 2019-08-12 10:12:18线程名为: task-1
    2019-08-12 10:12:20.001  INFO 16160 --- [         task-2] c.k.s.t.MultithreadScheduleTask          : 第一个定时任务执行时间: 2019-08-12 10:12:20线程名为: task-2
    2019-08-12 10:12:22.001  INFO 16160 --- [         task-3] c.k.s.t.MultithreadScheduleTask          : 第一个定时任务执行时间: 2019-08-12 10:12:22线程名为: task-3
    2019-08-12 10:12:24.001  INFO 16160 --- [         task-4] c.k.s.t.MultithreadScheduleTask          : 第一个定时任务执行时间: 2019-08-12 10:12:24线程名为: task-4
    2019-08-12 10:12:26.004  INFO 16160 --- [         task-5] c.k.s.t.MultithreadScheduleTask          : 第一个定时任务执行时间: 2019-08-12 10:12:26线程名为: task-5
    
  20. 异步任务:异步任务一般用于执行费核心任务,用于提高响应速度,比如向用户发通知邮件,短信等,在不影响主线程响应的情况下,异步执行相关的方法

  21. 在使用异步任务的时候要注意,在某些情况下,异步任务会失效

  22. 新增异步任务测试的Service接口

    public interface AsyncService {
    
        void firstTack() throws InterruptedException;
    
        void secondTask() throws InterruptedException;
    
        void thirdTask() throws InterruptedException;
    }
    
  23. 新增异步任务测试的Service实现类

    import com.kangswx.springboottaskscheduled.service.AsyncService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Service;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    @Service
    public class AsyncServiceImpl implements AsyncService {
    
        private static final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);
    
        private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @Async
        @Override
        public void firstTack() throws InterruptedException {
            logger.info("第一个异步任务开始执行: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName());
            Thread.sleep(1000 * 3L);
            logger.info("第一个异步任务执行结束: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName());
        }
    
        @Async
        @Override
        public void secondTask() throws InterruptedException {
            logger.info("第二个异步任务开始执行: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName());
            Thread.sleep(1000 * 4L);
            logger.info("第二个异步任务执行结束: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName());
        }
    
        @Async
        @Override
        public void thirdTask() throws InterruptedException {
            logger.info("第三个异步任务开始执行: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName());
            Thread.sleep(1000 * 5L);
            logger.info("第三个异步任务执行结束: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName());
        }
    }
    
  24. 新增异步任务测试的AsyncController

    import com.kangswx.springboottaskscheduled.service.AsyncService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    @RestController
    @RequestMapping("/api/v1/asynctask")
    public class AsyncController {
    
        private static final Logger logger = LoggerFactory.getLogger(AsyncController.class);
    
        @Autowired
        private AsyncService asyncService;
    
        /**
         * 调用异步任务接口
         * @throws InterruptedException
         */
        @GetMapping("excute")
        public void excuteAsync() throws InterruptedException {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date start = new Date();
            logger.info("调用异步任务开始"+sdf.format(start)
                    +", 线程名称为"+Thread.currentThread().getName());
            asyncService.firstTack();
            asyncService.secondTask();
            asyncService.thirdTask();
            Date end = new Date();
            logger.info("调用异步任务结束"+sdf.format(end));
    
            logger.info("执行总时间为: " + (end.getTime() - start.getTime())
                    +", 线程名称为"+Thread.currentThread().getName());
        }
    }
    
  25. 在浏览器上面访问http://localhost:8081/api/v1/asynctask/excute ,会看到如下的日志,从日志中可以看出主线程早就执行结束,但异步任务仍然在新开的线程下继续执行

    2019-08-12 10:42:32.359  INFO 15160 --- [nio-8081-exec-6] c.k.s.controller.AsyncController         : 调用异步任务开始2019-08-12 10:42:32, 线程名称为http-nio-8081-exec-6
    2019-08-12 10:42:32.360  INFO 15160 --- [         task-1] c.k.s.service.impl.AsyncServiceImpl      : 第二个异步任务开始执行: 2019-08-12 10:42:32 线程名为: task-1
    2019-08-12 10:42:32.360  INFO 15160 --- [nio-8081-exec-6] c.k.s.controller.AsyncController         : 调用异步任务结束2019-08-12 10:42:32
    2019-08-12 10:42:32.360  INFO 15160 --- [         task-2] c.k.s.service.impl.AsyncServiceImpl      : 第三个异步任务开始执行: 2019-08-12 10:42:32 线程名为: task-2
    2019-08-12 10:42:32.360  INFO 15160 --- [nio-8081-exec-6] c.k.s.controller.AsyncController         : 执行总时间为: 1, 线程名称为http-nio-8081-exec-6
    2019-08-12 10:42:32.360  INFO 15160 --- [         task-8] c.k.s.service.impl.AsyncServiceImpl      : 第一个异步任务开始执行: 2019-08-12 10:42:32 线程名为: task-8
    2019-08-12 10:42:35.361  INFO 15160 --- [         task-8] c.k.s.service.impl.AsyncServiceImpl      : 第一个异步任务执行结束: 2019-08-12 10:42:35 线程名为: task-8
    2019-08-12 10:42:36.361  INFO 15160 --- [         task-1] c.k.s.service.impl.AsyncServiceImpl      : 第二个异步任务执行结束: 2019-08-12 10:42:36 线程名为: task-1
    2019-08-12 10:42:37.361  INFO 15160 --- [         task-2] c.k.s.service.impl.AsyncServiceImpl      : 第三个异步任务执行结束: 2019-08-12 10:42:37 线程名为: task-2
    
  26. 具体的代码实现可见 SpringBoot下实现定时任务和异步任务

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!