天天看点

SpringBoot整合Quartz任务调度框架的使用SpringBoot整合Quartz任务调度框架的使用

SpringBoot整合Quartz任务调度框架的使用

Quartz框架是一个可以执行定时任务的框架,虽然spring也有提供定时功能,但功能不够强大,使用的不是很多。所谓的任务调度其实就是定时器,跟你设定一个闹钟在什么时间做什么事情一样。在我们的业务中,经常使用来做订单的超时判断,比如你下订单成功后30分钟没支付就会显示订单失效,这就是使用了定时功能去检查订单的时间。

Demo

完整版demo的项目结构图,先放在前面,以免部分人看不懂写的类该放到哪里去

SpringBoot整合Quartz任务调度框架的使用SpringBoot整合Quartz任务调度框架的使用

使用基本步骤

使用Quartz的三大步骤:

1)Job - 任务:你要做什么事

2)Trigger - 触发器 - 你什么时候去做

3) Scheduler - 任务调度 - 你什么时候需要去做什么事

创建工程

使用springboot创建maven工程,在现在新版本中(我的是2.3.2)springboot整合了Quartz相关的资源,所以我们只需要加一个starter就行了

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
           

创建任务类

定时任务就是需要做的事情,创建Demo 类,继承QuartzJobBean,重写executeInternal方法

package com.wangshili.quartz;

import java.util.Date;

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 com.wangshili.service.UserService;

/**
 * 需要继承QuartzJobBean重写里面的方法
 * @author wangshili
 *
 */
public class Demo extends QuartzJobBean{

	/**
	 * 要执行的具体内容
	 */
	@Override
	protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
		// TODO Auto-generated method stub
				System.out.println(new Date());
	}

}
           

创建Quartz配置类

@Configuration
public class QuartzDemoConfig {

	//1.创建任务
	@Bean
	public JobDetail orderjobDetail() {
		//指定job的名称和持久化保存任务
		return JobBuilder
				.newJob(Demo.class)	//引入自定义的任务
				.withIdentity("demo")//指定任务名称,随意
				.storeDurably()
				.build();
	}
	//2.定义触发器
	@Bean
	public Trigger orderTrigger(JobDetail  jobDetail ) throws IOException {
		SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
				.withRepeatCount(5)  //执行次数是从0开始计算的
				.withIntervalInMilliseconds(1000); //设定间隔时间。1000ms

		//0 0/1 * * * ? 时间表达式 规定任务多久执行一次
//		CronScheduleBuilder scheduleBuilder 
//		= CronScheduleBuilder.cronSchedule("0/2 * * * * ?");
		return TriggerBuilder
				.newTrigger()
				.forJob(orderjobDetail())
				.withIdentity("demo")  //指定触发器随意
				.withSchedule(scheduleBuilder).build();
	}
	

	/**
	 * 3.任务调度 ,创建scheduler
	 * @throws IOException 
	 */

	@Bean
	public SchedulerFactoryBean schedulerFactoryBean(@Autowired Trigger orderTrigger,
			@Autowired SpringJobFactory springJobFactory) throws IOException {
		SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
		schedulerFactoryBean.setJobDetails(orderjobDetail());
		schedulerFactoryBean.setTriggers(orderTrigger);
		return schedulerFactoryBean;
	}

}
           

启动后就可以看到打印输出的日期, 间隔1s,打印6次。

解决注入异常,空指针问题

在业务中不只是这么简单的打印,还要执行我们的业务代码,使用到自动注入相关的代码,所以这里我们先创建业务类

@Service
public class UserService {
	
	public void user() {
		System.out.println("user");
	}
}

           

改造Demo类

/**
 * 需要继承job重写里面的方法
 * @author wangshili
 *
 */

public class Demo extends QuartzJobBean{

	//quartz中的Job是在quartz中实例化出来的,不受spring的管理,所以就导致注入不进去了。
	@Autowired
	private UserService userService;
	/**
	 * 要执行的具体内容
	 */
	@Override
	protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
		// TODO Auto-generated method stub

				System.out.println(new Date());
				userService.user();
	}

}

           

这里直接运行项目就会出现一个问题,无法注入,在执行userService.user();时会报空指针异常,原因很简单,quartz中的Job是在quartz中实例化出来的,不受spring的管理,所以就导致注入不进去了。

所以我们要重写jobFactory,创建一个类去继承AdaptableJobFactory,重写createJobInstance方法,如下:

这里面的代码可以直接复制过去用就可以了。

/**
 * 解决spring bean注入Job的问题
 */
@Component  
public class SpringJobFactory extends AdaptableJobFactory  {       
    @Autowired    
    private AutowireCapableBeanFactory capableBeanFactory;    

    @Override    
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {    
        // 调用父类的方法    
        Object jobInstance = super.createJobInstance(bundle);    
        // 进行注入    
        capableBeanFactory.autowireBean(jobInstance);    
        return jobInstance;    
    }    
}  

           

只是添加这个类就可以了吗?当然还没,你得把它设置到调度器中,改造QuartzDemoConfig配置类,如下,在之前的代码上添加加粗的地方就可以了:

@Bean

public SchedulerFactoryBean schedulerFactoryBean(@Autowired Trigger orderTrigger,

@Autowired SpringJobFactory springJobFactory) throws IOException {

SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();

schedulerFactoryBean.setJobDetails(orderjobDetail());

schedulerFactoryBean.setTriggers(orderTrigger);

//注意这里需要将我们重写的jobFactory加入才能生效

schedulerFactoryBean.setJobFactory(springJobFactory);

return schedulerFactoryBean;

}

重新运行项目就可以正常的执行了。

任务持久化

我们先来了解下,Quartz提供两种基本作业存储类型:

—>第一种类型叫做RAMJobStore:

优点:最佳的性能,因为内存中数据访问最快

缺点:不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失

—>第二种类型叫做JDBC作业存储:

优点:保证信息不丢失,即使服务器宕机,重启后仍然能接着完成剩下的任务

缺点:性能差一些,需要操作数据库

通常为了保证我们的任务信息持久化,会采用JDBC的方式作业,我们接下来通过改造我们的项目来熟悉下。

创建数据库

通过上面的描述,持久化是在数据库中的,所以肯定要创建数据库,而这个数据库官方有给,我们需要去下载,http://www.quartz-scheduler.org/downloads/,随便下载一个版本,尽量匹配当前项目中使用的吧,比如我们这个项目是2.3.2的,下载个2.3.1的就行。

SpringBoot整合Quartz任务调度框架的使用SpringBoot整合Quartz任务调度框架的使用

下载后是一个tar.gz文件,直接解压得到tar文件,再解压tar就可以解压出来了

SpringBoot整合Quartz任务调度框架的使用SpringBoot整合Quartz任务调度框架的使用

进入\quartz-2.3.1-SNAPSHOT\src\org\quartz\impl\jdbcjobstore这个目录

SpringBoot整合Quartz任务调度框架的使用SpringBoot整合Quartz任务调度框架的使用

可以看到很多的sql文件,这里我用的是mysql数据库,所以选择对应的就行,然后去数据库创建数据库(sql里面没有创建数据库的语句,需要自己创建),在执行导入sql文件就行了,这里我数据库起名quartz

SpringBoot整合Quartz任务调度框架的使用SpringBoot整合Quartz任务调度框架的使用

创建 quartz.properties配置文件

在resources目录创建

SpringBoot整合Quartz任务调度框架的使用SpringBoot整合Quartz任务调度框架的使用
#使用自己的配置文件
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=druid
#数据库中quartz表的表名前缀
org.quartz.dataSource.druid.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.druid.URL=jdbc:mysql://localhost:3306/quartz?serverTimezone=GMT&characterEncoding=utf-8
org.quartz.dataSource.druid.user=root
org.quartz.dataSource.druid.password=root
#最大连接数
org.quartz.dataSource.druid.maxConnections=5
           

配置文件可以直接复制过去,需要注意的地方是数据库那块的配置,如果是mysql,那driver驱动不用改,需要注意ip和端口和数据库名,用户名和密码都要匹配你自己的。

添加c3p0坐标

因为Quartz默认使用的是c3p0连接池,所以pom文件需要添加对应的坐标

<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
<dependency>
	<groupId>c3p0</groupId>
	<artifactId>c3p0</artifactId>
	<version>0.9.1.2</version>
</dependency>
           

改造配置类

要想配置生效,还得进行配置,配置我们刚才的QuartzDemoConfig类,添加如下代码:

//指定quartz.properties
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        //填写刚刚创建的配置文件名
        propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }
           

修改SchedulerFactoryBean,增加schedulerFactoryBean.setQuartzProperties(quartzProperties());即可

/**
	 * 3.任务调度 ,创建scheduler
	 * @throws IOException 
	 */

	@Bean
	public SchedulerFactoryBean schedulerFactoryBean(@Autowired Trigger orderTrigger,
			@Autowired SpringJobFactory springJobFactory) throws IOException {
		SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
		schedulerFactoryBean.setJobDetails(orderjobDetail());
		schedulerFactoryBean.setTriggers(orderTrigger);
		//###增加此代码即可
		schedulerFactoryBean.setQuartzProperties(quartzProperties());
        //注意这里需要将我们重写的jobFactory加入才能生效
		schedulerFactoryBean.setJobFactory(springJobFactory);  
		return schedulerFactoryBean;
	}
           

这样就算完成了相关的配置

检验

运行项目,打印一半就停止项目,再启动,看看会不会继续执行剩下的打印次数

SpringBoot整合Quartz任务调度框架的使用SpringBoot整合Quartz任务调度框架的使用

图中已经打印了3次,因为我们设置的是5(包括0),那么还剩下3次,我们先看数据库的存储信息,在qrtz_simple_triggers表中,可以看到我们任务的信息,调度器名字、触发器名字、触发器组、目标次数、周期、已执行次数是3。所以我们恢复服务器后,就会根据这个表的内容继续执行我们剩下的任务

SpringBoot整合Quartz任务调度框架的使用SpringBoot整合Quartz任务调度框架的使用

重新启动可以发现,执行完3次后就不继续运行了,说明持久化成功。

SpringBoot整合Quartz任务调度框架的使用SpringBoot整合Quartz任务调度框架的使用

切换持久化的数据源

因为Quartz默认使用的是c3p0,如果你因业务需求或者喜好需要切换数据源比如druid,那么可以这样做

添加druid的pom坐标

<!-- druid连接池 -->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>1.1.12</version>
</dependency>
           

修改配置文件

在quartz.properties的配置文件中添加一个配置信息

后面的类全路径名可以先不用管,待会配置成你自己的

#切换数据源成druid,默认使用的是c3p0
org.quartz.dataSource.druid.connectionProvider.class =com.wangshili.config.DruidConnectionProvider 
           

添加配置类

要切换数据源必须重写ConnectionProvider 接口的方法,修改成druid的,具体配置类如下

/**
 * Druid连接池的Quartz扩展类
 */
public class DruidConnectionProvider implements ConnectionProvider {
     /*
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     *
     * 常量配置,与quartz.properties文件的key保持一致(去掉前缀),同时提供set方法,Quartz框架自动注入值。
     *
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    //JDBC驱动
    public String driver;
    //JDBC连接串
    public String URL;
    //数据库用户名
    public String user;
    //数据库用户密码
    public String password;
    //数据库最大连接数
    public int maxConnections;
    //数据库SQL查询每次连接返回执行到连接池,以确保它仍然是有效的。
    public String validationQuery;
    private boolean validateOnCheckout;
    private int idleConnectionValidationSeconds;
    public String maxCachedStatementsPerConnection;
    private String discardIdleConnectionsSeconds;
    public static final int DEFAULT_DB_MAX_CONNECTIONS = 10;
    public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120;
    //Druid连接池
    private DruidDataSource datasource;
    /*
    * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    *
    * 接口实现
    *
    * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    */
    public Connection getConnection() throws SQLException {
        return datasource.getConnection();
    }
    public void shutdown() throws SQLException {
        datasource.close();
    }
    public void initialize() throws SQLException{
        if (this.URL == null) {
            throw new SQLException("DBPool could not be created: DB URL cannot be null");
        }
        if (this.driver == null) {
            throw new SQLException("DBPool driver could not be created: DB driver class name cannot be null!");
        }
        if (this.maxConnections < 0) {
            throw new SQLException("DBPool maxConnectins could not be created: Max connections must be greater than zero!");
        }
        datasource = new DruidDataSource();
        try{
            datasource.setDriverClassName(this.driver);
        } catch (Exception e) {
            try {
                throw new SchedulerException("Problem setting driver class name on datasource: " + e.getMessage(), e);
            } catch (SchedulerException e1) {
            }
        }
        datasource.setUrl(this.URL);
        datasource.setUsername(this.user);
        datasource.setPassword(this.password);
        datasource.setMaxActive(this.maxConnections);
        datasource.setMinIdle(1);
        datasource.setMaxWait(0);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(DEFAULT_DB_MAX_CONNECTIONS);
        if (this.validationQuery != null) {
            datasource.setValidationQuery(this.validationQuery);
            if(!this.validateOnCheckout)
                datasource.setTestOnReturn(true);
            else
                datasource.setTestOnBorrow(true);
            datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
        }
    }
    /*
    * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    *
    * 提供get set方法
    *
    * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    */
    public String getDriver() {
        return driver;
    }
    public void setDriver(String driver) {
        this.driver = driver;
    }
    public String getURL() {
        return URL;
    }
    public void setURL(String URL) {
        this.URL = URL;
    }
    public String getUser() {
        return user;
    }
    public void setUser(String user) {
        this.user = user;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public int getMaxConnections() {
        return maxConnections;
    }
    public void setMaxConnections(int maxConnections) {
        this.maxConnections = maxConnections;
    }
    public String getValidationQuery() {
        return validationQuery;
    }
    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }
    public boolean isValidateOnCheckout() {
        return validateOnCheckout;
    }
    public void setValidateOnCheckout(boolean validateOnCheckout) {
        this.validateOnCheckout = validateOnCheckout;
    }
    public int getIdleConnectionValidationSeconds() {
        return idleConnectionValidationSeconds;
    }
    public void setIdleConnectionValidationSeconds(int idleConnectionValidationSeconds) {
        this.idleConnectionValidationSeconds = idleConnectionValidationSeconds;
    }
    public DruidDataSource getDatasource() {
        return datasource;
    }
    public void setDatasource(DruidDataSource datasource) {
        this.datasource = datasource;
    }
    public String getDiscardIdleConnectionsSeconds() {
        return discardIdleConnectionsSeconds;
    }
    public void setDiscardIdleConnectionsSeconds(String discardIdleConnectionsSeconds) {
        this.discardIdleConnectionsSeconds = discardIdleConnectionsSeconds;
    }
}
           

完结

关于SpringBoot整合Quartz的相关知识点和问题处理就到此结束了,如有不对,请及时留言。