• SpringBoot整合Quartz定时


    背景
    最近在做项目,项目中有个需求:需要使用定时任务,这个定时任务需要即时生效。
    查看Quartz官网之后发现:Quartz提供两种基本作业存储类型:

    RAMJobStore :RAM也就是内存,默认情况下Quartz会将任务调度存在内存中,这种方式性能是最好的,因为内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失
    JDBC作业存储:存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢。
    所以决定采用 JDBC作业存储的方式。

    为什么需要持久化?
    以后可以做集群。
    任务可以进行管理,随时停止、暂停、修改任务。
    你应该了解的概念
    因为之前接触过quartz这个任务调度框架,所以对quartz有一定的了解,quartz三要素:Scheduler、Trigger、JobDetai&Job。
    突然想起来,之前写过介绍-。- 自己都忘了,贴上地址,需要的童鞋可以先去了解下:
    https://blog.csdn.net/bicheng4769/article/details/81097305

    SpringBoot集成Quartz
    我们也可以自己去将quartz和springBoot整合在一起,其实说是springBoot还不如说是sping,因为我们没有用到spirngboot的相关的快捷方式。
    如果童鞋们想快速集成Quartz,立刻看到效果的话,可以直接往下翻,直接看SpirngBoot自带的Quartz插件。但我建议大家还是从spring整合Quartz开始,懂的原理,方有收获。

    Quartz初始化表
    如果需要做持久化的话,数据肯定是要存在数据库的,那么到底存在哪些表呢?其实官网文档也跟我们讲过了,地址如下:
    http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-09.html
    其中有句话:

    JDBCJobStore works with nearly any database, it has been used widely with Oracle, PostgreSQL, MySQL, MS SQLServer, HSQLDB, and DB2. To use JDBCJobStore, you must first create a set of database tables for Quartz to use. You can find table-creation SQL scripts in the “docs/dbTables” directory of the Quartz distribution.

    荣老夫这个四级的水平给你们翻译下:
    大概就是支持这么多的数据库类型。如果你要使用JDBCJoBStore的话,你先要创建一些表,这些表在 “doc/dbTables”里面。“doc/dbTables” 在哪儿呢?其实都在源码里面,直接到官网下下来就行了。

    Spring整合Quartz
    pom文件加入相关jar
    相关配置文件(不管是properties 还是yml。采用JDBC存储)
    业务逻辑层中使用。
    pom文件
    如下所示:

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <!--quartz -->
    <dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    </dependency>
    <dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz-jobs</artifactId>
    <!-- <version>2.3.0</version> -->
    </dependency>
    <!--定时任务需要依赖context模块-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.1</version>
    </dependency>
    <dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
    </dependency>
    <!--&lt;!&ndash; druid数据库连接池 &ndash;&gt;-->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
    </dependency>
    </dependencies>
    对应的properties 文件
    #使用自己的配置文件
    org.quartz.jobStore.useProperties:true

    #默认或是自己改名字都行
    org.quartz.scheduler.instanceName: DefaultQuartzScheduler
    #如果使用集群,instanceId必须唯一,设置成AUTO
    org.quartz.scheduler.instanceId = AUTO


    org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount: 10
    org.quartz.threadPool.threadPriority: 5
    org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true


    #存储方式使用JobStoreTX,也就是数据库
    org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
    org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    #是否使用集群(如果项目只部署到 一台服务器,就不用了)
    org.quartz.jobStore.isClustered = false
    org.quartz.jobStore.clusterCheckinInterval=20000
    org.quartz.jobStore.tablePrefix = qrtz_
    org.quartz.jobStore.dataSource = myDS

    #配置数据源
    #数据库中quartz表的表名前缀
    org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
    org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/aipyun?serverTimezone=GMT&characterEncoding=utf-8
    org.quartz.dataSource.myDS.user = root
    org.quartz.dataSource.myDS.password = root123
    org.quartz.dataSource.myDS.maxConnections = 5
    核心QuartzConfiguration类:
    ackage com.cj.config;

    import org.quartz.Scheduler;
    import org.quartz.spi.JobFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.scheduling.quartz.SchedulerFactoryBean;

    /**
    * 描述 : quartz 配置信息
    *
    * @author caojing
    * @create 2018-12-24-16:47
    */
    @Configuration
    public class QuartzConfiguration {
    @Autowired
    private JobFactory jobFactory;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
    SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
    schedulerFactoryBean.setJobFactory(jobFactory);
    // 用于quartz集群,QuartzScheduler 启动时更新己存在的Job
    schedulerFactoryBean.setOverwriteExistingJobs(true);
    //延长启动
    schedulerFactoryBean.setStartupDelay(1);
    //设置加载的配置文件
    schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
    return schedulerFactoryBean;
    }

    @Bean
    public Scheduler scheduler() {
    return schedulerFactoryBean().getScheduler();
    }
    }
    这其中我们把2个类的初始化移到了IOC中,因为之前Quartz的实例化是自己去控制的,为什么要这么做后面会有讲到。
    一个是SchedulerFactoryBean类,这个类其实就是之前xml配置中的SchedulerFactoryBean。附上之前xml配置如下(这里不需要配置,springboot建议我们少用xml配置)

    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
    <list>
    <ref bean="oceanStatusCronTrigger"/>
    </list>
    </property>
    </bean>
    这个类我相信只要用过xml配置的人一定很熟悉,这是Quartz入口。同时也是spring 和Scheduler 关系的桥梁。以便在Spring容器启动后,Scheduler自动开始工作,而在Spring容器关闭前,自动关闭Scheduler。

    JobFactory类
    package com.cj.config;

    import org.quartz.spi.TriggerFiredBundle;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.scheduling.quartz.AdaptableJobFactory;
    import org.springframework.scheduling.quartz.SpringBeanJobFactory;
    import org.springframework.stereotype.Component;

    /**
    * 描述:
    *
    * @author caojing
    * @create 2018-12-26-14:03
    */
    @Component
    public class JobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;


    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
    // 调用父类的方法
    Object jobInstance = super.createJobInstance(bundle);
    // 进行注入
    capableBeanFactory.autowireBean(jobInstance);
    return jobInstance;
    }
    }
    这个类的作用就是讲Job的实例化交给IOC去进行。
    其实问题在于:
    Job对象的实例化过程是在Quartz中进行的,注入的实体类是在Spring容器当中的 所以在job中无法注入Srping容器的实体类。
    解决方案:将Job Bean也纳入到Spring容器的管理之中,Spring容器自然能够为Job Bean自动装配好所需的依赖。
    如何纳入:Job的创建都是通过JobFactory创建的。官网解释为证:
    https://www.quartz-scheduler.org/api/2.2.1/org/quartz/spi/JobFactory.html

    A JobFactory is responsible for producing instances of Job classes.

    翻译:JobFactory负责生成Job类的实例。
    JobFactory 有2个实现类:AdaptableJobFactory 和 SimpleJobFactory。

    自定义的工厂类 JobFactory 继承 AdaptableJobFactory 。
    通过调用父类 AdaptableJobFactory 的方法createJobInstance来实现对Job的实例化。
    在Job实例化完以后,再调用自身方法为创建好的Job实例进行属性自动装配并将其纳入到Spring容器的管理之中。(通过AutowireCapableBeanFactory纳入)。截胡~~~~
    UploadTask 类:
    package com.cj.quartzdemo;
    import com.cj.controller.IndexController;
    import org.quartz.DisallowConcurrentExecution;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Service;

    import java.util.Date;

    /**
    * 描述:
    *
    * @author caojing
    * @create 2018-12-25-11:38
    */
    @Component
    @DisallowConcurrentExecution
    public class UploadTask extends QuartzJobBean {
    @Autowired
    private IndexController indexController;
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    System.out.println(new Date() + "任务开始------------------------------------");
    try {
    Thread.sleep(10000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(new Date() + "任务结束------------------------------------");
    }
    }


    继承QuartzJobBean类,重写executeInternal方法。
    附:DisallowConcurrentExecution 比如job执行10秒,任务是每隔5秒执行,加上这个注解,程序就会等10秒结束后再执行下一个任务。

    indexController类:
    package com.cj.controller;

    import com.cj.quartzdemo.UploadTask;
    import org.quartz.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;

    /**
    * 描述:
    *
    * @author caojing
    * @create 2018-12-26-14:11
    */
    @Controller
    public class IndexController {
    @Autowired
    private Scheduler scheduler;

    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public void index() throws SchedulerException {
    //cron表达式
    CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/8 * * * * ?");
    //根据name 和group获取当前trgger 的身份
    TriggerKey triggerKey = TriggerKey.triggerKey("cj", "123");
    CronTrigger triggerOld = null;
    try {
    //获取 触发器的信息
    triggerOld = (CronTrigger) scheduler.getTrigger(triggerKey);
    } catch (SchedulerException e) {
    e.printStackTrace();
    }
    if (triggerOld == null) {
    //将job加入到jobDetail中
    JobDetail jobDetail = JobBuilder.newJob(UploadTask.class).withIdentity("cj", "123").build();
    Trigger trigger = TriggerBuilder.newTrigger().withIdentity("cj","123").withSchedule(cronScheduleBuilder).build();
    //执行任务
    scheduler.scheduleJob(jobDetail, trigger);
    } else {
    System.out.println("当前job已存在--------------------------------------------");
    }
    }
    }

    浏览器输入 http://localhost:8080/index 就可以看到数据库已经存储了我们写的cron表达式和相应的类。
    查看数据库表(qrtz_cron_triggers)附上截图:


    至此,job 已经被我们成功持久化到数据库。我们来回顾下整体的一个流程。

    pom文件添加对应的依赖。
    mysql数据库对应表的初始化。
    配置对应的properties
    将原来quartz控制的类的实例化交给spirng IOC控制。(对应的是核心QuartzConfiguration类和JobFactory类)
    业务逻辑层对job进行控制。
    总结
    其实思路整理一下,我们发现过程其实还是挺简单的,唯一可能有些困难的是对QuartzConfiguration类和JobFactory类的理解。这两个类也是整合的核心类。
    但是在springboot2.0之后,我发现了一个很神奇的starter。

    深海收破烂
  • 相关阅读:
    10 个深恶痛绝的 Java 异常。。
    为什么公司宁愿 25K 重新招人,也不给你加到 20K?原因太现实……
    推荐一款代码神器,代码量至少省一半!
    Spring Cloud Greenwich 正式发布,Hystrix 即将寿终正寝。。
    hdu 3853 LOOPS(概率 dp 期望)
    hdu 5245 Joyful(期望的计算,好题)
    hdu 4336 Card Collector(期望 dp 状态压缩)
    hdu 4405 Aeroplane chess(概率+dp)
    hdu 5036 Explosion(概率期望+bitset)
    hdu 5033 Building (单调栈 或 暴力枚举 )
  • 原文地址:https://www.cnblogs.com/itboxue/p/12387423.html
Copyright © 2020-2023  润新知