背景
最近在做项目,项目中有个需求:需要使用定时任务,这个定时任务需要即时生效。
查看Quartz官网之后发现:Quartz提供两种基本作业存储类型:
- RAMJobStore :RAM也就是内存,默认情况下Quartz会将任务调度存在内存中,这种方式性能是最好的,因为内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失
- JDBC作业存储:存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢。
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文件
<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> <!--<!– druid数据库连接池 –>--> <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的创建都是通过JobFactory创建的。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进行控制。