Quartz(6-SpringBoot整合) | 总字数: 3.5k | 阅读时长: 15分钟 | 浏览量: |
Quartz 自动装配机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @ConfigurationProperties("spring.quartz") public class QuartzProperties { private JobStoreType jobStoreType = JobStoreType.MEMORY; private String schedulerName; private boolean autoStartup = true ; private Duration startupDelay = Duration.ofSeconds(0 ); private boolean waitForJobsToCompleteOnShutdown = false ; private boolean overwriteExistingJobs = false ; private final Map<String, String> properties = new HashMap <>(); private final Jdbc jdbc = new Jdbc (); public static class Jdbc { private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/quartz/impl/" + "jdbcjobstore/tables_@@platform@@.sql" ; private String schema = DEFAULT_SCHEMA_LOCATION; private DataSourceInitializationMode initializeSchema = DataSourceInitializationMode.EMBEDDED; private List<String> commentPrefix = new ArrayList <>(Arrays.asList("#" , "--" )); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class }) @EnableConfigurationProperties(QuartzProperties.class) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, LiquibaseAutoConfiguration.class, FlywayAutoConfiguration.class }) public class QuartzAutoConfiguration { @Bean @ConditionalOnMissingBean public SchedulerFactoryBean quartzScheduler (QuartzProperties properties, ObjectProvider<SchedulerFactoryBeanCustomizer> customizers, ObjectProvider<JobDetail> jobDetails, Map<String, Calendar> calendars, ObjectProvider<Trigger> triggers, ApplicationContext applicationContext) { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean (); SpringBeanJobFactory jobFactory = new SpringBeanJobFactory (); jobFactory.setApplicationContext(applicationContext); schedulerFactoryBean.setJobFactory(jobFactory); if (properties.getSchedulerName() != null ) { schedulerFactoryBean.setSchedulerName(properties.getSchedulerName()); } schedulerFactoryBean.setAutoStartup(properties.isAutoStartup()); schedulerFactoryBean.setStartupDelay((int ) properties.getStartupDelay().getSeconds()); schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(properties.isWaitForJobsToCompleteOnShutdown()); schedulerFactoryBean.setOverwriteExistingJobs(properties.isOverwriteExistingJobs()); if (!properties.getProperties().isEmpty()) { schedulerFactoryBean.setQuartzProperties(asProperties(properties.getProperties())); } schedulerFactoryBean.setJobDetails(jobDetails.orderedStream().toArray(JobDetail[]::new )); schedulerFactoryBean.setCalendars(calendars); schedulerFactoryBean.setTriggers(triggers.orderedStream().toArray(Trigger[]::new )); customizers.orderedStream().forEach((customizer) -> customizer.customize(schedulerFactoryBean)); return schedulerFactoryBean; } private Properties asProperties (Map<String, String> source) { Properties properties = new Properties (); properties.putAll(source); return properties; } @Configuration(proxyBeanMethods = false) @ConditionalOnSingleCandidate(DataSource.class) @ConditionalOnProperty(prefix = "spring.quartz", name = "job-store-type", havingValue = "jdbc") protected static class JdbcStoreTypeConfiguration { @Bean @Order(0) public SchedulerFactoryBeanCustomizer dataSourceCustomizer (QuartzProperties properties, DataSource dataSource, @QuartzDataSource ObjectProvider<DataSource> quartzDataSource, ObjectProvider<PlatformTransactionManager> transactionManager) { return (schedulerFactoryBean) -> { DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource); schedulerFactoryBean.setDataSource(dataSourceToUse); PlatformTransactionManager txManager = transactionManager.getIfUnique(); if (txManager != null ) { schedulerFactoryBean.setTransactionManager(txManager); } }; } private DataSource getDataSource (DataSource dataSource, ObjectProvider<DataSource> quartzDataSource) { DataSource dataSourceIfAvailable = quartzDataSource.getIfAvailable(); return (dataSourceIfAvailable != null ) ? dataSourceIfAvailable : dataSource; } @Bean @ConditionalOnMissingBean public QuartzDataSourceInitializer quartzDataSourceInitializer (DataSource dataSource, @QuartzDataSource ObjectProvider<DataSource> quartzDataSource, ResourceLoader resourceLoader, QuartzProperties properties) { DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource); return new QuartzDataSourceInitializer (dataSourceToUse, resourceLoader, properties); } @Configuration(proxyBeanMethods = false) static class QuartzSchedulerDependencyConfiguration { @Bean static SchedulerDependsOnBeanFactoryPostProcessor quartzSchedulerDataSourceInitializerDependsOnBeanFactoryPostProcessor () { return new SchedulerDependsOnBeanFactoryPostProcessor (QuartzDataSourceInitializer.class); } @Bean @ConditionalOnBean(FlywayMigrationInitializer.class) static SchedulerDependsOnBeanFactoryPostProcessor quartzSchedulerFlywayDependsOnBeanFactoryPostProcessor () { return new SchedulerDependsOnBeanFactoryPostProcessor (FlywayMigrationInitializer.class); } @Configuration(proxyBeanMethods = false) @ConditionalOnClass(SpringLiquibase.class) static class LiquibaseQuartzSchedulerDependencyConfiguration { @Bean @ConditionalOnBean(SpringLiquibase.class) static SchedulerDependsOnBeanFactoryPostProcessor quartzSchedulerLiquibaseDependsOnBeanFactoryPostProcessor () { return new SchedulerDependsOnBeanFactoryPostProcessor (SpringLiquibase.class); } } } } private static class SchedulerDependsOnBeanFactoryPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor { SchedulerDependsOnBeanFactoryPostProcessor(Class<?>... dependencyTypes) { super (Scheduler.class, SchedulerFactoryBean.class, dependencyTypes); } } }
SpringBoot 整合 Quartz 后,通过 spring.quartz 前缀可以设置其对应的属性值,实例化 SchedulerFactoryBean 后,调用其 afterPropertiesSet()方法去初始化 Quartz 配置信息(详细查看 SchedulerFactoryBean 的 initialize()方法及 Quartz(3-Scheduler)文章 )
单机版配置
依赖
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <version > 2.3.12.RELEASE</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-quartz</artifactId > <version > 2.3.12.RELEASE</version > </dependency >
任务创建
1 2 3 4 5 6 7 8 9 10 11 12 public class TestJob extends QuartzJobBean { private static final Log logger = LogFactory.getLog(TestJob.class); @Override protected void executeInternal (JobExecutionContext context) throws JobExecutionException { logger.info("测试======" ); } }
可以实现 Job 接口,也可以继承 QuartzJobBean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Configuration public class QuartzConfig { @Bean public JobDetail jobDetail () { return JobBuilder.newJob(TestJob.class) .withIdentity("testJob" ) .withDescription("testJob" ) .storeDurably() .build(); } @Bean public Trigger trigger () { return TriggerBuilder.newTrigger() .forJob(jobDetail()) .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5 )) .build(); } }
项目初始化手动创建任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Component public class JobInit implements ApplicationRunner { private static final String ID = "SUMMERDAY" ; @Autowired private Scheduler scheduler; @Override public void run (ApplicationArguments args) throws Exception { JobDetail jobDetail = JobBuilder.newJob(TestJob.class) .withIdentity(ID + " 01" ) .storeDurably() .build(); CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ? *" ); Trigger trigger = TriggerBuilder.newTrigger() .forJob(jobDetail) .withIdentity(ID + " 01Trigger" ) .withSchedule(scheduleBuilder) .startNow() .build(); scheduler.scheduleJob(jobDetail, trigger); } }
yaml 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 spring: quartz: job-store-type: memory auto-startup: true startup-delay: 0 wait-for-jobs-to-complete-on-shutdown: true overwrite-existing-jobs: false properties: org: quartz: threadPool: threadCount: 25 threadPriority: 5 class: org.quartz.simpl.SimpleThreadPool
持久化配置
集群部署
一个 Quartz 集群中的每个节点是一个独立的 Quartz 应用,它又管理着其他的节点。这就意味着你必须对每个节点分别启动或停止。Quartz 集群中,独立的 Quartz 节点并不与另一其的节点或是管理节点通信,而是通过同一个数据库表来感知到另一 Quartz 应用的。因为 Quartz 集群依赖于数据库,所以必须首先创建 Quartz 数据库表,Quartz 发布包中包括了所有被支持的数据库平台的 SQL 脚本。
创建数据库表
针对不同的数据库,org.quartz.impl.jdbcjobstore 包下提供了不同建表脚本,数据库脚本放在 org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar! 包中的\org\quartz\impl\jdbcjobstore 路径下
表名
描述
qrtz_fired_triggers
存储已触发的 trigger 相关信息
qrtz_paused_trigger_grps
存储已暂停的 trigger 组信息
qrtz_scheduler_state
存储 Scheduler 状态信息
qrtz_locks
存储悲观锁的信息
qrtz_simple_triggers
存储 Simple trigger 信息
qrtz_simprop_triggers
存储其他几种 trigger 信息
qrtz_cron_triggers
存储 cron trigger 信息
qrtz_blob_triggers
blog 类型存储 triggers
qrtz_triggers
存储已配置的 trigger 信息
qrtz_job_details
存储每一个已配置的 job details
qrtz_calendars
以 blog 类型存储 Calendar 信息
所有的表中都含有一个 SCHED_NAME 字段,对应我们配置的 scheduler-name,相同 Scheduler-name 的节点,形成一个 Quartz 集群
存储类型
Quartz 提供两种基本作业存储类型:RAMJobStore 和 JDBC 作业存储。在默认情况下 Quartz 将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能,因为内存中数据访问最快。不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失。
类型
优点
缺点
RAMJobStore
不要外部数据库,配置容易,运行速度快
因为调度程序信息是存储在被分配给 JVM 的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到 JVM 内存里面,所以可以存储多少个 Job 和 Trigger 将会受到限制
JDBC 作业存储
支持集群,因为所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务
运行速度的快慢取决与连接数据库的快慢
事务管理类型
类型
描述
JobStoreTX
如果不需要将调度命令(例如添加和删除 triggers)绑定到其他事务,那么可以通过使用 JobStoreTX 管理事务(这是最常见的选择)
JobStoreCMT
如果需要 Quartz 与其他事务(即 J2EE 应用程序服务器)一起工作,那么应该使用 JobStoreCMT,这种情况下,Quartz 将让应用程序服务器容器管理事务
1 2 3 org.quartz.jobStore.class =org.quartz.impl.jdbcjobstore.JobStoreCMT
数据库驱动代理
针对不同的数据库制作了不同的数据库的代理,其中使用最多的是 StdJDBCDelegate ,它是一个使用 JDBC 代码(和 SQL 语句)来执行其工作的委托。其他驱动代理可以在 “org.quartz.impl.jdbcjobstore” 包或其子包中找到。如 DB2v6Delegate(用于 DB2 版本 6 及更早版本),HSQLDBDelegate(HSQLDB),MSSQLDelegate(SQLServer),PostgreSQLDelegate(PostgreSQL),WeblogicDelegate(用于使用 Weblogic 创建的 JDBC 驱动程序)
1 org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
配置信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-quartz</artifactId > <version > 2.3.12.RELEASE</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <version > 2.3.12.RELEASE</version > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.3.2</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.19</version > </dependency >
yaml 配置(使用其自动装配机制)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/quartz?serverTimezone=GMT%2B8 username: root password: 123456 quartz: job-store-type: jdbc scheduler-name: hyhScheduler wait-for-jobs-to-complete-on-shutdown: true jdbc: initialize-schema: never properties: org: quartz: startup-delay: 60 overwrite-existing-jobs: false scheduler: instanceId: AUTO instanceName: hyhScheduler jobStore: class: org.quartz.impl.jdbcjobstore.JobStoreTX driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate tablePrefix: QRTZ_ isClustered: true clusterCheckinInterval: 1000 useProperties: false misfireThreshold: 60000 threadPool: threadCount: 25 threadPriority: 5 class: org.quartz.simpl.SimpleThreadPool
注意:同一集群下,instanceName 必须相同,instanceId 可自动生成,isClustered 为 true,持久化存储,指定数据库类型对应的驱动类和数据源连接。
自定义 properties 和配置类(使用 Quartz 的 initialize()方法去查找对应的 quartz.properties 文件)
quartz.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 org.quartz.scheduler.instanceName = hyhScheduler org.quartz.scheduler.instanceId = AUTO org.quartz.jobStore.class =org.springframework.scheduling.quartz.JobStoreTX org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.jobStore.useProperties = true org.quartz.jobStore.isClustered = true org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.jobStore.clusterCheckinInterval = 20000 org.quartz.jobStore.misfireThreshold = 60000 org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
配置类(参考 QuartzAutoConfiguration)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component public class JobFactory extends AdaptableJobFactory { @Resource private AutowireCapableBeanFactory capableBeanFactory; @Override protected Object createJobInstance (TriggerFiredBundle bundle) throws Exception { Object jobInstance = super .createJobInstance(bundle); capableBeanFactory.autowireBean(jobInstance); return jobInstance; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Configuration public class JobConfig { @Resource private JobFactory jobFactory; @Resource private DataSource dataSource; @Bean public SchedulerFactoryBean schedulerFactoryBean () { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean (); schedulerFactoryBean.setJobFactory(jobFactory); schedulerFactoryBean.setDataSource(dataSource); schedulerFactoryBean.setOverwriteExistingJobs(true ); schedulerFactoryBean.setStartupDelay(1 ); schedulerFactoryBean.setConfigLocation(new ClassPathResource ("quartz.properties" )); return schedulerFactoryBean; } @Bean public Scheduler scheduler () { return schedulerFactoryBean().getScheduler(); } }
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Component public class JobInit implements ApplicationRunner { private static final String ID = "SUMMERDAY" ; @Autowired private Scheduler scheduler; @Override public void run (ApplicationArguments args) throws Exception { JobDetail jobDetail = JobBuilder.newJob(SecondJob.class) .withIdentity(ID + " 02" ) .storeDurably() .build(); CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *" ); Trigger trigger = TriggerBuilder.newTrigger() .forJob(jobDetail) .withIdentity(ID + " 02Trigger" ) .withSchedule(scheduleBuilder) .startNow() .build(); Set<Trigger> set = new HashSet <>(); set.add(trigger); scheduler.scheduleJob(jobDetail, set, true ); } }