天天看點

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的相關知識點和問題處理就到此結束了,如有不對,請及時留言。