1. 为什么选择SpringBoot与Quartz的组合?
如果你用过Spring自带的@Scheduled注解,肯定知道它有多方便——加个注解,配个Cron表达式,定时任务就跑起来了。但用久了就会发现几个头疼的问题:任务信息全在代码里,改个时间就得重新部署;没法在运行时动态新增或暂停任务;更别说多台机器一起跑的时候,怎么防止任务被重复执行了。
我刚开始做运维平台的时候,就遇到过这些坑。当时需要给几百台网络设备做定时配置备份,每台设备的备份时间还不一样,用@Scheduled根本搞不定。后来换成了Quartz,才算真正解决了问题。SpringBoot 2.7对Quartz的集成已经非常成熟了,特别是spring-boot-starter-quartz这个starter,省去了大量繁琐的配置。但想把Quartz用得顺手,尤其是需要动态管理任务和集群部署时,有几个关键点必须自己动手处理,比如数据源绑定、Spring Bean注入,还有集群配置。
这篇文章我就结合自己踩过的坑,手把手带你实现一个生产级可用的动态任务调度系统。我会用一个真实的“网络设备定时备份”场景贯穿始终,从单机配置讲到集群部署,把增删改查任务、持久化、故障恢复这些核心功能都覆盖到。代码都是经过线上验证的,你拿过去改改就能用。
2. 环境准备与核心依赖:选对版本,事半功倍
第一步是把依赖和环境准备好。这里我强烈建议你锁定版本,避免以后升级带来不必要的麻烦。我目前用的是SpringBoot 2.7.11和Quartz 2.3.2,这个组合非常稳定。
在你的pom.xml里,需要加上这些依赖:
<!-- SpringBoot Quartz Starter (核心) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- Druid 数据库连接池 (性能好,监控全) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Hutool工具包 (处理JSON很方便) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
这里有个关键点:Quartz需要自己的数据库表来存储任务和触发器信息。spring-boot-starter-quartz虽然能自动建表,但在生产环境我从来不建议用。因为自动建表可能因为权限问题失败,而且你不清楚它到底建了些什么。最稳妥的办法是手动执行官方提供的建表脚本。
你可以在Quartz的核心jar包里找到它:quartz-core-2.3.2.jar!/org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql。把这个脚本拿出来,在你的MySQL数据库里执行一遍。执行完后会看到一堆以QRTZ_开头的表,比如QRTZ_JOB_DETAILS、QRTZ_TRIGGERS,这些就是Quartz的“大脑”,所有任务信息都会存在这里。
3. 攻克两大核心难题:数据源绑定与Bean注入
把Quartz整合到SpringBoot里,90%的坑都集中在两个地方:一是Quartz找不到Spring管理的数据源,二是Job类里无法使用@Autowired注入Spring Bean。下面我就分享我的解决方案,这也是本文最核心的部分。
3.1 自定义数据源提供者:让Quartz用上Druid
Quartz默认会用自己的方式去获取数据库连接,但我们的数据源(比如Druid)是由Spring容器管理的。如果不做特殊处理,Quartz就会报错,说找不到数据源。解决思路是:我们写一个中间人(ConnectionProvider),告诉Quartz:“别自己找了,用我这个现成的Druid连接池吧”。
import com.alibaba.druid.pool.DruidDataSource;
import org.quartz.utils.ConnectionProvider;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 自定义Quartz数据源提供者
* 作用:将Spring管理的Druid数据源桥接给Quartz使用
*/
@Component
public class DruidQuartzConnectionProvider implements ConnectionProvider {
private final DruidDataSource dataSource;
// 通过构造器注入Spring容器里的DruidDataSource
public DruidQuartzConnectionProvider(DruidDataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Connection getConnection() throws SQLException {
// 关键:直接从Druid连接池获取连接
return dataSource.getConnection();
}
@Override
public void shutdown() {
// 数据源由Spring管理,这里不需要关闭
}
@Override
public void initialize() throws SQLException {
// 初始化工作由Spring完成,这里留空
}
}
这个类实现了Quartz的ConnectionProvider接口。getConnection()方法是最关键的,它直接委托给Druid数据源。这样一来,Quartz每次需要操作数据库时,都会通过这个类拿到连接,完美衔接。
3.2 配置Quartz调度器工厂:支持Bean注入的关键
解决了数据源,下一个难题是Bean注入。Quartz在触发任务时,会自己new一个Job实例,这个实例完全在Spring容器之外,所以里面的@Autowired、@Resource注解都会失效。我的解决办法是自定义一个JobFactory,在Quartz创建Job实例后,手动把这个实例“塞回”Spring容器,让Spring完成依赖注入。
import org.quartz.Scheduler;
import org.quartz.spi.TriggerFiredBundle;
import org.quartz.utils.DBConnectionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.annotation.PostConstruct;
import java.util.Properties;
@Configuration
public class QuartzConfig {
@Autowired
private DruidDataSource dataSource;
@Autowired
private ApplicationContext applicationContext;
/**
* 初始化阶段,将自定义数据源提供者注册到Quartz
*/
@PostConstruct
public void initDataSourcePr


3万+

被折叠的 条评论
为什么被折叠?



