当前位置: 首页 > news >正文

Spring Boot整合Quartz实现定时任务表配置

最近有个小项目要做,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-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;
 
/**
* 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/

 

相关文章:

  • Arcgis for Js实现graphiclayer的空间查询(续)
  • 【树莓派】服务配置相关2:基于RPi Desktop的服务配置
  • (推荐)叮当——中文语音对话机器人
  • 睿仁医疗郑世斌:医疗智能硬件数据精度应放在第一位
  • 软件开发的流程
  • 理解.NET中的异常(二)
  • Spring 和 SpringMVC 常用注解
  • python并开发编程之协程
  • 探索我的项目管理之路之:软件质量的六个考核要素
  • 使用交换机实现数据单向访问控制
  • 为恶意文件“画像” 瀚思科技基于深度学习技术快速锁定未知威胁
  • 真实案例:一位网页开发者几乎毁掉一家小公司
  • 支付宝灾备能力为何引发争议
  • window对象的方法属性
  • Spark Mllib里的本地向量集(密集型数据集和稀疏型数据集概念、构成)(图文详解)...
  • Angular 4.x 动态创建组件
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • CSS实用技巧
  • echarts花样作死的坑
  • Java 多线程编程之:notify 和 wait 用法
  • JS+CSS实现数字滚动
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • nginx 配置多 域名 + 多 https
  • PHP的类修饰符与访问修饰符
  • php面试题 汇集2
  • Python 反序列化安全问题(二)
  • Rancher如何对接Ceph-RBD块存储
  • Redash本地开发环境搭建
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • SpringCloud(第 039 篇)链接Mysql数据库,通过JpaRepository编写数据库访问
  • Vue官网教程学习过程中值得记录的一些事情
  • 阿里云ubuntu14.04 Nginx反向代理Nodejs
  • 阿里云前端周刊 - 第 26 期
  • 第13期 DApp 榜单 :来,吃我这波安利
  • 分布式事物理论与实践
  • 力扣(LeetCode)22
  • 前端工程化(Gulp、Webpack)-webpack
  • 前端相关框架总和
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 推荐一个React的管理后台框架
  • 学习笔记:对象,原型和继承(1)
  • 因为阿里,他们成了“杭漂”
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • 昨天1024程序员节,我故意写了个死循环~
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • # 数论-逆元
  • #QT(智能家居界面-界面切换)
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • (2)Java 简介
  • (9)STL算法之逆转旋转
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (二)PySpark3:SparkSQL编程
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB