SpringBoot如何实现动态的定时任务(二)

时间:2020-8-30 作者:admin


SpringBoot实现动态的定时任务

一、介绍

SpringBoot项目中,创建定时任务除了使用@Scheduled 注解外,还可以使用 SchedulingConfigurer
@Schedule 注解有一个缺点,其定时的时间不能动态的改变,而基于 SchedulingConfigurer 接口的方式可以做到动态的定时任务。

二、依赖

因为这种方式实现动态定时任务需要用到数据库,所以需要mysql驱动jpa依赖,以及lombok测试类依赖。

<!--继承SpringBoot父工程-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/>
    </parent>


		<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <!--        SpringBoot测试类-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--	jpa		-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--	mysql驱动		-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

三、定时任务配置类ScheduledConfig

我们不使用@Scheduled注解的形式,采用实现SchedulingConfigurer接口的形式来实现动态定时任务。

原理

@FunctionalInterface
public interface SchedulingConfigurer {
    void configureTasks(ScheduledTaskRegistrar var1);
}

实现SchedulingConfigurer接口需要重写configureTasks方法。
然后使用ScheduledTaskRegistrar调用addTriggerTask方法。
使用addTriggerTask方法就是增加一个触发任务。

public void addTriggerTask(Runnable task, Trigger trigger) {
        this.addTriggerTask(new TriggerTask(task, trigger));
    }

这个方法的参数有两个:
第一个参数task,简单解释就是我们要执行的定时时间,这个事件要实现Runnable接口,就是创建一个线程。
第二个参数trigger,就是触发器,点进Trigger这个类,

public interface Trigger {
    @Nullable
    Date nextExecutionTime(TriggerContext var1);
}

nextExecutionTime就是下一次执行的时间,所以我们可以发现这个Trigger的作用就是控制时间下一次执行的时间。
Trigger类这个就是我们实现动态控制定时任务的关键!
另外CronTrigger类实现了Trigger类,
所以我们在这里查询数据库中cron表达式,赋值到这里。这样我们通过修改数据库中的cron表达式,就可以达到动态的控制定时任务的执行了。

完整的配置类代码

package com.lsh.config;

import com.lsh.entity.SpringScheduledCron;
import com.lsh.respsitory.SpringScheduledCronRepository;
import com.lsh.task.DynamicPrintTask3;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * @author :LiuShihao
 * @date :Created in 2020/8/24 4:36 下午
 * @desc :查询数据库cron表达式  动态切换cron表达式
 *
 */
@Slf4j
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
    @Autowired
    private SpringScheduledCronRepository cronRepository;
	//注入我们的定时任务类
    @Autowired
    DynamicPrintTask task;
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        
            //可以通过改变数据库数据进而实现动态改变执行周期
            /**
             *
             * public void addTriggerTask(Runnable task, Trigger trigger) {
             *      this.addTriggerTask(new TriggerTask(task, trigger));
             *     }
             * 使用这个方法会增加一个触发任务 :即  !这个定时任务的下一次执行时间!
             * 参数:Runnable    一个事件
             * 参数:Trigger     一个触发器
             * public interface Trigger {
             *     @Nullable
             *     Date nextExecutionTime(TriggerContext var1);   下一次执行时间
             * }
             */
            taskRegistrar.addTriggerTask(((Runnable) task3),
                    triggerContext -> {
                        //查询数据库  获得该类的cron表达式
                        String cronExpression = cronRepository.findSpringScheduledCronByCronKey(task3.getClass().getName()).getCronExpression();
                        log.info("cronRepository.findByCronExpression():"+cronExpression);
                        // CronTrigger实现了Trigger
                        return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
                    }
            );
        
    }
    @Bean
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(10);
    }
}

四、定时任务类

我们的定时任务类必须实现Runnable接口!

package com.lsh.task;

import com.lsh.respsitory.SpringScheduledCronRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author :LiuShihao
 * @date :Created in 2020/8/24 4:53 下午
 * @desc :
 */
@Component
public class DynamicPrintTask implements Runnable {
    public static Boolean isRun = false;
    @Autowired
    SpringScheduledCronRepository springScheduledCronRepository;

    @Override
    public void run() {
        if (isRun) return;
        isRun = true;
        //通过类名获得表达式信息  判断状态是否可用 1:正常   2:停用  如果不可用直接返回
        //this.getClass().getName()  获得当前类名   com.lsh.task.DynamicPrintTask3
        Integer status = springScheduledCronRepository.findSpringScheduledCronByCronKey(this.getClass().getName()).getStatus();
        if (2 == status){
            return;
        }
        System.out.println("-----=====定时任务3:"+ LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        isRun = false;
    }
}

五、JPA

package com.lsh.respsitory;

import com.lsh.entity.SpringScheduledCron;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author :LiuShihao
 * @date :Created in 2020/8/24 4:58 下午
 * @desc :
 */

public interface SpringScheduledCronRepository extends JpaRepository<SpringScheduledCron, Integer> {
    //根据CronKey查找表达式
    SpringScheduledCron findSpringScheduledCronByCronKey(String CronKey);
}

六、实体类

package com.lsh.entity;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

/**
 * @author :LiuShihao
 * @date :Created in 2020/8/24 5:01 下午
 * @desc :
 */
@Data
@Entity(name = "spring_scheduled_cron")
public class SpringScheduledCron {
    @Id
    @Column(name = "cron_id")
    private Integer cronId;
    @Column(name = "cron_key", unique = true)
    private String cronKey;
    @Column(name = "cron_expression")
    private String cronExpression;
    @Column(name = "task_explain")
    private String taskExplain;
    private Integer status;
}

sql语句

CREATE TABLE `spring_scheduled_cron` (
  `cron_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `cron_key` varchar(128) NOT NULL COMMENT '定时任务完整类名',
  `cron_expression` varchar(20) NOT NULL COMMENT 'cron表达式',
  `task_explain` varchar(50) NOT NULL DEFAULT '' COMMENT '任务描述',
  `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1:正常;2:停用',
  PRIMARY KEY (`cron_id`),
  UNIQUE KEY `cron_key` (`cron_key`),
  UNIQUE KEY `cron_key_unique_idx` (`cron_key`)
) 

七、yml配置文件

spring:
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/day27
    username: root
    password: 123456
  jpa:
    show-sql: true
server:
  port: 8089

八、主启动类

@SpringBootApplication
@EnableScheduling
public class TimerTaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(TimerTaskApplication.class);
    }
}

九、启动测试

表数据:
SpringBoot如何实现动态的定时任务(二)
SpringBoot如何实现动态的定时任务(二)

缺点

不过这种方式也有缺点,就是增加了数据库的压力,长时间占着数据库的连接,查询基数大。
我们可以引入缓存来减轻数据库压力,减少内存消耗
缓存依赖:

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

另外除了引入缓存,还用另外一种方式来解决这个问题,就是使用MQ延时队列也可以实现动态的控制定时任务使用MQ延时队列来解决动态定时任务问题

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。