任务调度框架(6)番外:使用 SpringBoot2.X 实现 Quartz 动态任务的分布式调度


见名知意,该篇番外主要是要解决如下几个问题:

  1. 使用SpringBoot2.x 版本集成 Quartz
  2. Quartz 的任务动态实现:
  • 调度任务可以通过页面进行新增、删除、启动、暂定等操作
  • 任务数据使用数据库保存
  • 任务之间实现简单的依赖
  1. Quartz 实现分布式调度,使用其本身提供的基于数据库的实现

SpringBoot2 集成 Quartz

  1. SpringBoot不同的版本对于Quartz 的集成有一定的差别,本文使用 2.1.2.RELEASE 版本。其实通过分析SpringBoot对于Quartz的自动化配置源码,也有助于我们理解Quartz的使用
  2. SpringBoot-2.1.2.RELEASE 版本已经集成了对于Quartz的自动化配置,其源码路径为org.springframework.boot.autoconfigure.quartz

集成简单实现

Pom依赖
    # Web工程
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    # quartz
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    
    # 数据库JDBC
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    
    # 使用MySql
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
编码功能实现
  1. 由于Springboot2的自动化配置,不需要做任何配置,直接写JobDetail、Trigger、Job 即可实现
# Job 实现
@DisallowConcurrentExecution
public class DemoJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        System.out.println("~~ DemoJob 启动运行汇总~~");
    }
}

# JobDetail、Trigger Bean配置
@Configuration
public class QuartzJobConfig {

    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean() {
        JobDetailFactoryBean jobDetail = new JobDetailFactoryBean();
        jobDetail.setName("DemoJob");
        jobDetail.setGroup("DemoJob_Group");
        jobDetail.setJobClass(DemoJob.class);
        jobDetail.setDurability(true);
        return jobDetail;
    }

    @Bean
    public CronTriggerFactoryBean cronTriggerFactoryBean() {
        CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
        trigger.setJobDetail(jobDetailFactoryBean().getObject());
        trigger.setCronExpression("*/10 * * * * ?");
        trigger.setName("DemoJob");
        trigger.setMisfireInstruction(0);

        return trigger;
    }
}
  1. 这样就实现了 SpringBoot2.x 版本集成 Quartz 功能,在进行下一步之前,我们先对自动化配置的源码简单分析一下。

自动配置实现分析

SpringBoot关于Quartz的自动配置的类一共有6个,分别为:

  1. JobStoreType:是一个枚举类,定义了jobsStore的类型,基于内存和数据库
  2. QuartzAutoConfiguration:自动配置类,配置了Quartz的主要组件,如SchedulerFactoryBean
  3. QuartzDataSource:是一个注解类。如果需要注入Quartz配置的数据库操作类,需要使用此注解标注。参考QuartzAutoConfiguration中的用法
  4. QuartzDataSourceInitializer:该类主要用于数据源初始化后的一些操作,根据不同平台类型的数据库进行选择不同的数据库脚本
  5. QuartzProperties:该类对应了在application.yml配置文件以spring.quartz开头的相关配置
  6. SchedulerFactoryBeanCustomizer:在自动配置的基础上自定义配置需要实现的此接口。
自动化配置分析 QuartzAutoConfiguration
  1. 初始化注入任务以及配置:构造函数实现
  • 注入了属性配置文件类:QuartzProperties
  • 注入了自定义扩展配置:SchedulerFactoryBeanCustomizer
  • 注入了Quartz的任务组件:JobDetailTriggerCalendar。所以我们只需要进行 JobDetail、Trigger Bean配置,会自动注入进QuartzAutoConfiguration类中,这边是通过ObjectProvider的使用实现的。
	public QuartzAutoConfiguration(QuartzProperties properties,
			ObjectProvider<SchedulerFactoryBeanCustomizer> customizers,
			ObjectProvider<JobDetail[]> jobDetails,
			ObjectProvider<Map<String, Calendar>> calendars,
			ObjectProvider<Trigger[]> triggers, ApplicationContext applicationContext) {
		this.properties = properties;
		this.customizers = customizers;
		this.jobDetails = jobDetails.getIfAvailable();
		this.calendars = calendars.getIfAvailable();
		this.triggers = triggers.getIfAvailable();
		this.applicationContext = applicationContext;
	}
  1. 配置 SchedulerFactoryBean 的详细信息。这个类是一个 FactoryBean
  • 配置 JobFactory,内部设置了applicationContext与spring容器结合
  • 配置各种属性,是通过QuartzProperties类实现
  • 配置注入进来的 JobDetailTriggerCalendar
  • 配置自定配置,是通过SchedulerFactoryBeanCustomizer实现。这边包括自定义,也包括基于数据库实现的JobStore配置。
	@Bean
	@ConditionalOnMissingBean
	public SchedulerFactoryBean quartzScheduler() {
	
	    # 配置 `JobFactory`
		SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
		SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
		jobFactory.setApplicationContext(this.applicationContext);
		schedulerFactoryBean.setJobFactory(jobFactory);
		
		# 开始配置各种属性
		if (this.properties.getSchedulerName() != null) {
			schedulerFactoryBean.setSchedulerName(this.properties.getSchedulerName());
		}
		schedulerFactoryBean.setAutoStartup(this.properties.isAutoStartup());
		schedulerFactoryBean
				.setStartupDelay((int) this.properties.getStartupDelay().getSeconds());
		schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(
				this.properties.isWaitForJobsToCompleteOnShutdown());
		schedulerFactoryBean
				.setOverwriteExistingJobs(this.properties.isOverwriteExistingJobs());
		if (!this.properties.getProperties().isEmpty()) {
			schedulerFactoryBean
					.setQuartzProperties(asProperties(this.properties.getProperties()));
		}
		
		# 配置 jobDetails、triggers等
		if (this.jobDetails != null && this.jobDetails.length > 0) {
			schedulerFactoryBean.setJobDetails(this.jobDetails);
		}
		if (this.calendars != null && !this.calendars.isEmpty()) {
			schedulerFactoryBean.setCalendars(this.calendars);
		}
		if (this.triggers != null && this.triggers.length > 0) {
			schedulerFactoryBean.setTriggers(this.triggers);
		}
		
		# 自定义配置
		customize(schedulerFactoryBean);
		return schedulerFactoryBean;
	}
  1. 基于数据库实现的 JobStore 配置,内部类JdbcStoreTypeConfiguration
  • @ConditionalOnSingleCandidate(DataSource.class) 指定pring容器中有且只有一个指明的DataSourceBean时生效
  • 通过dataSourceCustomizer方法实现schedulerFactoryBean的数据库相关配置,该方法返回一个 SchedulerFactoryBeanCustomizer
  • 配置QuartzDataSourceInitializer 数据库初始化 Bean
  1. 通过后置工厂处理器 DataSourceInitializerSchedulerDependencyPostProcessor 实现对于QuartzDataSourceInitializer这个Bean的依赖关系(dependsOn)
		@Bean
		@Order(0)
		public SchedulerFactoryBeanCustomizer dataSourceCustomizer(
				QuartzProperties properties, DataSource dataSource,
				@QuartzDataSource ObjectProvider<DataSource> quartzDataSource,
				ObjectProvider<PlatformTransactionManager> transactionManager) {
				
			return (schedulerFactoryBean) -> {
			    # 判断是否为 JobStore
				if (properties.getJobStoreType() == JobStoreType.JDBC) {
				    # 获取 DataSource
					DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource);
					
					# 配置 DataSource 和 TransactionManager管理
					schedulerFactoryBean.setDataSource(dataSourceToUse);
					PlatformTransactionManager txManager = transactionManager.getIfUnique();
					if (txManager != null) {
						schedulerFactoryBean.setTransactionManager(txManager);
					}
				}
			};
		}
支持功能配置 QuartzProperties
  1. @ConfigurationProperties("spring.quartz")spring.quartz开头的配置
  2. SpringBoot 已经做了相应的默认值处理,即使不做任何配置,也是没有问题的。
  3. 比较简单,直接贴码。属性的具体含义,任务调度框架(3)Quartz 简单使用
spring:
  quartz:
    scheduler-name: springboot-quartz-jdbc-dynamic
    auto-startup: false
    startup-delay: 5s
    overwrite-existing-jobs: false
    wait-for-jobs-to-complete-on-shutdown: true
    job-store-type: memory
    #    jdbc:
    #      initialize-schema: embedded
    #      schema: classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql
    #      comment-prefix: --
    properties: {
      org.quartz.scheduler.instanceName: springboot-quartz-jdbc-dynamic,
      org.quartz.scheduler.instanceId: AUTO,
      org.quartz.threadPool.class: org.springframework.scheduling.quartz.SimpleThreadPoolTaskExecutor,
      org.quartz.threadPool.threadCount: 25,
      org.quartz.threadPool.threadPriority: 5,
      org.quartz.jobStore.misfireThreshold: 60000,
      #      org.quartz.jobStore.tablePrefix: QRTZ_,
      #      org.quartz.jobStore.isClustered: true,
      #      org.quartz.jobStore.clusterCheckinInterval: 20000,
      #      org.quartz.jobStore.maxMisfiresToHandleAtATime: 1,
      #      org.quartz.jobStore.txIsolationLevelSerializable: false
    }

小结

  1. 至此,我们完成了SpringBoot与Quartz的集成,并且简单运行了我们的调度任务
  2. 简单分析了 SpringBoot 对于Quartz的自动配置,由于各个版本的差别,这边使用的是 SpringBoot-2.1.2.RELEASE

Quartz 实现分布式调度

回顾分析

任务调度框架(4)Quartz 分布式实现 已经对Quartz自身的分布式实现做了简单的介绍,这边主要基于SpringBoot怎么做。

配置简单实现

  1. 上述完成的 SpringBoot与Quartz的集成,可以看到有几个先关的配置:
  • job-store-type 可以选择JDBC完成分布式JdbcJobStore切换
  • jdbc.XXX 主要是对于初始化SQL的配置。树妖是对于quartz提供的11张表的初始化sql
  • 对于JdbcJobStore 的一些特殊配置,如表前缀、集群指定、数据库检查等,基于RamJobStore时,这些是不允许配置的。
spring:
  quartz:
    job-store-type: memory
    jdbc:
      initialize-schema: embedded
      schema: classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql
      comment-prefix: --
    properties: {
      org.quartz.jobStore.misfireThreshold: 60000,
      org.quartz.jobStore.tablePrefix: QRTZ_,
      org.quartz.jobStore.isClustered: true,
      org.quartz.jobStore.clusterCheckinInterval: 20000,
      org.quartz.jobStore.maxMisfiresToHandleAtATime: 1,
      org.quartz.jobStore.txIsolationLevelSerializable: false
    }
  1. 以上就配置好了Quartz 实现分布式调度,就是这么简单
  2. 【注意】在尝试的时候,jdbc.xxx配置没有生效,个人是自己手动初始化的表。

Quartz 的任务动态实现

  1. 以上我们简单完成了SpringBoot集成与基于JDBC的分布式,但是我们的任务还是基于Bean配置的:
  • 新增任务需要手动硬编码,增加JobDetailTrigger的Bean配置
  • 上线后的任务无法修改,需要修改代码,停止应用
  1. 所以,所谓的动态任务主要是三个问题:
  • 任务数据使用数据库保存,包括任务的基本信息与trigger信息
  • 调度任务可以通过页面进行新增、修改、删除、启动、暂停、重启等操作
  • 任务之间实现简单的依赖,如A任务依赖于B任务,那么A任务必须等到B任务执行完成才会自动执行

数据使用数据库保存

  1. 简单,把任务调度分为两个模块:
  • 基本任务(BatchTask)与任务计划(BatchSchedule),BatchTask与BatchSchedule是一对多关系,代替Quartz中jobGroup 的概念。
  • 任务计划(BatchSchedule) 中可能需要用到配置的一些参数,定义任务计划参数(BatchScheduleParam)
  1. 具体的实体如下,数据库相关表结构略
  • 基本任务(BatchTask)
public class BatchTask extends AbstractDataEntity {
    /**
     * 任务编码:唯一
     */
    private String code;

    /**
     * 任务名称
     */
    private String name;

    /**
     * 任务描述
     */
    private String description;

    /**
     * 前置任务
     */
    private List<BatchTask> previous;
}
  • 任务计划(BatchSchedule)
public class BatchSchedule extends AbstractDataEntity {
    /**
     * 计划编码
     */
    private String code;

    /**
     * 计划名称
     */
    private String name;

    /**
     * 计划状态: 整个生命周期状态
     */
    private Integer status;

    /**
     * 执行表达式类型
     */
    private Integer cronType;

    /**
     * 执行表达式
     */
    private String cronExpression;

    /**
     * 描述
     */
    private String description;

    /**
     * 处理业务类
     */
    private String interfaceName;

    /**
     * 任务编码(任务组的概念)
     */
    private String taskCode;

    /**
     * 开始时间(最近)
     */
    private Date startDate;

    /**
     * 结束时间(最近)
     */
    private Date endDate;

    /**
     * 前置计划列表
     */
    private List<BatchSchedule> dependencies;

    /**
     * 参数列表
     */
    private List<BatchScheduleParam> params;
}
  • 计划参数(BatchScheduleParam)
public class BatchScheduleParam {

    /**
     * 任务计划ID
     */
    private String scheduleId;

    /**
     * 任务计划code
     */
    private String scheduleCode;

    /**
     * 参数名
     */
    private String paramName;

    /**
     * 参数值
     */
    private String paramValue;
}
  1. 有了这些实体,无非就是根据实体对基本任务与任务计划做CRUD,这个比较简单,不赘述。

image

image

任务计划的动态管理

手动配置实现的原理
  1. 手动配置是需要编码实现 JobDetailFactoryBeanCronTriggerFactoryBean,然后通过SpringBoot的自动化配置,设置到
    schedulerFactoryBean对象的对应属性中。
schedulerFactoryBean.setJobDetails(this.jobDetails);
schedulerFactoryBean.setTriggers(this.triggers);
  1. SchedulerFactoryBean源码中,通过afterPropertiesSet()方法中方法,注册到Scheduler对象中
  • 最终核心是通过Scheduler.scheduleJob(jobDetail, trigger);添加
	// Initialize the Scheduler instance...
	this.scheduler = prepareScheduler(prepareSchedulerFactory());
	try {
		registerListeners();
		registerJobsAndTriggers(); # 注册JobsAndTriggers
	}
	
	protected void registerJobsAndTriggers() throws SchedulerException {
	    
	    // Register JobDetails.
		if (this.jobDetails != null) {
    		for (JobDetail jobDetail : this.jobDetails) {
    			addJobToScheduler(jobDetail);
    		}
    	}
    	
		// Register Triggers.
		if (this.triggers != null) {
			for (Trigger trigger : this.triggers) {
				addTriggerToScheduler(trigger);
			}
		}
	}
	
	private boolean addJobToScheduler(JobDetail jobDetail) throws SchedulerException {
		if (this.overwriteExistingJobs || getScheduler().getJobDetail(jobDetail.getKey()) == null) {
			# 最终是通过Scheduler.addJob(jobDetail, true); 添加
			getScheduler().addJob(jobDetail, true);
			return true;
		}
		else {
			return false;
		}
	}
	
	private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException {
	    # 最终是通过Scheduler.scheduleJob(jobDetail, trigger); 添加(只是一部分功能)
	    getScheduler().scheduleJob(jobDetail, trigger);
	}
	

动态管理:创建计划任务引擎类
  1. Scheduler 在SpringBoot中已经通过SchedulerFactoryBean自动配置好了,直接注入即可使用。
  2. 具体可以 参考源码
public class QuartzScheduleEngine {


    @Autowired
    private Scheduler scheduler;


    /**
     * 新增计划任务: 主要是添加 jobDetail 和 trigger
     *
     * @param batchSchedule
     */
    public Date addJob(BatchSchedule batchSchedule) throws Exception {

        String cronExpression = batchSchedule.getCronExpression();
        String name = batchSchedule.getCode();
        String group = batchSchedule.getTaskCode();
        String interfaceName = batchSchedule.getInterfaceName();

        // 校验数据
        this.checkNotNull(batchSchedule);

        // 添加 1-JobDetail
        // 校验 JobDetail 是否存在
        JobKey jobKey = JobKey.jobKey(name, group);
        if (scheduler.checkExists(jobKey)) {
            if (Strings.isNullOrEmpty(cronExpression)) {
                // 已经存在并且执行一次,立即执行
                scheduler.triggerJob(jobKey);
            } else {
                throw new Exception("任务计划 JobKey 已经在执行队列中,不需要重复启动");
            }
        } else {

            // 构建 JobDetail
            Class<? extends Job> jobClazz = (Class<? extends Job>) Class.forName(interfaceName);
            JobDetail jobDetail = JobBuilder.newJob(jobClazz).withIdentity(jobKey).build();
            jobDetail.getJobDataMap().put(BatchSchedule.SCHEDULE_KEY, batchSchedule.toString());

            // 添加 2-Trigger
            // 校验 Trigger 是否存在
            TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
            Trigger trigger = scheduler.getTrigger(triggerKey);
            if (Objects.nonNull(trigger)) {
                throw new Exception("任务计划 Trigger 已经在执行队列中,不需要重复启动");
            }

            // 构建 Trigger
            trigger = getTrigger(cronExpression, triggerKey);

            return scheduler.scheduleJob(jobDetail, trigger);
        }

        return new Date();
    }


    /**
     * 修改
     *
     * @param batchSchedule
     */
    public void updateCronExpression(BatchSchedule batchSchedule) throws Exception {
        updateJobCronExpression(batchSchedule);
    }


    /**
     * 更新Job的执行表达式
     *
     * @param batchSchedule
     * @throws SchedulerException
     */
    public Date updateJobCronExpression(BatchSchedule batchSchedule) throws SchedulerException {
        checkNotNull(batchSchedule);

        String name = batchSchedule.getCode();
        String group = batchSchedule.getTaskCode();
        TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
        // 在队列中才需要修改
        if (scheduler.checkExists(triggerKey)) {
            // 构建 Trigger
            String cronExpression = batchSchedule.getCronExpression();
            Trigger trigger = this.getTrigger(cronExpression, triggerKey);
            return scheduler.rescheduleJob(triggerKey, trigger);
        }
        return null;
    }

    /**
     * 构建 Trigger
     *
     * @param cronExpression
     * @param triggerKey
     * @return
     */
    private Trigger getTrigger(String cronExpression, TriggerKey triggerKey) {
        Trigger trigger;
        if (Strings.isNullOrEmpty(cronExpression.trim())) {
            trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).build();
        } else {
            cronExpression = cronExpression.replaceAll("#", " ");
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
        }
        return trigger;
    }

    /**
     * 暂停计划任务
     *
     * @param batchSchedule
     */
    public void pauseJob(BatchSchedule batchSchedule) throws Exception {
        checkNotNull(batchSchedule);
        JobKey jobKey = JobKey.jobKey(batchSchedule.getCode(), batchSchedule.getTaskCode());
        if (!scheduler.checkExists(jobKey)) {
            throw new Exception("任务计划不在执行队列中,不能暂停");
        }
        scheduler.pauseJob(jobKey);
    }

    /**
     * 从暂停中恢复
     *
     * @param batchSchedule
     */
    public void resumeJob(BatchSchedule batchSchedule) throws Exception {
        checkNotNull(batchSchedule);
        JobKey jobKey = JobKey.jobKey(batchSchedule.getCode(), batchSchedule.getTaskCode());
        if (!scheduler.checkExists(jobKey)) {
            throw new Exception("任务计划不在执行队列中,不能恢复");
        }

        scheduler.resumeJob(jobKey);
    }

    /**
     * 删除计划任务
     *
     * @param batchSchedule
     */
    public boolean deleteJob(BatchSchedule batchSchedule) throws SchedulerException {
        boolean flag = true;
        checkNotNull(batchSchedule);
        JobKey jobKey = JobKey.jobKey(batchSchedule.getCode(), batchSchedule.getTaskCode());
        if (scheduler.checkExists(jobKey)) {
            flag = scheduler.deleteJob(jobKey);
        }

        return flag;
    }

    /**
     * 添加任务监听
     *
     * @param jobListener
     * @param matcher
     * @throws SchedulerException
     */
    public void addJobListener(JobListener jobListener, Matcher<JobKey> matcher) throws SchedulerException {
        scheduler.getListenerManager().addJobListener(jobListener, matcher);
    }

    /**
     * 执行一次(可用于测试)
     *
     * @param batchSchedule
     */
    public void runJobOnce(BatchSchedule batchSchedule) throws SchedulerException {
        checkNotNull(batchSchedule);
        JobKey jobKey = JobKey.jobKey(batchSchedule.getCode(), batchSchedule.getTaskCode());
        scheduler.triggerJob(jobKey);
    }

    private void checkNotNull(BatchSchedule batchSchedule) {
        Preconditions.checkNotNull(batchSchedule, "计划为空");
        Preconditions.checkState(!StringUtils.isEmpty(batchSchedule.getCode()), "计划编号为空");
        Preconditions.checkState(!StringUtils.isEmpty(batchSchedule.getTaskCode()), "计划所属任务为空");
        Preconditions.checkState(!StringUtils.isEmpty(batchSchedule.getInterfaceName()), "任务执行业务类为空");
    }


    public SchedulerMetaData getMetaData() throws SchedulerException {
        SchedulerMetaData metaData = scheduler.getMetaData();
        return metaData;
    }
}

任务状态与计划依赖
  1. 使用JobListener实现,需要自定义配置的支持
public class CustomGlobalJobListener extends JobListenerSupport {


    @Override
    public String getName() {
        return this.getClass().getName();
    }

    /**
     * Scheduler 在 JobDetail 将要被执行时调用这个方法。
     *
     * @param context
     */
    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        getLog().debug("计划 {} : ~~~ 【RUNNING】 更新正在运行中状态 ~~~ ");
    }

    /**
     * Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决了时调用这个方法
     *
     * @param context
     */
    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
    }

    /**
     * Scheduler 在 JobDetail 被执行之后调用这个方法
     *
     * @param context
     * @param jobException
     */
    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        getLog().debug("计划 {} : ~~~ 【COMPLETE | ERROR】 更新已经结束状态 ~~~ ");

        // 唤醒子任务
        batchScheduleService.notifyChildren(scheduleJob);
    }
}

  1. 自定义实现,可以实现SchedulerFactoryBeanCustomizer接口
@Configuration
public class SchedulerFactoryBeanCustomizerConfig implements SchedulerFactoryBeanCustomizer {

    @Bean
    public CustomGlobalJobListener globalJobListener() {
        return new CustomGlobalJobListener();
    }


    @Override
    public void customize(SchedulerFactoryBean schedulerFactoryBean) {
        schedulerFactoryBean.setGlobalJobListeners(globalJobListener());
    }
}
  1. 计划依赖:
  • 如计划有依赖其他计划,则该计划一般不允许手动运行,需要等待所依赖的计划完成后在监听器中自动唤醒
  • 目前只简单实现了单个计划依赖,没有实现复杂功能。后期可以扩展:多计划依赖,依赖排序等功能。

小结

至此,Quartz 的任务动态实现已经完成,主要可以分为三个部分:

  1. 任务与计划定义,使用数据库保存
  2. 动态计划的管理,使用Quartz本身的API实现
  3. 任务计划状态监控,使用JobListener监听器实现
  4. 计划依赖,使用JobListener监听器实现。

参考

  1. lotso-web:使用 SpringBoot2.X 实现 Quartz 动态任务的分布式调度
  2. 源码地址 Learning_Job_Schedule
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。 Quartz的优势: 1、Quartz是一个任务调度框架(库),它几乎可以集成到任何应用系统中。 2、Quartz是非常灵活的,它让您能够以最“自然”的方式来编写您的项目的代码,实现您所期望的行为 3、Quartz是非常轻量级的,只需要非常少的配置 —— 它实际上可以被跳框架使用,如果你的需求是一些相对基本的简单的需求的话。 4、Quartz具有容错机制,并且可以在重启服务的时候持久化(”记忆”)你的定时任务,你的任务也不会丢失。 5、可以通过Quartz,封装成自己的分布式任务调度实现强大的功能,成为自己的产品。6、有很多的互联网公司也都在使用Quartz。比如美团 Spring是一个很优秀的框架,它无缝的集成了Quartz,简单方便的让企业级应用更好的使用Quartz进行任务调度。   课程说明:在我们的日常开发中,各种大型系统的开发少不了任务调度,简单的单机任务调度已经满足不了我们的系统需求,复杂的任务会让程序猿头疼, 所以急需一套专门的框架帮助我们去管理定时任务,并且可以在多台机器去执行我们的任务,还要可以管理我们的分布式定时任务。本课程从Quartz框架讲起,由浅到深,从使用到结构分析,再到源码分析,深入解析Quartz、Spring+Quartz,并且会讲解相关原理, 让大家充分的理解这个框架框架的设计思想。由于互联网的复杂性,为了满足我们特定的需求,需要对Spring+Quartz进行二次开发,整个二次开发过程都会进行讲解。Spring被用在了越来越多的项目中, Quartz也被公认为是比较好用的定时器设置工具,学完这个课程后,不仅仅可以熟练掌握分布式定时任务,还可以深入理解大型框架的设计思想。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值