Springboot定时任务

@Component
@EnableScheduling
public class SpringBootTestJob {

    @Scheduled(cron = "0/5 * * * * ?")
    public void testScheduled(){
        System.out.println("SpringBootTestJob test");
    }
}

这段代码使用了 Spring Boot 自带的定时任务机制。解释如下:

  • @Component:这是一个 Spring 的注解,表示将该类标识为 Spring 容器中的一个 Bean,让 Spring 自动管理它的生命周期。

  • @EnableScheduling:启用 Spring 的调度任务功能。它会在 Spring 容器中创建一个 TaskScheduler,使得 @Scheduled 注解的任务能够按照指定的时间调度运行。

  • @Scheduled(cron = “0/5 * * * * ?”):这是定时任务的核心注解。cron 表达式定义了任务执行的时间规则:

    • 0/5 * * * * ? 表示每隔 5 秒执行一次任务。
      • 0/5:从第 0 秒开始,每 5 秒执行一次;
      • *:每分钟都执行;
      • *:每小时都执行;
      • *:每天都执行;
      • *:每月都执行;
      • ?:不指定星期几(因为月份和星期几的参数冲突时使用 ?)。

这段代码每隔 5 秒打印一次 "SpringBootTestJob test"

适用场景:
  • Spring Boot 内置的定时任务功能适合一些简单的定时任务,尤其是单个节点环境下。
  • 它适合不需要复杂调度、持久化、分布式支持等的场景。

2. 为什么有时候不用 Spring Boot 自带的定时任务?

虽然 Spring Boot 自带的定时任务使用方便,但有一些局限性,尤其是在更复杂的生产环境中,可能无法满足一些需求:

1. 只适合单体,不适合集群:
  • Spring Boot 自带的定时任务在单节点上很好用,但是在集群环境中,所有节点都可能执行相同的定时任务,从而导致任务重复执行,造成问题。
  • 解决方法:可以通过加分布式锁等方式来避免重复执行,但这种方式实现起来相对复杂,且不够灵活。
2. 无法实时更改定时任务状态和策略:
  • 如果你需要动态地停止、修改、重新调度任务,Spring Boot 自带的定时任务就显得不够灵活了。
  • 比如,如果在生产环境中需要临时停止某个定时任务,Spring Boot 的 @Scheduled 注解方式并不容易实现动态控制。
3. 无法持久化任务信息:
  • Spring Boot 自带的定时任务没有持久化机制,如果应用重启,所有定时任务会丢失。虽然可以利用数据库或其他持久化方式实现,但它本身不提供这些功能。

3. Quartz 框架的介绍

Quartz 是一个功能强大的作业调度框架,可以解决上述 Spring Boot 自带定时任务的不足,尤其在集群、持久化、灵活性等方面表现优异。

引入 Quartz 依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

Spring Boot 提供了对 Quartz 的支持,只需要引入这个依赖,框架会自动配置一些 Quartz 的基础设置。

配置 Quartz 定时任务:
@Configuration
public class QuartzConfig {

    /**
     * 声明一个任务
     * @return
     */
    @Bean
    public JobDetail jobDetail() {
        return JobBuilder.newJob(TestJob.class)
                .withIdentity("TestJob", "test")
                .storeDurably()  // 表示即使没有触发器,任务也会被持久化
                .build();
    }

    /**
     * 声明一个触发器,什么时候触发这个任务
     * @return
     */
    @Bean
    public Trigger trigger() {
        return TriggerBuilder.newTrigger()
                .forJob(jobDetail())
                .withIdentity("trigger", "trigger")
                .startNow()  // 立即开始
                .withSchedule(CronScheduleBuilder.cronSchedule("*/2 * * * * ?"))  // 每两秒执行一次
                .build();
    }
}
  • JobDetail:表示要执行的作业,即 Quartz 任务类。使用 JobBuilder 创建一个作业实例并指定标识符。
  • Trigger:表示触发器,控制何时执行这个作业。CronScheduleBuilder 用来根据 Cron 表达式设定定时规则。
Quartz Job 类:
#禁用并发执行
@DisallowConcurrentExecution
public class TestJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("TestJob TEST开始");
        // 模拟任务执行
        System.out.println("TestJob TEST结束");
    }
}
  • @DisallowConcurrentExecution:表示任务不允许并发执行,即在任务没有完成之前,不会再次执行这个任务。
  • JobExecutionContext:上下文信息,包含了任务的所有执行信息。
让quartz将定时调度的任务保存到数据库中

1. 创建数据库表

首先,在数据库中创建相关的 Quartz 表来存储调度任务的状态、触发器和其他相关信息。以下是创建表的 SQL 语句:

-- Quartz 默认的数据库表
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;

-- 创建 QRTZ_JOB_DETAILS 表
CREATE TABLE QRTZ_JOB_DETAILS (
    SCHED_NAME VARCHAR(120) NOT NULL COMMENT '定时任务名称',
    JOB_NAME VARCHAR(200) NOT NULL COMMENT 'job名称',
    JOB_GROUP VARCHAR(200) NOT NULL COMMENT 'job组',
    DESCRIPTION VARCHAR(250) NULL COMMENT '描述',
    JOB_CLASS_NAME VARCHAR(250) NOT NULL COMMENT 'job类名',
    IS_DURABLE VARCHAR(1) NOT NULL COMMENT '是否持久化',
    IS_NONCONCURRENT VARCHAR(1) NOT NULL COMMENT '是否非同步',
    IS_UPDATE_DATA VARCHAR(1) NOT NULL COMMENT '是否更新数据',
    REQUESTS_RECOVERY VARCHAR(1) NOT NULL COMMENT '请求是否覆盖',
    JOB_DATA BLOB NULL COMMENT 'job数据',
    PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
);

-- 创建 QRTZ_TRIGGERS 表
CREATE TABLE QRTZ_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL COMMENT '定时任务名称',
    TRIGGER_NAME VARCHAR(200) NOT NULL COMMENT '触发器名称',
    TRIGGER_GROUP VARCHAR(200) NOT NULL COMMENT '触发器组',
    JOB_NAME VARCHAR(200) NOT NULL COMMENT 'job名称',
    JOB_GROUP VARCHAR(200) NOT NULL COMMENT 'job组',
    DESCRIPTION VARCHAR(250) NULL COMMENT '描述',
    NEXT_FIRE_TIME BIGINT(13) NULL COMMENT '下一次触发时间',
    PREV_FIRE_TIME BIGINT(13) NULL COMMENT '前一次触发时间',
    PRIORITY INTEGER NULL COMMENT '等级',
    TRIGGER_STATE VARCHAR(16) NOT NULL COMMENT '触发状态',
    TRIGGER_TYPE VARCHAR(8) NOT NULL COMMENT '触发类型',
    START_TIME BIGINT(13) NOT NULL COMMENT '开始时间',
    END_TIME BIGINT(13) NULL COMMENT '结束时间',
    CALENDAR_NAME VARCHAR(200) NULL COMMENT '日程名称',
    MISFIRE_INSTR SMALLINT(2) NULL COMMENT '未触发实例',
    JOB_DATA BLOB NULL COMMENT 'job数据',
    PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME, JOB_NAME, JOB_GROUP)
);

-- 其他表的创建省略...

这些表会用来存储定时任务的详细信息、触发器信息等。

2. 配置 Scheduler 使用数据库存储

为了让 Quartz 使用数据库来保存任务调度信息,我们需要在 application.propertiesapplication.yml 文件中进行相应配置。如下所示:

# Quartz 配置,使用 JDBCJobStore 存储任务
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource = quartzDataSource
org.quartz.jobStore.tablePrefix = QRTZ_

# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/quartz_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=20

这些配置使得 Quartz 使用 MySQL 数据库并将调度信息保存到数据库中。

3. 配置 JobFactoryScheduler

在 Spring Boot 项目中,Quartz 使用 SchedulerFactoryBean 来创建和配置调度器。你可以通过自定义 JobFactory 来注入 Spring 管理的 Bean。以下是增强后的 MyJobFactorySchedulerConfig 类:

@Component
public class MyJobFactory extends SpringBeanJobFactory {

    @Resource
    private AutowireCapableBeanFactory beanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object jobInstance = super.createJobInstance(bundle);
        beanFactory.autowireBean(jobInstance);  // 注入 Spring 管理的依赖
        return jobInstance;
    }
}

@Configuration
public class SchedulerConfig {

    @Resource
    private MyJobFactory myJobFactory;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("dataSource") DataSource dataSource) throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);
        factory.setJobFactory(myJobFactory);  // 配置 JobFactory,注入 Spring Bean
        factory.setStartupDelay(2);  // 延迟启动调度器
        return factory;
    }
}

通过这种方式,我们可以确保 Quartz 任务的执行能够访问到 Spring 容器中的所有 Bean。

4. 创建任务调度接口

创建一个 JobController 来操作定时任务的增加、暂停、恢复、删除等操作。以下是增强后的 JobController 类:

@RestController
@RequestMapping(value = "/admin/job")
public class JobController {

    private static Logger LOG = LoggerFactory.getLogger(JobController.class);

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    @RequestMapping(value = "/add")
    public CommonResp add(@RequestBody CronJobReq cronJobReq) {
        String jobClassName = cronJobReq.getName();
        String jobGroupName = cronJobReq.getGroup();
        String cronExpression = cronJobReq.getCronExpression();
        String description = cronJobReq.getDescription();
        LOG.info("创建定时任务开始:{},{},{},{}", jobClassName, jobGroupName, cronExpression, description);
        CommonResp commonResp = new CommonResp();

        try {
            Scheduler sched = schedulerFactoryBean.getScheduler();
            sched.start();  // 启动调度器

            JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(jobClassName))
                .withIdentity(jobClassName, jobGroupName)
                .build();

            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

            CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(jobClassName, jobGroupName)
                .withDescription(description)
                .withSchedule(scheduleBuilder)
                .build();

            sched.scheduleJob(jobDetail, trigger);

        } catch (SchedulerException | ClassNotFoundException e) {
            LOG.error("创建定时任务失败:" + e);
            commonResp.setSuccess(false);
            commonResp.setMessage("创建定时任务失败: " + e.getMessage());
        }

        LOG.info("创建定时任务结束:{}", commonResp);
        return commonResp;
    }

    // 其他接口:run、pause、resume、delete等
}

5. 请求参数和返回结果

请求参数 CronJobReq 类:

public class CronJobReq {
    private String group;
    private String name;
    private String description;
    private String cronExpression;

    // Getter 和 Setter 方法
}

返回结果 CronJobResp 类:

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class CronJobResp {
    private String group;
    private String name;
    private String description;
    private String state;
    private String cronExpression;

    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date nextFireTime;

    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date preFireTime;

    // Getter 和 Setter 方法
}

6. 测试接口

你可以使用以下 HTTP 请求来测试定时任务的创建:

POST http://localhost:8000/admin/job/add
Content-Type: application/json

{
  "name": "com.stu.train.batch.job.TestJob",
  "group": "default",
  "cronExpression": "*/2 * * * * ?",
  "description": "Test job"
}

通过上述步骤,Quartz 将会将调度任务的信息保存到数据库中,确保任务的持久化管理和恢复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值