一、依赖引入
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.4.RELEASE</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> <version>2.3.0</version> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.6.26</version> </dependency>
<dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.10.2</version> </dependency>
二、添加配置
application.properties
spring.quartz.properties.org.quartz.scheduler.instanceName=testScheduler spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO spring.quartz.properties.org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_ spring.quartz.properties.org.quartz.jobStore.isClustered=true spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=10000 spring.quartz.properties.org.quartz.jobStore.useProperties=false spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool spring.quartz.properties.org.quartz.threadPool.threadCount=10 spring.quartz.properties.org.quartz.threadPool.threadPriority=5 spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true spring.quartz.job-store-type=jdbc
三、建表
在数据库创建tables_mysql.sql里面的表
四、编码
涉及六个类如下
(1)JobFactory.java
@Component
public class JobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
//进行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
(2)ClassUtils.java
public class ClassUtils {
public static Set<Class<?>> getClasses(String packageName, Class<? extends Annotation> annotationCls) {
Reflections reflections = new Reflections(packageName);
return reflections.getTypesAnnotatedWith(annotationCls);
}
public static boolean isSubclass(Class<?> clazz, Class<?> superClass) {
if (clazz == null || superClass == null) {
return false;
}
if (clazz.equals(superClass) || clazz.isAssignableFrom(superClass)) {
return true;
}
return isSubclass(clazz.getSuperclass(), superClass);
}
}
(3)QuartzConfig.java
@Configuration
public class QuartzConfig implements SchedulerFactoryBeanCustomizer {
@Autowired
private JobFactory jobFactory;
@Autowired
private DefaultListableBeanFactory beanFactory;
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
//项目启动完成后,等待10秒后开始执行调度器初始化
schedulerFactoryBean.setStartupDelay(10);
//设置调度器自动运行
schedulerFactoryBean.setAutoStartup(true);
//QuartzScheduler启动时更新已存在的job
schedulerFactoryBean.setOverwriteExistingJobs(true);
//将spring管理job自定义工厂交由调度器维护
schedulerFactoryBean.setJobFactory(jobFactory);
//这样当spring关闭时,会等待所有已经启动的quartz job结束后spring才能完全shutdown。
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);
}
/**
* 初始化定时任务监听器
**/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
@PostConstruct
public void initMethod() {
//创建注解定时任务
createAnnotationJob();
}
/**
* 找出所有定时任务,并创建
*/
private void createAnnotationJob() {
//TODO 此处改成定时任务TestJob.java所在包即可
String packageName = this.getClass().getPackage().getName();
Class<QuartzSchedule> annotationCls = QuartzSchedule.class;
Set<Class<?>> annotationClsSet = ClassUtils.getClasses(packageName, annotationCls);
annotationClsSet.forEach(cls -> {
QuartzSchedule annotation = cls.getAnnotation(annotationCls);
String cronSchedule = annotation.cronSchedule();
if(!StringUtils.isEmpty(cronSchedule) && ClassUtils.isSubclass(cls, Job.class)) {
Class<? extends Job> jobSubClass = cls.asSubclass(Job.class);
createJob(jobSubClass, cronSchedule);
}
});
}
/**
* 创建定时任务
* @param jobCls 定时任务执行类
* @param cronSchedule 执行计划
*/
private void createJob(Class<? extends Job> jobCls, String cronSchedule) {
//任务详情
JobDetail jobDetail = jobDetail(jobCls);
//手动注入定时任务详情jobDetail
registerSingletonBean(jobDetail);
//任务触发器
Trigger trigger = jobTrigger(jobCls, cronSchedule);
//手动注入触发器trigger
registerSingletonBean(trigger);
}
/**
* 注入bean
*/
private void registerSingletonBean(Object bean) {
String beanName = bean.getClass().getSimpleName().substring(0,1).toLowerCase() + bean.getClass().getSimpleName().substring(1) + UUID.randomUUID();
// 手动注入bean
beanFactory.registerSingleton(beanName, bean);
}
/**
* 定时任务详情
*/
private JobDetail jobDetail(Class<? extends Job> jobCls){
String name = jobCls.getSimpleName();
String group = jobCls.getSimpleName() + "Group";
return JobBuilder.newJob(jobCls)
.withIdentity(name,group)
.storeDurably()// 即使没有Trigger关联时,也不需要删除该JobDetail
.build();
}
/**
* 定时任务触发器
*/
private Trigger jobTrigger(Class<? extends Job> jobCls, String cronSchedule){
String name = jobCls.getSimpleName();
String group = jobCls.getSimpleName() + "Group";
return TriggerBuilder.newTrigger()
.forJob(jobDetail(jobCls))
.withIdentity(name,group)
.withSchedule(CronScheduleBuilder.cronSchedule(cronSchedule))
.build();
}
}
(4)QuartzSchedule.java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface QuartzSchedule {
/**
* 任务调度表达式
*/
String cronSchedule() default "";
/**
* 任务名称
*/
String name() default "";
}
(5)QuartzJobLock.java
集群状态需要使用分布式锁
@Slf4j
public abstract class QuartzJobLock extends QuartzJobBean {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 执行定时任务
*/
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String className = this.getClass().getSimpleName();
QuartzSchedule annotation = this.getClass().getAnnotation(QuartzSchedule.class);
if(annotation == null) {
log.error("该类 {} 未配置QuartzSchedule注解,不执行任务", className);
return;
}
String lockKey = className + "Key";
boolean lock = tryLock(lockKey);
if (!lock) {
log.error(lockKey + " 正在执行");
return;
}
try {
executeJob();
} catch (Exception e) {
log.error(lockKey + " error{}", e);
} finally {
unlock(lockKey);
}
}
/**
* 加锁
*/
private boolean tryLock(String key) {
return redisTemplate.opsForValue().setIfAbsent(key, "1", 100, TimeUnit.SECONDS);
}
/**
* 解锁
*/
private void unlock(String key) {
redisTemplate.delete(key);
}
/**
* 调用具体方法实现业务逻辑
*/
public abstract void executeJob() ;
}
(5)TestJob.java
每新增一个定时任务,只需要创建一个如下的类
定时任务注解 @QuartzSchedule name:定时任务名称 cronSchedule:执行计划
@DisallowConcurrentExecution
@Slf4j
@Component
@QuartzSchedule(name = "测试定时任务", cronSchedule = "0 */1 * * * ?")
public class TestJob extends QuartzJobLock {
@Autowired
private TestService testService;
/**
* 业务逻辑
*/
@Override
public void executeJob() {
testService.doSomething();
}
}