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

简介: 最近有个小项目要做,spring mvc下的task设置一直不太灵活,因此在Spring Boot上想做到灵活的管理定时任务。需求就是,当项目启动的时候,如果有定时任务则加载进来,生成scheduler,通过后台表配置可以随时更新定时任务状态(启动、更改、删除)。

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

 

相关文章
|
2月前
|
Java Spring
Spring Boot配置的优先级?
在Spring Boot项目中,配置可通过配置文件和外部配置实现。支持的配置文件包括application.properties、application.yml和application.yaml,优先级依次降低。外部配置常用方式有Java系统属性(如-Dserver.port=9001)和命令行参数(如--server.port=10010),其中命令行参数优先级高于系统属性。整体优先级顺序为:命令行参数 &gt; Java系统属性 &gt; application.properties &gt; application.yml &gt; application.yaml。
355 0
|
5月前
|
安全 Java API
深入解析 Spring Security 配置中的 CSRF 启用与 requestMatchers 报错问题
本文深入解析了Spring Security配置中CSRF启用与`requestMatchers`报错的常见问题。针对CSRF,指出默认已启用,无需调用`enable()`,只需移除`disable()`即可恢复。对于`requestMatchers`多路径匹配报错,分析了Spring Security 6.x中方法签名的变化,并提供了三种解决方案:分次调用、自定义匹配器及降级使用`antMatchers()`。最后提醒开发者关注版本兼容性,确保升级平稳过渡。
596 2
|
30天前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
391 10
|
6月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
745 0
|
2月前
|
人工智能 安全 Java
Spring Boot yml 配置敏感信息加密
本文介绍了如何在 Spring Boot 项目中使用 Jasypt 实现配置文件加密,包含添加依赖、配置密钥、生成加密值、在配置中使用加密值及验证步骤,并提供了注意事项,确保敏感信息的安全管理。
601 1
|
2月前
|
SQL XML Java
配置Spring框架以连接SQL Server数据库
最后,需要集成Spring配置到应用中,这通常在 `main`方法或者Spring Boot的应用配置类中通过加载XML配置或使用注解来实现。
172 0
|
4月前
|
监控 Java 调度
SpringBoot中@Scheduled和Quartz的区别是什么?分布式定时任务框架选型实战
本文对比分析了SpringBoot中的`@Scheduled`与Quartz定时任务框架。`@Scheduled`轻量易用,适合单机简单场景,但存在多实例重复执行、无持久化等缺陷;Quartz功能强大,支持分布式调度、任务持久化、动态调整和失败重试,适用于复杂企业级需求。文章通过特性对比、代码示例及常见问题解答,帮助开发者理解两者差异,合理选择方案。记住口诀:单机简单用注解,多节点上Quartz;若是任务要可靠,持久化配置不能少。
412 4
|
6月前
|
Java 数据库连接 数据库
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——MyBatis 介绍和配置
本文介绍了Spring Boot集成MyBatis的方法,重点讲解基于注解的方式。首先简述MyBatis作为持久层框架的特点,接着说明集成时的依赖导入,包括`mybatis-spring-boot-starter`和MySQL连接器。随后详细展示了`properties.yml`配置文件的内容,涵盖数据库连接、驼峰命名规范及Mapper文件路径等关键设置,帮助开发者快速上手Spring Boot与MyBatis的整合开发。
717 0
|
Java 调度 Maven
spring boot 2.0 quartz 轻松实现定时任务和作业调度
spring boot 2.0 quartz 轻松实现定时任务和作业调度
207 0
|
存储 负载均衡 Java
实战 | 使用Spring Boot + Quartz 实现分布式定时任务平台
本文将从项目实战出发来介绍分布式定时任务的实现。在某些应用场景下要求任务必须具备高可用性和可扩展性,单台服务器不能满足业务需求,这时就需要使用Quartz实现分布式定时任务。
实战 | 使用Spring Boot + Quartz  实现分布式定时任务平台