quartz由浅入深学习:简单的quartz,动态添加修改任务和触发器quartz;扫盲必备

Qrartz学习---基于springboot从入门到实现对于触发器定时任务的动态增删改查;

一、 quartz:

   在学习quartz之前,我们先来认识一下quartz,它到底是个什么东西,我们为什么要学它;

  • 看一下官方的解释:

  • 再来看一些比较通俗一点的解释:

说的比较通俗一点,quartz就是一个定时器,用来完成复杂一点的定时任务的,它的使用也是非常的简单,下边我们将由浅入深的介绍quartz这个框架;

二、quartz的一些使用属性和概念的介绍:

     要了解一个框架之前,我们首先要知道它里边最基本的一些类的属性是什么:

说的比较通俗一点:

  1. job:就是你的定时器需要完成的任务是什么;
  2. jobDetail:有的时候,我们的job定义的任务可能会比较的抽象,这个时候jobDetail就发挥作用了,它是你的一个job的实例。job和jobDetail是不分家的。
  3. trigger:触发器 ,他的作用是什么时候来触发这个定时任务,比如说每三天执行一次,每周末执行一次等等,触发器一共分两种,分别是简单的触发器和cron触发器;
  4. scheduler:调度器,它的作用就是将trigger和job联系起来,因为不管怎么说,触发器还是要作用在job身上才是有用的嘛;

他们之间的关系如下图所示:

简单点的关系:

复杂点的关系:

从上图我们可以看出,触发器可以绑定job(也可以用scheduler绑定),而scheduler和绑定触发器,进而将三者绑定在一起,同时呢,一个job是可以对应多个jobDetail的;

而一个jobDetail也是可以对应多个trigger的;

关于job和jobDetail的关系:

好的,概念扫盲暂时到这里,接下来让我们来看一点简单的实例把!

三、简单的quartz定时任务的使用:

前置pom:

 <!-- quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>

3.1、spring的注解@Bean方式:


    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean() {
        JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
        // 关联我们自己的job类
        factoryBean.setJobClass(QuartzDemo.class);// 这里他将job对象创建仅仅是反射,没有加入到spring容器里边
        return factoryBean;
    }
    
    @Bean
    public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean) {
        CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
        String cronExpress = "0/4 * * * * ? *";
        factoryBean.setCronExpression(cronExpress);
        return factoryBean;
    }

    /*     * 创建scheduler对象*/

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(CronTriggerFactoryBean CronTriggerFactoryBean, MyAdptableFactory myAdptableFactory) {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        // 关联trigger
        schedulerFactoryBean.setTriggers(CronTriggerFactoryBean.getObject());
        // 用自己写的jobFactory覆盖本来的factory实现将job加入到spring容器中
        schedulerFactoryBean.setJobFactory(myAdptableFactory);
        return schedulerFactoryBean;
    }

3.2、为什么要自己重写工厂类覆盖quartz本来的;

其中这里我们在最后创建scheduler的时候,说到了将自己写的jobFactory覆盖quartz本来的jobFactory,这是为什么呢?

其实我们的job的创建过程是在AdaptableJobFactory这个类中完成的,我们来看一下这个类的源码:

public class AdaptableJobFactory implements JobFactory {
    public AdaptableJobFactory() {
    }

    public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
        try {
            Object jobObject = this.createJobInstance(bundle);
            return this.adaptJob(jobObject);
        } catch (Throwable var4) {
            throw new SchedulerException("Job instantiation failed", var4);
        }
    }

    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Class<?> jobClass = bundle.getJobDetail().getJobClass();
        return ReflectionUtils.accessibleConstructor(jobClass, new Class[0]).newInstance();
    }

    protected Job adaptJob(Object jobObject) throws Exception {
        if (jobObject instanceof Job) {
            return (Job)jobObject;
        } else if (jobObject instanceof Runnable) {
            return new DelegatingJob((Runnable)jobObject);
        } else {
            throw new IllegalArgumentException("Unable to execute job class [" + jobObject.getClass().getName() + "]: only [org.quartz.Job] and [java.lang.Runnable] supported.");
        }
    }
}

它的重点就在createJobInstance 这个方法中,通过这个方法我们可以看到,job的创建仅仅就是一个反射的过程,并不是通过spring的动态代理代理出来的,这样的话就会造成很多问题,比如说:事务的失效,不能在job中用@AutoWire注解来注入别的类使用等等;

所以我们要自己重写他的一个实例过程,将他加入到spring容器中去;代码如下:

@Component("MyAdptableFactory")
public class MyAdptableFactory extends AdaptableJobFactory {
    /**
     * 改方法需要将实例话的任务对象否手动的添加到springIOC容器中并且完成对象的注入
     * 因为quartz的本来的创建job只是用反射创建对象加入这种情况是不能呗spirng容器所管理的
     * @param bundle
     * @return
     * @throws Exception
     */
    //AutowireCapableBeanFactory 可以将一个对象添加到spring IOC容器中,并且完成该对象的注入
    @Autowired
    private AutowireCapableBeanFactory autowireCapableBeanFactory;
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object object=super.createJobInstance(bundle);
         //将object添加到spring容器中
        this.autowireCapableBeanFactory.autowireBean(object);
        return object;
    }
}

这样的话,我们就可以在job中用 @Autowired注解来注入别的bean了,但是需要注意即使这样,我们自己实现的job类,还是无法应用事务的,因为这是我们手动将自己创建出来的类;

3.3、原生的写法实现简单定时任务:

在这个里边我们实现了将一个job里边绑定多个触发器;

这里对其中某些方法做一些解释:

其中withIdentity(绑定身份):作用是对于你创建的触发器或者job进行命名分组,作用是好管理;

usingJobData:作用是给job或者trigger一个属性值,这个属性值你可以在job中进行调用;

withSchedule:作用是给触发器一个触发事件,有两种,分别是简单的和cron的;

getJobDataMap:也是给job或者trigger进行注入属性;

forJob:给触发器绑定一个job;

//创建一个scheduler
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.getContext().put("skey", "svalue");

        //创建一个Trigger
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1") //进行触发器的命名分组
                .usingJobData("t1", "第一个触发器")  // 注入属性
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10)
                        .repeatForever()).build(); // 进行时间间隔的设置
        trigger.getJobDataMap().put("t2", "tv2");


        Trigger trigger1=TriggerBuilder.newTrigger()
                         .withIdentity("trigger2","group2")
                         .forJob("myjob","mygroup")
                         .usingJobData("t3","第二个触发")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                        .repeatForever()).build();
        Trigger trigger2=TriggerBuilder.newTrigger()
                .withIdentity("trigger2","group3")
                .forJob("myjob","mygroup")
                .usingJobData("t3","第二次触发修改")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();


        //创建一个job
        JobDetail job = JobBuilder.newJob(jobDemo1.class)
                .usingJobData("j1", "jv1") // 注入属性
                .withIdentity("myjob", "mygroup").build();//进行job的命名分组
        job.getJobDataMap().put("j2", "jv2");// 注入属性

        //注册trigger并启动scheduler
        scheduler.scheduleJob(job,trigger);
        scheduler.scheduleJob( trigger1);
        scheduler.scheduleJob(trigger2);
        scheduler.start();

3.3.1、上述代码中,jobDetail和trigger都创建好了,呢么job如何创建呢?我们看下边的代码---(只演示原生写法的job)

从代码中可以看出,我们可以把创建jobDetail和trigger时注入的属性一一拿出来,其中呢,getMergedJobDataMap是综合了jobDetail和trigger的属性并集,并且优先选取trigger中的属性;

public class jobDemo1 implements Job {
    public jobDemo1() {
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
//        Object tv1 = context.getTrigger().getJobDataMap().get("t1");
//        Object tv2 = context.getTrigger().getJobDataMap().get("t2");
//        Object jv1 = context.getJobDetail().getJobDataMap().get("j1");
//        Object jv2 = context.getJobDetail().getJobDataMap().get("j2");
//        Object sv = null;
//        try {
//            sv = context.getScheduler().getContext().get("skey");
//        } catch (SchedulerException e) {
//            e.printStackTrace();
//        }
//        System.out.println(tv1+":"+tv2);
//        System.out.println(jv1+":"+jv2);
//        System.out.println(sv);
//        System.out.println("hello:"+ LocalDateTime.now());
        JobKey key = context.getJobDetail().getKey();//获取jobDetail的分组和命名
        TriggerKey key1 = context.getTrigger().getKey();
        JobDataMap dataMap=context.getMergedJobDataMap();
        String j1 = dataMap.getString("j1");
        System.out.println("key = " + key);
        System.out.println("key1 = " + key1);
        System.out.println("dataMap = " + dataMap.get("t3"));
        System.out.println("j1 = " + j1);


    }
    }

恭喜你,如果你看到这里的话,呢么你对quartz的基本操作已经了解的差不多了,接下来的篇幅,就让我们去了解进阶的篇幅把!

四、略微复杂的quartz的使用:动态添加修改定时任务

     设想一个项目场景:

在一个项目中,我需要动态的向项目中添加或者修改定时任务,还有暂停和启动的操作,相当于动态的添加定时任务,如果有这种需求 的话呢?你打算怎么做?

4.1、quartz的固化(本案例不使用,仅作为学习)

 这样的话,我们就需要将定时任务集成到数据库中了,在这方面,quartz给我们做了固化,具体的操作很简单:

在项目的配置文件(application.yml)中加入如下配置;

其中,always,代表每次启动项目都会给数据库添加quartz的表,它里边还有另外两个属性:never、embedded

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
    username: root
    password: 123456
  quartz:
    job-store-type: jdbc
    jdbc:
      initialize-schema: always

加入之后,我们再启动项目的时候,会看到:日志中会有这么几行字:具体的意思就是,现在的quartz是以本地持久化的形式来的,不是在内存中的,并且没有集群;

现在的话,你的数据库中,就会多了几张表:分别存储着不同的信息;

4.2、自己创建表进行jobDetail和trigger的动态添加修改:

先给大家贴出一个实体 SettleTaskPlan ,后续说明作用:

public class SettleTaskPlan extends EntityBase implements Serializable {    
private static final long serialVersionUID = 1L;
	@TableId(value="SETTLE_TASK_ID", type = IdType.INPUT)
	@ExcelProperty(value = "序号",index = 0)
	private java.lang.Long settleTaskId;
	@TableField("APP_CODE")
	@ExcelProperty(value = "应用编码",index = 19)
	private java.lang.String appCode;
	@TableField("CHECK_REMARK")
	@ExcelProperty(value = "审核意见",index = 13)
	private java.lang.String checkRemark;
	@TableField("CHECK_STATE")
	@ExcelProperty(value = "审核状态",index = 14)
	private java.lang.String checkState;

	@TableField("CHECK_TIME")
	@ExcelProperty(value = "审核时间",index = 15)
	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
	private java.util.Date checkTime;
	@TableField("CHECK_USER_ID")
	@ExcelProperty(value = "审核人id",index = 12)
	private java.lang.Long checkUserId;
	@TableField("CRON_EXPRESS")
	@ExcelProperty(value = "执行表达式",index = 3)
	private java.lang.String cronExpress;
	@TableField("DEL_FLAG")
	@ExcelProperty(value = "删除标记",index = 16)
	private java.lang.String delFlag;
	@TableField("DISABLE_FLAG")
	@ExcelProperty(value = "禁用标记",index = 17)
	private java.lang.String disableFlag;
	@TableField("EXEC_TIMES")
	@ExcelProperty(value = "执行次数",index = 9)
	private java.lang.Long execTimes;
	@TableField("EXECUTE_FUN")
	private java.lang.String executeFun;
	@TableField("EXECUTE_JSON_PARAM")
	private java.lang.String executeJsonParam;
	@TableField("FAILED_TIMES")
	@ExcelProperty(value = "失败次数",index = 10)
	private java.lang.Long failedTimes;
	@TableField("LAST_EXECUTED")
	@ExcelProperty(value = "最后一次执行时间",index = 7)
	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
	private java.util.Date lastExecuted;
	@TableField("LAST_STATE")
	@ExcelProperty(value = "最后一次执行状态",index = 8)
	private java.lang.String lastState;
	@TableField("REMARK")
	@ExcelProperty(value = "备注",index = 18)
	private java.lang.String remark;
	@TableField("START_EXECUTED")
	@ExcelProperty(value = "开始时间",index = 4)
	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
	private java.util.Date startExecuted;
	@TableField("SUCCESS_TIMES")
	@ExcelProperty(value = "成功次数",index = 11)
	private java.lang.Long successTimes;
	@TableField("TASK_CODE")
	@ExcelProperty(value = "计划编码",index = 1)
	private java.lang.String taskCode;
	@TableField("TASK_NAME")
	@ExcelProperty(value = "计划名称",index = 2)
	private java.lang.String taskName;
	@TableField("TASK_TYPE")
	@ExcelProperty(value = "计划类型",index = 5)
	private java.lang.String taskType;
	@TableField("TRIGGER_FUN")
	@ExcelProperty(value = "模块代码",index = 6)
	private java.lang.String triggerFun;

	@TableField("SETTLE_OBJECT")
	private String settleObject;

 

 

前边已经介绍了简单的quartz的应用,相信大家肯定都可以自己手动创建一个定时任务并且跑起来了;

但是想要完成动态的添加修改,呢么应该怎么做呢?

我们可以想一下我们的需求,动态的添加修改;我们再看看前边文章的内容,前边的文章,我们介绍了如何在springboot里边添加一个定时任务,呢么是不是我们只需要再知道如何去修改和删除定时任务,呢么我们的需求就完成了呢?

下边介绍一下如何对程序中的定时任务进行修改和删除:

定时任务的修改:

 /**
     * 根据核算计划修改定时任务
     *
     * @param settleTaskPlan
     * @return
     * @throws SchedulerException
     */
    public boolean updateQuartzPlan(SettleTaskPlan settleTaskPlan) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(settleTaskPlan.getSettleTaskId().toString());
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        Trigger trigger1 = trigger.getTriggerBuilder()
                .withIdentity(triggerKey)
                .withSchedule(CronScheduleBuilder.cronSchedule(settleTaskPlan.getCronExpress()))
                .build();
        scheduler.rescheduleJob(triggerKey, trigger1);
        return true;
    }

定时任务的添加:

  /**
     * 根据核算计划新增一个定时任务详情到quartz中
     *
     * @param settleTaskPlan
     * @return
     * @throws SchedulerException
     */
    public boolean addQuartzPlan(SettleTaskPlan settleTaskPlan) throws SchedulerException {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setJobFactory(new MyAdptableFactory());
        //创建一个jobDetail
        JobDetail jobDetail = JobBuilder.newJob(QuartzDemo.class)
                .withIdentity(settleTaskPlan.getSettleTaskId().toString())
                .build();

        //创建对应的触发器
        Trigger trigger = TriggerBuilder.newTrigger()
                .startNow()
                .withIdentity(settleTaskPlan.getSettleTaskId().toString())
                .usingJobData("settleTaskId", settleTaskPlan.getSettleTaskId().toString())
                .withSchedule(CronScheduleBuilder.cronSchedule(settleTaskPlan.getCronExpress()))
                .build();
        //将触发器和jobDetail联合起来
        scheduler.scheduleJob(jobDetail, trigger);
        return true;
    }

定时任务的删除:

 /**
     * 按照核算计划主键将定时计划从quartz中剔除掉
     *
     * @param settleTaskPlans
     * @return
     */
    public boolean deleteQuartzPlan(List<SettleTaskPlan> settleTaskPlans) throws SchedulerException {
        for (SettleTaskPlan settleTaskPlan : settleTaskPlans) {
            //将对应的jobdetail删除掉
            JobKey jobKey = JobKey.jobKey(settleTaskPlan.getSettleTaskId().toString());
            scheduler.deleteJob(jobKey);
        }
        return true;
    }

 

同时我们应该再去思考一个问题,我们如何将这些定时任务存储起来呢?总不能我添加完在下次启动的时候就没有了吧?这是不合理的。

所以我们要在数据库建立一张表SettleTaskPlan ,将定时任务存储起来,定时任务的分组命名信息就是表的主键,这样我们就可以根据表的主键创建修改删除对应的定时任务了;

表中最主要的信息就是两个:标识符(主键),cron表达式,其余的可以根据自己的需要自行添加;

建立好表之后,我们只需要在系统的每次启动前,循环访问数据库添加对应的定时任务就可以了:

public class QuartzConfig {
    //TODO:两种思路,一种是给每一个事件都创建一个jobDetail和一个触发器,另一种是给一个jobDetail创建多个触发器
    @Autowired
    SettleTaskPlanService settleTaskPlanService;

    @Autowired
    public Scheduler scheduler;
    //在每次启动项目的时候,去循环数据库中的定时任务然后启动这些定时任务
    @PostConstruct
    public void initJob()throws Exception{
        //从数据库中查询所有的定时任务并且一个一个启动
        QueryWrapper<SettleTaskPlan> queryWrapper=new QueryWrapper();
        queryWrapper.eq("DEL_FLAG","0");
        queryWrapper.eq("DISABLE_FLAG","0");
        queryWrapper.eq("CHECK_STATE","1");
        List<SettleTaskPlan> list = settleTaskPlanService.list(queryWrapper);
        //用自己写的jobFactory覆盖本来的factory实现将job加入到spring容器中
            SchedulerFactoryBean schedulerFactoryBean=new SchedulerFactoryBean();
            schedulerFactoryBean.setJobFactory(new MyAdptableFactory());
            for (SettleTaskPlan taskPlan : list) {
                //创建一个jobDetail
                JobDetail jobDetail= JobBuilder.newJob(QuartzDemo.class)
                        .withIdentity(taskPlan.getSettleTaskId().toString())
                        .build();

                //创建对应的触发器
                Trigger trigger=TriggerBuilder.newTrigger()
                        .startNow()
                        .withIdentity(taskPlan.getSettleTaskId().toString())
                        .usingJobData("settleTaskId",taskPlan.getSettleTaskId().toString())
                        .withSchedule(CronScheduleBuilder.cronSchedule(taskPlan.getCronExpress()))
                        .build();
                //将触发器和jobDetail联合起来
                scheduler.scheduleJob(jobDetail, trigger);
                scheduler.start();
            }
    }
}

以上就是quartz的动态添加修改;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果你使用的是 Spring Boot 框架,可以在 `application.properties` 或 `application.yml` 文件中添加以下配置: ```properties # Quartz 配置 ## 指定 Quartz 的 Scheduler 实现类 spring.quartz.scheduler-name = MyScheduler spring.quartz.job-store-type = jdbc spring.quartz.jdbc.initialize-schema = always ## 数据库连接配置 spring.quartz.properties.org.quartz.dataSource.myDS.driver = com.mysql.cj.jdbc.Driver spring.quartz.properties.org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz?serverTimezone=UTC spring.quartz.properties.org.quartz.dataSource.myDS.user = root spring.quartz.properties.org.quartz.dataSource.myDS.password = root ## 配置线程池 spring.quartz.properties.org.quartz.threadPool.threadCount = 10 spring.quartz.properties.org.quartz.threadPool.threadPriority = 5 spring.quartz.properties.org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool ``` 上述配置中,我们指定了 Quartz 的实现类为 `MyScheduler`,使用了 JDBC 存储方式,在启动时自动初始化数据库。同时,我们还配置了数据库连接信息和线程池相关配置。 如果你使用的是 `application.yml` 文件,可以按如下格式进行配置: ```yaml # Quartz 配置 spring: quartz: scheduler-name: MyScheduler job-store-type: jdbc jdbc: initialize-schema: always properties: org: quartz: dataSource: myDS: driver: com.mysql.cj.jdbc.Driver URL: jdbc:mysql://localhost:3306/quartz?serverTimezone=UTC user: root password: root threadPool: threadCount: 10 threadPriority: 5 class: org.quartz.simpl.SimpleThreadPool ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值