SpringBoot整合Quartz定时任务

SpringBoot整合Quartz定时任务

1.加载properties方式

1.pom依赖

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

   <!--quartz-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
        </dependency>
        <!--quartz-jobs-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>

2.quartz.properties配置文件

#Main Scheduler Settings
#配置集群时,quartz调度器的id,由于配置集群时,只有一个调度器,必须保证每个服务器该值都相同,可以不用修改,只要每个ams都一样就行
org.quartz.scheduler.instanceName=quartzScheduler_ym
org.quartz.scheduler.rmi.export=false
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false
#集群中每台服务器自己的id,AUTO表示自动生成,无需修改
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer=true
org.quartz.scheduler.skipUpdateCheck=true
#一次性取出的任务数,默认值是1,适合负载均衡,但不适合大量的短时任务
org.quartz.scheduler.batchTriggerAcquisitionMaxCount=25

#Configure ThreadPool
#quartz线程池的实现类,无需修改
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool 
#quartz线程池中线程数,可根据任务数量和负载度来调整
org.quartz.threadPool.threadCount=25
#quartz线程优先级
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true

#Configure JobStore
org.quartz.jobStore.acquireTriggersWithinLock=true
#表示如果某个任务到达执行时间,而此时线程池中没有可用线程时,任务等待的最大时间,如果等待时间超过下面配置的值(毫秒),本次就不在执行,而等待下一次执行时间的到来,可根据任务量和负责程度来调整
org.quartz.jobStore.misfireThreshold=60000
#实现集群时,任务的存储实现方式,org.quartz.impl.jdbcjobstore.JobStoreTX表示数据库存储,无需修改
org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate

#quartz存储任务相关数据的表的前缀,无需修改
org.quartz.jobStore.tablePrefix=QRTZ_
#是否启用集群,启用,改为true,注意:启用集群后,必须配置下面的数据源,否则quartz调度器会初始化失败
org.quartz.jobStore.isClustered=true
#集群中服务器相互检测间隔,每台服务器都会按照下面配置的时间间隔往服务器中更新自己的状态,如果某台服务器超过以下时间没有checkin,调度器就会认为该台服务器已经down掉,不会再分配任务给该台服务器
org.quartz.jobStore.clusterCheckinInterval=10000

#Configure DataSources
#连接数据库数据源名称,与下面配置中org.quartz.dataSource.myDS的myDS一致即可,可以无需修改
org.quartz.jobStore.dataSource=myDS
org.quartz.dataSource.myDS.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.myDS.URL=jdbc:mysql://127.0.0.1:3306/ruoyi_local?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
org.quartz.dataSource.myDS.user=root
org.quartz.dataSource.myDS.password=root
#配置连接数据库连接池大小,一般为上面配置的线程池的2倍
org.quartz.dataSource.myDS.maxConnections=50
#org.quartz.dataSource.myDS.validationQuery=select 1 from dual

1.普通方法定时任务配置执行

/**
 * 定时任务执行方法
 * @author moshangshang
 */
public class QuartzTest {

    public void test(){
        System.out.println("该方法被调用" );
    }
}

配置触发

/**
 * Quartz配置(方法测试任务配置)
 */
@Configuration
public class QuartzConfig  {

   /**
     * 要调用的工作类
     */
    @Bean
    public QuartzTest quartzDay(){
        return new QuartzTest();
    }


    // targetMethod: 指定需要定时执行QuartzTest中的test()方法
    // concurrent:对于相同的JobDetail,当指定多个Trigger时, 很可能第一个job完成之前,
    // 第二个job就开始了。指定concurrent设为false,多个job不会并发运行,第二个job将不会在第一个job完成之前开始。
    // triggers:通过再添加其他的ref元素可在list中放置多个触发器。 
    @Bean(name = "daymailtask")
    public MethodInvokingJobDetailFactoryBean detailFactoryBean(@Qualifier("quartzDay") Object quartzDay) {
        MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
        bean.setTargetObject(quartzDay);
        //调用方法名
        bean.setTargetMethod("test");
        bean.setConcurrent(false);
        return bean;
    }


    @Bean(name = "doDayMailTime")
    public CronTriggerFactoryBean cronTriggerBean(@Qualifier("daymailtask") MethodInvokingJobDetailFactoryBean daymailtask) {
        CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
        trigger.setJobDetail(daymailtask.getObject());
        // 十秒一次
        trigger.setCronExpression("0/10 * * * * ?");
        return trigger;
    }


    @Bean
    @Lazy(false)
    public SchedulerFactoryBean schedulerFactory(Trigger[] cronTriggerBean) {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        System.err.println(cronTriggerBean[0]);
        bean.setTriggers(cronTriggerBean);
        return bean;
    }

    @Bean
    public Scheduler scheduler(SchedulerFactoryBean schedulerFactoryBean) {
        return schedulerFactoryBean.getScheduler();
    }
    
}

2.对于quartz定时任务job中不能注入bean对象处理

由于Quartz的job类中无法直接注入spring容器的bean对象,会报空指针异常,所以自定义的quartz的工厂来将spring的bean进行注入,然后在调度器工厂的bean中进行设置,即可以在job类中对bean进行注入,但在操作时需要注入自己配置的工厂bean对象。

@Component
public class CustomJobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        //调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        //进行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}
@Configuration
public class QuartzConfig {

    @Autowired
    private CustomJobFactory customJobFactory;

    @SneakyThrows
    @Bean("schedulerFactory")
    public StdSchedulerFactory scheduler(){
        StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 自定义 JobFactory 使得在 Quartz Job 中可以使用 @Autowired
        scheduler.setJobFactory(customJobFactory);
        scheduler.start();
        return schedulerFactory;
    }

}
 @Autowired
 private StdSchedulerFactory schedulerFactory ;

3.加载属性配置文件

@Slf4j
@Component
public class QuartzManager implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
	private StdSchedulerFactory schedulerFactory ;

    private static final String CONFIG_FILE = "quartz.properties";
 
    //操作方法,见操作工具类

    private void init() {
        try {
            Properties properties = new Properties();
            properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(CONFIG_FILE));
            schedulerFactory.initialize(properties);
            System.out.println(schedulerFactory.getScheduler());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (null == event.getApplicationContext().getParent()) {
            init();
        }
    }

}

使用参考springboot整合,使用schedulerFactory操作

2.纯yml配置方式

pom依赖

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

推荐使用springboot的自动注入依赖及yml配置,该版本测试基于springboot2.7

yml配置文件

spring:
   #定时任务
  quartz:
    # 将任务等保存化到数据库
    job-store-type: jdbc
    # 程序结束时会等待quartz相关的内容结束
    wait-for-jobs-to-complete-on-shutdown: true
    # QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
    overwrite-existing-jobs: true
    # 延迟启动
    startup-delay: 1
    properties:
      org:
        quartz:
          # scheduler相关
          scheduler:
            # scheduler的实例名
            instanceName: scheduler
            rmi:
              export: false
              proxy: false
            #集群中每台服务器自己的id,AUTO表示自动生成,无需修改,由于配置集群时,只有一个调度器,必须保证每个服务器该值都相同,
            instanceId: AUTO
            wrapJobExecutionInUserTransaction: false
            threadsInheritContextClassLoaderOfInitializer: true
            skipUpdateCheck: true
            #一次性取出的任务数,默认值是1,适合负载均衡,但不适合大量的短时任务
            batchTriggerAcquisitionMaxCount: 25
          # 持久化相关
          jobStore:
            acquireTriggersWithinLock: true
            # 表示如果某个任务到达执行时间,而此时线程池中没有可用线程时,任务等待的最大时间,
            # 如果等待时间超过下面配置的值(毫秒),本次就不在执行,而等待下一次执行时间的到来,可根据任务量和负责程度来调整
            misfireThreshold: 60000
            # 实现集群时,任务的存储实现方式,org.quartz.impl.jdbcjobstore.JobStoreTX表示数据库存储
            # 升级spring boot > 2.5.6的版本后将不再支持JobStoreTX方式进行配置默认数据源
            class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
            #oracle数据库为 org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            # quartz存储任务相关数据的表的前缀
            tablePrefix: qrtz_
            # 是否启用集群,启用,改为true,注意:启用集群后,必须配置下面的数据源,否则quartz调度器会初始化失败
            isClustered: true
            # 集群中服务器相互检测间隔,每台服务器都会按照下面配置的时间间隔往服务器中更新自己的状态,
            # 如果某台服务器超过以下时间没有checkin,调度器就会认为该台服务器已经down掉,不会再分配任务给该台服务器
            clusterCheckinInterval: 10000
            # 是否将JobDataMap中的属性转为字符串存储
            useProperties: false
          # 线程池相关
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            # 线程池中线程数,可根据任务数量和负载度来调整
            threadCount: 25
            # 线程优先级
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
          # 数据源配置
          # 连接数据库数据源名称,与下面配置中dataSource.myDS的myDS一致
          dataSource: myDS
          dataSource.myDS.driver: com.mysql.cj.jdbc.Driver
          dataSource.myDS.URL: jdbc:mysql://127.0.0.1:3306/ruoyi_local?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
          dataSource.myDS.user: root
          dataSource.myDS.password: root
          # 配置连接数据库连接池大小,一般为上面配置的线程池的2倍
          dataSource.myDS.maxConnections: 50
          # dataSource.myDS.validationQuery: select 1 from dual

编写任务核心处理方法

注意事项:

  • Quartz的定时任务的job类中实现的StatefulJob接口已过时,在开发时尽量使用 实现Job接口或者继承QuartzJobBean类的方式进行执行类的实现。
/**
 * 定时任务执行方法
 * @author moshangshang
 */
public class QuartzTestJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        String userName = (String) context.getJobDetail().getJobDataMap().get("username");
        System.out.println("userName:" + userName);
    }

}

1.编写任务执行配置

/**
 * 定时任务执行配置
 * @author moshangshang
 */
@Configuration
public class TaskConfig {

    @Bean("helloJob")
    public JobDetail helloJobDetail() {
        return JobBuilder.newJob(QuartzTestJob.class)
                .withIdentity("DateTimeJob")
                .usingJobData("username", "Hello Quartz")
                .storeDurably()//即使没有Trigger关联时,也不需要删除该JobDetail
                .build();
    }

    @Bean
    public Trigger printTimeJobTrigger() {
        // 每秒执行一次
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/1 * * * * ?");
        return TriggerBuilder.newTrigger()
                .forJob(helloJobDetail())
                .withIdentity("quartzTaskService")
                .withSchedule(cronScheduleBuilder)
                .build();
    }
    
}

2.注入SchedulerFactoryBean动态使用

springboot使用yml配置时可直接给工具类注入SchedulerFactoryBean对象使用

在使用yml自动配置时,配置自定义的StdSchedulerFactory,获取的是默认的属性配置,yml不会生效,需使用SchedulerFactoryBean

@Autowired
private SchedulerFactoryBean schedulerFactory;

3.操作工具类

@Slf4j
@Component
public class QuartzManager {


    @Autowired
    private Scheduler scheduler;

    /**
     * 添加一个定时任务
     *
     * @param jobName 任务名
     * @param jobGroupName 任务组名
     * @param triggerName 触发器名
     * @param triggerGroupName 触发器组名
     * @param jobClass 任务
     * @param cron 时间设置
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass,
                       String cron, String id) {
        try {
            // 任务名,任务组,任务执行类
            JobDetail jobDetail = JobBuilder.newJob(jobClass)
                    .withIdentity(jobName, jobGroupName)
                    .build();
            // 参数
            JobDataMap jobDataMap = jobDetail.getJobDataMap();
            jobDataMap.put("jkConfigId", id);
            // 创建Trigger对象
            Trigger trigger =  TriggerBuilder.newTrigger()
                    // 触发器名,触发器组
                    .withIdentity(triggerName, triggerGroupName).startNow()
                    // 触发器时间设定
                    .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                    .build();
            // 调度容器设置JobDetail和Trigger
            scheduler.scheduleJob(jobDetail, trigger);
            // 启动
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 添加一个定时任务
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass,
                       String cron, Map<String, Object> dataMap) {
        try {
            // 任务名,任务组,任务执行类
            JobDetail jobDetail = JobBuilder.newJob(jobClass)
                    .withIdentity(jobName, jobGroupName)
                    .build();
            // 参数
            JobDataMap jobDataMap = jobDetail.getJobDataMap();
            //传递的所有参数
            jobDataMap.putAll(dataMap);
            // 创建Trigger对象
            Trigger trigger =  TriggerBuilder.newTrigger()
                    // 触发器名,触发器组
                    .withIdentity(triggerName, triggerGroupName).startNow()
                    // 触发器时间设定
                    .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                    .build();
            // 调度容器设置JobDetail和Trigger
            scheduler.scheduleJob(jobDetail, trigger);
            // 启动
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 修改一个任务的触发时间
     *
     * @param triggerName 触发器名
     * @param triggerGroupName 触发器组名
     * @param cron 时间设置
     */
    public void modifyJobTime(String triggerName, String triggerGroupName, String cron) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                /** 方式一:修改一个任务的触发时间 */
                // 创建Trigger对象
                trigger = TriggerBuilder.newTrigger().startNow()
                        // 触发器时间设定
                        .withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
                scheduler.rescheduleJob(triggerKey, trigger);
                /** 方式二:先删除,然后再创建一个新的Job */
                // JobDetail jobDetail =
                // scheduler.getJobDetail(JobKey.jobKey(jobName,jobGroupName));
                // Class<? extends Job> jobClass = jobDetail.getJobClass();
                // removeJob(jobName,jobGroupName,triggerName,triggerGroupName);
                // addJob(jobName,jobGroupName,triggerName,triggerGroupName,jobClass,cron);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 触发状态为运行的任务
     * 立即执行一次任务
     */
    public void triggerRunningJob(String jobName, String jobGroupName) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
            scheduler.triggerJob(jobKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 移除一个任务
     */
    public void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            // 停止触发器
            scheduler.pauseTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除任务
            scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 启动所有定时任务
     */
    public void startJobs() {
        try {
            scheduler.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 关闭所有定时任务
     */
    public void shutdownJobs() {
        try {
            if (!scheduler.isShutdown()) {
                scheduler.shutdown();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 暂停所有定时任务
     */
    public void standby() {
        try {
            scheduler.standby();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

注意:shutdown方法后不能再启动,会报错,可以使用暂停standby方法

The Scheduler cannot be restarted after shutdown() has been called.

测试

@RestController
@RequestMapping("/quartz")
public class QuartzController {

    @Autowired
    private QuartzManager quartzManager;



    @GetMapping("/start")
    public void test4() {
        quartzManager.startJobs();
    }


    @GetMapping("/standby")
    public void test5() {
        quartzManager.standby();
    }



    @GetMapping("/add")
    public void test6() {
        quartzManager.addJob("DateTimeJob","aaa",
                "quartzTaskService","bbb", QuartzTestJob.class,"0/1 * * * * ?","11");
    }


    @GetMapping("/remove")
    public void test7() {
        quartzManager.removeJob("DateTimeJob",null,"quartzTaskService",null);
    }


}
### 集成Spring BootQuartz调度器框架 为了在Spring Boot项目中集成Quartz以实现定时任务,可以利用Spring提供的便捷类来支持Quartz,并减少应用程序与其他组件之间的耦合度[^1]。 #### 创建Spring Boot应用并引入依赖项 首先,在`pom.xml`文件中加入必要的Maven依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> ``` 这一步骤确保了项目的构建配置包含了运行所需的所有库和工具。 #### 定义Job类 接着定义一个实现了`Job`接口的任务执行逻辑。例如创建名为`MyJob.java`的Java源码如下所示: ```java import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class MyJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("Executing job at " + new java.util.Date()); } } ``` 这段代码展示了如何编写简单的作业处理程序,它会在每次触发时打印当前时间戳至控制台。 #### 设置计划表达式 通过配置文件或编程方式设置具体的调度策略。这里采用XML形式指定调度参数作为例子说明(实际开发推荐使用注解或者YAML/Properties格式)。编辑位于资源目录下的`scheduler-context.xml`: ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- Define the bean of our custom job --> <bean id="myJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="app"/> <property name="targetMethod" value="printMessage"/> </bean> <!-- Configure a simple trigger that fires every five seconds --> <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> <property name="jobDetail" ref="myJobDetail"/> <property name="startDelay" value="0"/> <property name="repeatInterval" value="5000"/> </bean> <!-- Register the scheduler factory bean --> <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="simpleTrigger"/> </list> </property> </bean> </beans> ``` 上述配置指定了每五秒重复一次的任务触发机制[^2]。 #### 启动应用程序 最后修改入口函数中的启动过程,加载自定义上下文环境变量: ```java package com.example.demo; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { // 加载包含quartz配置的应用上下文 new ClassPathXmlApplicationContext("scheduler-context.xml"); SpringApplication.run(DemoApplication.class, args); } } ``` 当调用此方法后,将会初始化Quartz调度服务实例并且按照预定的时间间隔持续循环执行已注册的工作单元直到进程终止为止[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值