SpringBoot + Quartz 框架的实现及源码的深究

    定时任务 想必做程序的都或多或少的接触过,以便于我们以某个特定的 时间/频率 去执行所需要的程序,Quartz 是一个优秀的框架,可以根据我们的配置将 定时任务的执行 时间/频率 持久化至数据库, 我们通过修改数据库中的任务下次执行时间,达到不需要等到任务配置执行的原始 时间/频率,随时地运行定时任务; 并且可以看到任务的运行状态 WATING BLOCKING等

   1.导入依赖▼

   quartz自定义配置的数据源会使用C3P0创建连接,所以要引入C3P0依赖

 <!-- Quartz定时任务 -->
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-quartz</artifactId>
   </dependency>
<!--C3P0 -->
   <dependency>
       <groupId>com.mchange</groupId>
       <artifactId>c3p0</artifactId>
       <version>0.9.5.5</version>
   </dependency>

2.quartz 配置文件,yml方式▼

创建定时任务表的sql太长,这里就不贴了,我会将sql上传至GitHub,文末我会贴地址

## quartz定时任务
spring:
  quartz:
    #jdbc 采用数据库方式  memory 采用内存方式
    job-store-type: jdbc  
    initialize-schema: embedded
    #设置自动启动,默认为 true
    auto-startup: true
    #启动时更新己存在的Job
    overwrite-existing-jobs: true
    properties:
      org:
        quartz:
          scheduler:
            instanceName: MyScheduler
            instanceId: AUTO
          jobStore:
            #指定使用的JobStore
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            #数据库前缀
            tablePrefix: QRTZ_
            #是否为集群
            isClustered: false
            #检测任务执行时间的间隔  毫秒
            misfireThreshold: 5000
            clusterCheckinInterval: 10000
            #数据源名称
            dataSource: myDS
          #线程池配置
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 20
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
          #数据源
          dataSource:
            myDS:
              driver: com.mysql.cj.jdbc.Driver
              URL: jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&serverTimezone=Asia/Shanghai
              user: root
              password: root
              maxConnections: 5

有同学可能会问了,配置文件是配置好了,是在哪引用的呢? 别急, 且听我娓娓道来

spring-boot-starter-quartz (为方便诉说,下文中使用 bootquartz代替) 这个包下的QuartzProperties会帮我们自动加载配置文件,且看以下部分截图

▲可以看到, QuartzProperties 使用了 @ConfigurationProperties 加载了 spring.quartz 前缀的配置,也就是上面我们的配置文件中的配置;加载之后呢, bootquartz包下有 类 QuartzAutoConfiguration, 看名字就可以知道,这个就是自动配置 quartz的类了.

所以我们不需要再去通过代码去配置 SchedulerFactoryBean 了,这是后话

QuartzAutoConfiguration 类注释▼

通过上面的截图我们发现,这里引用了 QuartzProperties▲

其中的 quartzScheduler()方法帮助我们创建了 SchedulerFactoryBean 并使用了 QuartzProperties 中的自定义配置,以下是quartzScheduler()部分代码▼

	@Bean
	@ConditionalOnMissingBean
	public SchedulerFactoryBean quartzScheduler() {
		SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
		if (!this.properties.getProperties().isEmpty()) {
			schedulerFactoryBean
					.setQuartzProperties(asProperties(this.properties.getProperties()));
		}
		customize(schedulerFactoryBean);
		return schedulerFactoryBean;
	}

姑且一提,方法中调用了 customize(SchedulerFactoryBean  schedulerFactoryBean) 方法,这个方法会寻找实现了 SchedulerFactoryBeanCustomizer 接口的配置类,在其实现方法 customize(SchedulerFactoryBean  schedulerFactoryBean)中 可对 SchedulerFactoryBean  使用代码自定义配置

那么到这里结束了吗?不! 这里还有本文中最大的一个坑,作者深受其扰,扒了两天的源码才找到这个问题!!!

如果我们的项目中有其它的默认数据源,那么quartz会忽略配置文件中自定义数据源,使用默认数据源,原因看以下源码▼

首先是 QuartzAutoConfiguration 中的 静态内部类 JdbcStoreTypeConfiguration▼

	@Configuration
	@ConditionalOnSingleCandidate(DataSource.class)
	protected static class JdbcStoreTypeConfiguration {

		@Bean
		@Order(0)
		public SchedulerFactoryBeanCustomizer dataSourceCustomizer(
				QuartzProperties properties, DataSource dataSource,
				@QuartzDataSource ObjectProvider<DataSource> quartzDataSource,
				ObjectProvider<PlatformTransactionManager> transactionManager) {
			return (schedulerFactoryBean) -> {
				if (properties.getJobStoreType() == JobStoreType.JDBC) {
                              //重点在这里 begin
					DataSource dataSourceToUse = getDataSource(dataSource,
							quartzDataSource);
					schedulerFactoryBean.setDataSource(dataSourceToUse);
                              //重点在这里 end
					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;
		}

▲其中的getDataSource 方法判断了我们项目中的 quartzDataSource是否为空,如果为空,那么就使用默认的数据源;quartzDataSource怎么才能不为空呢? 可以看到dataSourceCustomizer 方法参数中有 @QuartzDataSource 注解, 这个注解会去寻找我们项目中使用@QuartzDataSource配置的数据源,但是 我都已经在配置文件中自定义了数据源,再去手动配置一遍不是多此一举吗? 接着往下看

SchedulerFactoryBean 的初始化方法部分源码▼

private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException {
		Properties mergedProps = new Properties();
	
		if (this.dataSource != null) {
			mergedProps.setProperty(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName());
		}
}

我们在静态内部类设置过了数据源,初始化方法只要发现数据源不为空,那么就使用会使用 LocalDataSourceJobStore 覆盖我们quartz配置文件中设置的  org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX

而LocalDataSourceJobStore 中的初始化方法使用的是 SchedulerFactoryBean 中设置的数据源,所以我们quartz配置文件中的数据源才不会生效!!!

怎么解决呢?   我们上面提到了customize(SchedulerFactoryBean  schedulerFactoryBean) 方法,这个方法会寻找实现了 SchedulerFactoryBeanCustomizer 接口的配置类,在其实现方法 customize(SchedulerFactoryBean  schedulerFactoryBean)中 可对 SchedulerFactoryBean  使用代码自定义配置

所以 我们只要在SchedulerFactoryBean 创建后调用初始化方法之前,再将DataSource设置为null,那么SchedulerFactoryBean 初始化时,将会使用我们配置文件中的JobStoreTX去寻找我们配置的数据源了,至此,填坑完毕▼

import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

/**
 * @author Demo-Liu
 * @create 2020-06-12 11:20
 * @description 配置定时任务
 */
@Configuration
public class SchedulerConfig implements SchedulerFactoryBeanCustomizer {

    /**
     * @Author Demo-Liu
     * @Date 20200614 12:44
     * 自定义 quartz配置
     * @param schedulerFactoryBean
     */
    @Override
    public void customize(SchedulerFactoryBean schedulerFactoryBean) {
        schedulerFactoryBean.setDataSource(null);
    }

}

以上

在文末附上我的GitHub小demo,其中包含了quartz的数据库建表sql,并提供了一种可以更加灵活便捷的通过yml文件配置定时任务的方式  地址: GitHub-BootQuartzYml

以下是yml配置文件配置定时任务的例子

#通过加载此配置文件实现动态创建Job 旨在通过一种更灵活便捷的方式来控制定时任务
#20200611 by Demo-Liu
#jobs:
#  jobList:
#    - jobConf:
#        name: 测试任务                             #任务名 可选
#        job: com.example.demo.quartz.DemoJob  #任务类包路径 必须
#        param:                                     #可为job类注入参数(可配置多项)   可选
#          jtbs: test
#        cron: 10 * * * * ?                         #任务执行频率 必须
#        active: true                               #任务激活状态 必须
jobs:
  jobList:
    - jobConf:
        name: 测试任务
        job: com.example.demo.quartz.DemoJob
        param:
          jtbs: test
          ss: test2
        cron: 0/10 * * * * ?
        active: true
    - jobConf:
        name: 测试任务2
        job: com.example.demo.quartz.DemoJob2
        param:
          jtbs: test
          ss: test2
        cron: 0/10 * * * * ?
        active: false

 

欢迎关注我的微信公众号 "抓几个娃",只分享干货

 

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页