最近有個小項目要做,spring mvc下的task設定一直不太靈活,是以在Spring Boot上想做到靈活的管理定時任務。需求就是,當項目啟動的時候,如果有定時任務則加載進來,生成scheduler,通過背景表配置可以随時更新定時任務狀态(啟動、更改、删除)。
<!-- spring's support for quartz -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!--quartz-->
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
一個是Spring架構的支援,一個是Quartz的依賴,有的部落格會加上quartz-jobs,在目前示例中沒有用到,這裡不做添加。
application.properties增加參數
#quartz enabled 設定在目前項目是否運作quartz定時任務
quartz.enabled=true
增加quartz配置檔案quartz.properties
# thread-pool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=2
# job-store
org.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
@NoArgsConstructor
public 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;
public interface JobConfigRepository extends JpaRepository<JobConfig, Integer> {
List<JobConfig> findAllByStatus(int status);
Service
調用Repository的方法,提供查詢,JobConfigService
import com.example.demo.repositories.JobConfigRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class JobConfigService {
@Autowired
private JobConfigRepository jobConfigRepository;
public List<JobConfig> findAllByStatus(Integer status) {
return jobConfigRepository.findAllByStatus(status);
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的增删改功能以及JobDetail、CronTrigger的建立,需要注意,這裡的資料都是源于JobConfig這個表,name是FullEntity+Id拼接而成的。具體看代碼可知:
import com.example.demo.config.AutoWiringSpringBeanJobFactory;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
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
public static Boolean modifyScheduler(JobConfig oldConfig,JobConfig config,
if (oldConfig == null || config == null || context == null) {
String oldJobClassStr = oldConfig.getFullEntity();
String oldName = oldJobClassStr + oldConfig.getId();
String oldGroupName = oldConfig.getGroupName();
//1、清除舊的定時任務
delete(oldName, oldGroupName);
//2、建立新的定時任務
} catch (SchedulerException e) {
* 提取的删除任務的方法
* @param oldName
* @param oldGroupName
* @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;
* 提取出的建立定時任務的方法
private static Boolean create(JobConfig config, ApplicationContext context) {
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) {
Trigger trigger = createCronTrigger(jobDetail,
time, name, groupName, description);
if (trigger == null) {
jobFactory.setApplicationContext(context);
schedulerFactoryBean.setJobFactory(jobFactory);
schedulerFactoryBean.setJobDetails(jobDetail);
schedulerFactoryBean.setTriggers(trigger);
schedulerFactoryBean.afterPropertiesSet();
Scheduler scheduler = schedulerFactoryBean.getScheduler();
if (!scheduler.isShutdown()) {
scheduler.start();
} catch (ClassNotFoundException e) {
* 根據指定的參數,建立JobDetail
* @param clazz
* @param name
* @param groupName
* @param description
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
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);
factoryBean.afterPropertiesSet();
} catch (ParseException e) {
return factoryBean.getObject();
通過Spring Boot的@Configuration及@ConditionalOnExpression(“‘${quartz.enabled}’==’true’”)實作初始化時是否附加元件目的定時任務——SchedulerConfig,這裡的參數quartz.enabled的值即是我們上面在配置檔案裡配置的。代碼如下:
package com.example.demo.config;
import com.example.demo.service.JobConfigService;
import com.example.demo.util.SchedulerUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnExpression("'${quartz.enabled}'=='true'")
public class SchedulerConfig {
Logger logger = LoggerFactory.getLogger(getClass());
private ApplicationContext applicationContext;
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) {
Boolean flag = SchedulerUtil.createScheduler(
config, applicationContext);
System.out.println("執行結果:" + (flag == true ? "成功" : "失敗"));
return stdSchedulerFactory;
這裡定義了一個簡單的Job繼承org.quartz.Job,主要是查詢目前的定時任務表配置資料,MyJob,具體代碼如下:
package com.example.demo.jobs;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
@Component
public class MyJob implements Job {
public void execute(JobExecutionContext context) {
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...");
System.out.println(config.toString());
表建立
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=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...
到這裡,Spring Boot與quartz的整合已經完成了,可以通過配置表job_config以及配置quartz.enabled參數來靈活使用定時任務了!
後續還會繼續實踐、豐富這個示例,如果上文有什麼問題,歡迎留言指正,謝謝!
定時任務的路還有很長,想更靈活?可能需要elastic-job等架構吧!歡迎留言交流!
http://www.icnws.com/2017/145-spring-boot-quartz-editable/