1. 程式人生 > >Quartz:基本用法總結

Quartz:基本用法總結

OpenSymphony所提供的Quartz是任務排程領域享譽盛名的開源框架。Spring提供了整合Quartz的功能,可以讓開發人員以更面向Spring的方式建立基於Quartz的任務排程應用。任務排程本身設計多執行緒併發、執行時間規則制定及解析、執行現場保持與恢復、執行緒池維護等諸多方面的工作。如果以自定義執行緒池的原始方法開發,難點很大。 ## 1.普通JAVA任務 啟動基本的Quartz任務包含一下流程: 1. 建立任務類:實現Job介面的void execute(JobExecutionContext context)方法,定義被執行任務的執行邏輯; 2. 生成JobDetail物件:通過載入任務類(不是例項)來繫結任務邏輯與任務資訊; 3. 生成Trigger物件:定時器的觸發時間有兩種方式可以定義,分別是CronSchedule和simpleSchedule()。前者使用正則表示式,後者則是簡單封裝後的定時器。 4. 獲取Scheduler物件:通過`StdSchedulerFactory`工廠方法初始化scheduler物件,把任務和定時器繫結在一起,並啟動任務。 ### 完整例項程式碼 ```java import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; public class RAMQuartz { private static Logger logger = LoggerFactory.getLogger(RAMQuartz.class); public static void main(String[] args) throws SchedulerException { //建立scheduler SchedulerFactory sf = new StdSchedulerFactory(); Scheduler scheduler = sf.getScheduler(); //定義一個JobDetail //定義Job類為RAMJob類,這是真正的執行邏輯所在 JobDetail jb = JobBuilder.newJob(RAMJob1.class) .withDescription("this is a ram job") .withIdentity("ramJob", "ramGroup")//定義name/group .build(); //通過JobDataMap傳遞引數 jb.getJobDataMap().put("Test", "This is test parameter value"); long time = System.currentTimeMillis() + 3*1000L; Date startTime = new Date(time); //定義一個Trigger Trigger trigger = TriggerBuilder.newTrigger() .withDescription("") .withIdentity("ramTrigger", "ramTriggerGroup")//定義name/group .startAt(startTime)//加入scheduler後,在指定時間啟動 //使用CronTrigger .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) .build(); //繫結任務和定時器到排程器 scheduler.scheduleJob(jb,trigger); //啟動 scheduler.start(); logger.info("啟動時間 : " + new Date()); } } ``` ```java import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.util.Date; public class RAMJob1 implements Job{ private static Logger logger = LoggerFactory.getLogger(RAMJob.class); @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { try { JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap(); String str = dataMap.getString("Test"); logger.info("Quartz dataMap : " + new Date() + "\n" + str); } catch (Exception e) { e.printStackTrace(); } } } ``` ## 2.物件注入 在Spring的WEB應用中使用定時器,通常都會用到spring的特性——物件注入。前面的程式碼雖然能夠很好地執行簡單的定時器任務,但是遇到複雜的執行邏輯(如資料庫讀寫等),就不能應付了。 下面程式碼可以看出,任務2需要執行myBatis的資料庫插入語句: ```java public class RAMJob2 implements Job{ @Autowired private TestQuartzMapper testQuartzMapper; private static Logger logger = LoggerFactory.getLogger(RAMJob.class); @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { try { testQuartzMapper.insertSelective(testQuartz); logger.info("Insert MyBatis Success!"); } catch (Exception e) { e.printStackTrace(); } } } ``` 執行這個業務邏輯,就不得不注入物件。如果仍然延用上面的方法,我們會發現執行的時候,testQuartzMapper的物件為null,結果自然毫無懸念地不斷報錯。 如何為我們的定時器注入Spring的物件,下面介紹一下思路: 1. 自定義`JobFactory`工廠方法,擴充套件`AdaptableJobFactory`,重寫其`createJobInstance`方法; 2. 宣告`SchedulerFactoryBean`,傳入自定義的`JobFactory`工廠方法; 3. 通過新的`SchedulerFactoryBean`獲取`scheduler`例項,用注入的方式在需要的地方使用。 ### 完整示例 ```java import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.scheduling.quartz.AdaptableJobFactory; import org.springframework.stereotype.Component; @Component public class MyJobFactory extends AdaptableJobFactory { @Autowired private AutowireCapableBeanFactory capableBeanFactory; @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { // 呼叫父類的方法 Object jobInstance = super.createJobInstance(bundle); // 進行注入 capableBeanFactory.autowireBean(jobInstance); return jobInstance; } } ``` ```java @Configuration public class QuartzConfig { @Autowired private MyJobFactory myJobFactory; @Bean public SchedulerFactoryBean schedulerFactoryBean() throws IOException { SchedulerFactoryBean factory = new SchedulerFactoryBean(); // 載入quartz資料來源配置 factory.setQuartzProperties(quartzProperties()); // 自定義Job Factory,用於Spring注入 factory.setJobFactory(myJobFactory); return factory; } @Bean public Scheduler scheduler() throws IOException, SchedulerException { Scheduler scheduler = schedulerFactoryBean().getScheduler(); scheduler.start(); return scheduler; } } ``` ### 3.Spring簡單任務 Spring對Quartz進行了封裝,方便開發者呼叫。下面以Spring Boot為例,介紹一下簡單任務在Spring的執行方式。 #### 任務類定義 仔細觀察可以發現,與普通Java任務的區別在於使用了@Component和@EnableScheduling的註釋,相應的,就不用宣告`implements Job`,以及重寫`execute`方法。這是Spring提供的一種便利。 ```java @Component @EnableScheduling public class SpringJob { @Autowired WriteService writeService; private Logger logger = LoggerFactory.getLogger(this.getClass()); public void myJobBusinessMethod() { this.logger.info("MyFirstExerciseJob哇被觸發了哈哈哈哈哈"); writeService.writeMSG("張三"); } } ``` ## 配置JobDetail和Trigger的Bean `MethodInvokingJobDetailFactoryBean`是Spring提供的JobDetail工廠方法,使用它可以快速地定義JobDetail。然而,缺點是生成的任務無法持久化儲存,也就是說,無法管理任務的啟動、暫停、恢復、停止等操作。 `CronTriggerFactoryBean`為表示式型觸發器。 ```java @Configuration public class QuartzJobConfig { /** * 方法呼叫任務明細工廠Bean */ @Bean(name = "SpringJobBean") public MethodInvokingJobDetailFactoryBean myFirstExerciseJobBean(SpringJob springJob) { MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean(); jobDetail.setConcurrent(false); // 是否併發 jobDetail.setName("general-springJob"); // 任務的名字 jobDetail.setGroup("general"); // 任務的分組 jobDetail.setTargetObject(springJob); // 被執行的物件 jobDetail.setTargetMethod("myJobBusinessMethod"); // 被執行的方法 return jobDetail; } /** * 表示式觸發器工廠Bean */ @Bean(name = "SpringJobTrigger") public CronTriggerFactoryBean myFirstExerciseJobTrigger(@Qualifier("SpringJobBean") MethodInvokingJobDetailFactoryBean springJobBean) { CronTriggerFactoryBean tigger = new CronTriggerFactoryBean(); tigger.setJobDetail(springJobBean.getObject()); tigger.setCronExpression("0/10 * * * * ?"); // 什麼是否觸發,Spring Scheduler Cron表示式 tigger.setName("general-springJobTrigger"); return tigger; } } ``` ### 排程器 下面將任務和觸發器註冊到排程器 ```java @Configuration public class QuartzConfig { /** * 排程器工廠Bean */ @Bean(name = "schedulerFactory") public SchedulerFactoryBean schedulerFactory(@Qualifier("SpringJobTrigger") Trigger springJobTrigger) { SchedulerFactoryBean bean = new SchedulerFactoryBean(); // 覆蓋已存在的任務 bean.setOverwriteExistingJobs(true); // 延時啟動定時任務,避免系統未完全啟動卻開始執行定時任務的情況 bean.setStartupDelay(15); // 註冊觸發器 bean.setTriggers(SpringJobTrigger); return bean; } } ``` 完成上述配置後,啟動spring boot就可以出發定時器任務了。而且,仔細觀察上面的程式碼,在執行過程中有`WriteService`的spring物件注入,而無需我們自己去自定義JobFactory的Spring物件。 ## 4.持久化 任務持久化需要用到資料庫,而初始化資料庫的SQL可以從下載的釋出版的檔案中找到,比如,我在官網的Download頁下載了當前版本的Full Distribution:`Quartz 2.2.3 .tar.gz`,解壓後在`quartz-2.2.3\docs\dbTables`能找到初始化指令碼,因我用的是MySQL的Innodb引擎,所以我用此指令碼`tables_mysql_innodb.sql`。 ### 配置 預設情況下,排程器的詳情資訊會被儲存在記憶體,模式為:`RAMJobStore `,而且也不需要填寫quartz.properties的配置。然而,如果是持久化的模式,那麼quartz.properties就必須填寫,因為檔案中制定了資訊儲存模式和資料來源資訊。 ``` # 執行緒排程器例項名 org.quartz.scheduler.instanceName = quartzScheduler # 執行緒池的執行緒數,即最多3個任務同時跑 org.quartz.threadPool.threadCount = 3 # 如何儲存任務和觸發器等資訊 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX # 驅動代理 org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 表字首 org.quartz.jobStore.tablePrefix = qrtz_ # 資料來源 org.quartz.jobStore.dataSource = quartzDataSource # 是否叢集 org.quartz.jobStore.isClustered = false # 資料來源 # 驅動 org.quartz.dataSource.quartzDataSource.driver = com.mysql.cj.jdbc.Driver # 連線URL org.quartz.dataSource.quartzDataSource.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8&useSSL=true&&serverTimezone=Asia/Shanghai # 使用者名稱 org.quartz.dataSource.quartzDataSource.user = root # 密碼 org.quartz.dataSource.quartzDataSource.password = 123456 # 最大連線數 org.quartz.dataSource.quartzDataSource.maxConnections = 5 ``` 其他內容和`RAMJobStore`模式