最近有個小項目要做,spring mvc下的task設置一直不太靈活,因此在Spring Boot上想做到靈活的管理定時任務。需求就是,當項目啟動的時候,如果有定時任務則加載進來,生成scheduler,通過后台表配置可以隨時更新定時任務狀態(啟動、更改、刪除)。
添加依賴
<!-- spring's support for quartz -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!--quartz-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
|
一個是Spring框架的支持,一個是Quartz的依賴,有的博客會加上quartz-jobs,在當前示例中沒有用到,這里不做添加。
調整配置
-
application.properties增加參數
#quartz enabled 設置在當前項目是否運行quartz定時任務quartz.enabled=true -
增加quartz配置文件quartz.properties
# thread-poolorg.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPoolorg.quartz.threadPool.threadCount=2# job-storeorg.quartz.jobStore.class=org.quartz.simpl.RAMJobStore這些參數可以不設置,有一些默認值,threadCount的默認值是10,spring mvc下定時任務的默認值是1,所以如果某個定時任務卡住了,肯會影響其后的多個定時任務的執行。
任務表配置
-
Entity
實體類,這里是JobConfig,這里沒有做過多的設計,只是實現了cron類型的定時任務及任務狀態,fullEntity是執行任務的類全名,如我們用的com.example.demo.jobs.MyJob
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import java.util.Date;/*** Created by Administrator on 2017/8/25.*/@Entity@Data@AllArgsConstructor@NoArgsConstructorpublic class JobConfig {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Integer id;private String name;private String fullEntity;private String groupName;private String cronTime;private Integer status;private Date createAt;private Date updateAt;}代碼沒有set/get是因為使用了lombok,@Data注解實現set/get/toString等工作
-
Repository
這里主要是定義了一個根據定時任務狀態獲取對應的定時任務的方法,JobConfigRepository
import com.example.demo.dto.JobConfig;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* Created by Administrator on 2017/8/25.
*/
public interface JobConfigRepository extends JpaRepository<JobConfig, Integer> {
List<JobConfig> findAllByStatus(int status);
}
|
-
Service
調用Repository的方法,提供查詢,JobConfigService
import com.example.demo.dto.JobConfig;
import com.example.demo.repositories.JobConfigRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Created by Administrator on 2017/8/25.
*/
@Service
public class JobConfigService {
@Autowired
private JobConfigRepository jobConfigRepository;
public List<JobConfig> findAllByStatus(Integer status) {
return jobConfigRepository.findAllByStatus(status);
}
}
|
添加自動注入支持
源於https://gist.github.com/jelies/5085593的解決方案,解決的問題就是在org.quartz.Job的子類里無法直接使用Service等依賴,具體如下:
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
/**
* Adds auto-wiring support to quartz jobs.
* @see "https://gist.github.com/jelies/5085593"
*/
public final class AutoWiringSpringBeanJobFactory extends SpringBeanJobFactory
implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
|
Scheduler工具類
實現Scheduler的增刪改功能以及JobDetail、CronTrigger的創建,需要注意,這里的數據都是源於JobConfig這個表,name是FullEntity+Id拼接而成的。具體看代碼可知:
import com.example.demo.config.AutoWiringSpringBeanJobFactory;
import com.example.demo.dto.JobConfig;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import java.text.ParseException;
public class SchedulerUtil {
//定時任務Scheduler的工廠類,Quartz提供
private static StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
//CronTrigger的工廠類
private static CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
//JobDetail的工廠類
private static JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
//自動注入Spring Bean的工廠類
private static AutoWiringSpringBeanJobFactory jobFactory =
new AutoWiringSpringBeanJobFactory();
//定時任務Scheduler的工廠類,Spring Framework提供
private static SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
static {
//加載指定路徑的配置
schedulerFactoryBean.setConfigLocation(new ClassPathResource("quartz.properties"));
}
/**
* 創建定時任務,根據參數,創建對應的定時任務,並使之生效
* @param config
* @param context
* @return
*/
public static boolean createScheduler(JobConfig config,
ApplicationContext context) {
try {
//創建新的定時任務
return create(config, context);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 刪除舊的定時任務,創建新的定時任務
* @param oldConfig
* @param config
* @param context
* @return
*/
public static Boolean modifyScheduler(JobConfig oldConfig,JobConfig config,
ApplicationContext context) {
if (oldConfig == null || config == null || context == null) {
return false;
}
try {
String oldJobClassStr = oldConfig.getFullEntity();
String oldName = oldJobClassStr + oldConfig.getId();
String oldGroupName = oldConfig.getGroupName();
//1、清除舊的定時任務
delete(oldName, oldGroupName);
//2、創建新的定時任務
return create(config, context);
} catch (SchedulerException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 提取的刪除任務的方法
* @param oldName
* @param oldGroupName
* @return
* @throws SchedulerException
*/
private static Boolean delete(String oldName, String oldGroupName)
throws SchedulerException {
TriggerKey key = new TriggerKey(oldName, oldGroupName);
Scheduler oldScheduler = schedulerFactory.getScheduler();
//根據TriggerKey獲取trigger是否存在,如果存在則根據key進行刪除操作
Trigger keyTrigger = oldScheduler.getTrigger(key);
if (keyTrigger != null) {
oldScheduler.unscheduleJob(key);
}
return true;
}
/**
* 提取出的創建定時任務的方法
* @param config
* @param context
* @return
*/
private static Boolean create(JobConfig config, ApplicationContext context) {
try {
//創建新的定時任務
String jobClassStr = config.getFullEntity();
Class clazz = Class.forName(jobClassStr);
String name = jobClassStr + config.getId();
String groupName = config.getGroupName();
String description = config.toString();
String time = config.getCronTime();
JobDetail jobDetail = createJobDetail(clazz, name, groupName, description);
if (jobDetail == null) {
return false;
}
Trigger trigger = createCronTrigger(jobDetail,
time, name, groupName, description);
if (trigger == null) {
return false;
}
jobFactory.setApplicationContext(context);
schedulerFactoryBean.setJobFactory(jobFactory);
schedulerFactoryBean.setJobDetails(jobDetail);
schedulerFactoryBean.setTriggers(trigger);
schedulerFactoryBean.afterPropertiesSet();
Scheduler scheduler = schedulerFactoryBean.getScheduler();
if (!scheduler.isShutdown()) {
scheduler.start();
}
return true;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SchedulerException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 根據指定的參數,創建JobDetail
* @param clazz
* @param name
* @param groupName
* @param description
* @return
*/
public static JobDetail createJobDetail(Class clazz, String name,
String groupName, String description) {
jobDetailFactory.setJobClass(clazz);
jobDetailFactory.setName(name);
jobDetailFactory.setGroup(groupName);
jobDetailFactory.setDescription(description);
jobDetailFactory.setDurability(true);
jobDetailFactory.afterPropertiesSet();
return jobDetailFactory.getObject();
}
/**
* 根據參數,創建對應的CronTrigger對象
*
* @param job
* @param time
* @param name
* @param groupName
* @param description
* @return
*/
public static CronTrigger createCronTrigger(JobDetail job, String time,
String name, String groupName, String description) {
factoryBean.setName(name);
factoryBean.setJobDetail(job);
factoryBean.setCronExpression(time);
factoryBean.setDescription(description);
factoryBean.setGroup(groupName);
try {
factoryBean.afterPropertiesSet();
} catch (ParseException e) {
e.printStackTrace();
}
return factoryBean.getObject();
}
}
|
Scheduler初始化配置
通過Spring Boot的@Configuration及@ConditionalOnExpression(“‘${quartz.enabled}’==’true’”)實現初始化時是否加載項目的定時任務——SchedulerConfig,這里的參數quartz.enabled的值即是我們上面在配置文件里配置的。代碼如下:
package com.example.demo.config;
import com.example.demo.dto.JobConfig;
import com.example.demo.service.JobConfigService;
import com.example.demo.util.SchedulerUtil;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
@ConditionalOnExpression("'${quartz.enabled}'=='true'")
public class SchedulerConfig {
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ApplicationContext applicationContext;
@Autowired
private JobConfigService jobConfigService;
@Bean
public StdSchedulerFactory stdSchedulerFactory() {
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
//獲取JobConfig集合
List<JobConfig> configs = jobConfigService.findAllByStatus(1);
logger.debug("Setting the Scheduler up");
for (JobConfig config : configs) {
try {
Boolean flag = SchedulerUtil.createScheduler(
config, applicationContext);
System.out.println("執行結果:" + (flag == true ? "成功" : "失敗"));
} catch (Exception e) {
e.printStackTrace();
}
}
return stdSchedulerFactory;
}
}
|
Job實現
這里定義了一個簡單的Job繼承org.quartz.Job,主要是查詢當前的定時任務表配置數據,MyJob,具體代碼如下:
package com.example.demo.jobs;
import com.example.demo.dto.JobConfig;
import com.example.demo.service.JobConfigService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@Component
public class MyJob implements Job {
@Autowired
private JobConfigService jobConfigService;
public void execute(JobExecutionContext context) {
System.out.println();
System.out.println();
//是哪個定時任務配置在執行,可以看到,因為在前面我們將描述設置為了配置類的toString結果
System.out.println(context.getJobDetail().getDescription());
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(this.toString() + ":" + f.format(new Date()) +
"正在執行Job executing...");
List<JobConfig> configs = jobConfigService.findAllByStatus(1);
for (JobConfig config : configs) {
System.out.println(config.toString());
}
}
}
|
數據庫表job_config
- 表創建
CREATE TABLE `job_config` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`create_at` DATETIME DEFAULT NULL,
`cron_time` VARCHAR(255) DEFAULT NULL,
`full_entity` VARCHAR(255) DEFAULT NULL,
`group_name` VARCHAR(255) DEFAULT NULL,
`name` VARCHAR(255) DEFAULT NULL,
`status` INT(11) DEFAULT NULL,
`update_at` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`)
)
ENGINE = InnoDB
AUTO_INCREMENT = 3
DEFAULT CHARSET = utf8;
|
- 表數據
/*Data for the table `job_config` */
INSERT INTO `job_config` (`id`, `create_at`, `cron_time`, `full_entity`, `group_name`, `name`, `status`, `update_at`)
VALUES (1, '2017-08-25 21:03:35', '0/8 * * * * ?', 'com.example.demo.jobs.MyJob', 'test', 'My test', 1, NULL),
(2, '2017-08-25 21:12:02', '0/23 * * * * ?', 'com.example.demo.jobs.MyJob', 'test', 'My Job', 1, NULL);
|
運行結果
JobConfig(id=1, name=My test, fullEntity=com.example.demo.jobs.MyJob, groupName=test, cronTime=0/8 * * * * ?, status=1, createAt=2017-08-25 21:03:35.0, updateAt=null)
com.example.demo.jobs.MyJob@7602fe69:2017-08-26 10:43:16正在執行Job executing...
Hibernate: select jobconfig0_.id as id1_1_, jobconfig0_.create_at as create_a2_1_, jobconfig0_.cron_time as cron_tim3_1_, jobconfig0_.full_entity as full_ent4_1_, jobconfig0_.group_name as group_na5_1_, jobconfig0_.name as name6_1_, jobconfig0_.status as status7_1_, jobconfig0_.update_at as update_a8_1_ from job_config jobconfig0_ where jobconfig0_.status=?
JobConfig(id=1, name=My test, fullEntity=com.example.demo.jobs.MyJob, groupName=test, cronTime=0/8 * * * * ?, status=1, createAt=2017-08-25 21:03:35.0, updateAt=null)
JobConfig(id=2, name=My Job, fullEntity=com.example.demo.jobs.MyJob, groupName=test, cronTime=0/23 * * * * ?, status=1, createAt=2017-08-25 21:12:02.0, updateAt=null)
JobConfig(id=2, name=My Job, fullEntity=com.example.demo.jobs.MyJob, groupName=test, cronTime=0/23 * * * * ?, status=1, createAt=2017-08-25 21:12:02.0, updateAt=null)
com.example.demo.jobs.MyJob@4a49530:2017-08-26 10:43:23正在執行Job executing...
Hibernate: select jobconfig0_.id as id1_1_, jobconfig0_.create_at as create_a2_1_, jobconfig0_.cron_time as cron_tim3_1_, jobconfig0_.full_entity as full_ent4_1_, jobconfig0_.group_name as group_na5_1_, jobconfig0_.name as name6_1_, jobconfig0_.status as status7_1_, jobconfig0_.update_at as update_a8_1_ from job_config jobconfig0_ where jobconfig0_.status=?
JobConfig(id=1, name=My test, fullEntity=com.example.demo.jobs.MyJob, groupName=test, cronTime=0/8 * * * * ?, status=1, createAt=2017-08-25 21:03:35.0, updateAt=null)
JobConfig(id=2, name=My Job, fullEntity=com.example.demo.jobs.MyJob, groupName=test, cronTime=0/23 * * * * ?, status=1, createAt=2017-08-25 21:12:02.0, updateAt=null)
|
到這里,Spring Boot與quartz的整合已經完成了,可以通過配置表job_config以及配置quartz.enabled參數來靈活使用定時任務了!
后續還會繼續實踐、豐富這個示例,如果上文有什么問題,歡迎留言指正,謝謝!
源碼:https://github.com/icnws/spring-data-jpa-demo
定時任務的路還有很長,想更靈活?可能需要elastic-job等框架吧!歡迎留言交流!
http://www.icnws.com/2017/145-spring-boot-quartz-editable/