在实践中遇到动态管理定时任务的需求,场景通常是动态添加、更新、删除任务,借助Quartz,可方便实现功能。
以下使用Quartz结合Spring Boot方式使用。
POM依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
代码
QuartzManager类管理任务的增删改查。
/**
* @author wzx
* @time 2018/6/9
*/
@Component
@Scope("singleton")
public class QuartzManager implements ApplicationContextAware {
private static SchedulerFactory schedulerFactory = new StdSchedulerFactory();
private static final String JOB_DEFAULT_GROUP_NAME = "JOB_DEFAULT_GROUP_NAME";
private static final String TRIGGER_DEFAULT_GROUP_NAME = "TRIGGER_DEFAULT_GROUP_NAME";
private Logger logger = LoggerFactory.getLogger(QuartzManager.class);
private ApplicationContext applicationContext;
private Scheduler scheduler;
@Autowired
private AutowiringSpringBeanJobFactory autowiringSpringBeanJobFactory;
public void start() {
//启动所有任务
try {
this.scheduler = schedulerFactory.getScheduler();
scheduler.setJobFactory(autowiringSpringBeanJobFactory);
//启动所有任务,这里获取AbstractTask的所有子类
Map<String, AbstractTask> tasks = applicationContext.getBeansOfType(AbstractTask.class);
tasks.forEach((k, v) -> {
String cronExpression = v.getCronExpression();
if (cronExpression != null) {
addJob(k, v.getClass().getName(), cronExpression);
}
});
logger.info("start jobs finished.");
} catch (SchedulerException e) {
logger.error(e.getMessage(), e);
throw new RuntimeException("init Scheduler failed");
}
}
public boolean addJob(String jobName, String jobClass, String cronExp) {
boolean result = false;
if (!CronExpression.isValidExpression(cronExp)) {
logger.error("Illegal cron expression format({})", cronExp);
return result;
}
try {
JobDetail jobDetail = JobBuilder.newJob().withIdentity(new JobKey(jobName, JOB_DEFAULT_GROUP_NAME))
.ofType((Class<Job>) Class.forName(jobClass))
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExp))
.withIdentity(new TriggerKey(jobName, TRIGGER_DEFAULT_GROUP_NAME))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (Exception e) {
logger.error(e.getMessage(), e);
logger.error("QuartzManager add job failed");
}
return result;
}
public boolean updateJob(String jobName, String cronExp) {
boolean result = false;
if (!CronExpression.isValidExpression(cronExp)) {
logger.error("Illegal cron expression format({})", cronExp);
return result;
}
JobKey jobKey = new JobKey(jobName, JOB_DEFAULT_GROUP_NAME);
TriggerKey triggerKey = new TriggerKey(jobName, TRIGGER_DEFAULT_GROUP_NAME);
try {
if (scheduler.checkExists(jobKey) && scheduler.checkExists(triggerKey)) {
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
Trigger newTrigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExp))
.withIdentity(new TriggerKey(jobName, TRIGGER_DEFAULT_GROUP_NAME))
.build();
scheduler.rescheduleJob(triggerKey, newTrigger);
result = true;
} else {
logger.error("update job name:{},group name:{} or trigger name:{},group name:{} not exists..",
jobKey.getName(), jobKey.getGroup(), triggerKey.getName(), triggerKey.getGroup());
}
} catch (SchedulerException e) {
logger.error(e.getMessage(), e);
logger.error("update job name:{},group name:{} failed!", jobKey.getName(), jobKey.getGroup());
}
return result;
}
public boolean deleteJob(String jobName) {
boolean result = false;
JobKey jobKey = new JobKey(jobName, JOB_DEFAULT_GROUP_NAME);
try {
if (scheduler.checkExists(jobKey)) {
result = scheduler.deleteJob(jobKey);
} else {
logger.error("delete job name:{},group name:{} not exists.", jobKey.getName(), jobKey.getGroup());
}
} catch (SchedulerException e) {
logger.error(e.getMessage(), e);
logger.error("delete job name:{},group name:{} failed!", jobKey.getName(), jobKey.getGroup());
}
return result;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
定义抽象任务类AbstractTask,实现Job接口,子类Job实例需实现executeInternal方法。
/**
* @author wzx
* @time 2018/6/9
*/
public abstract class AbstractTask implements Job {
private Logger logger = LoggerFactory.getLogger(AbstractTask.class);
protected abstract void executeInternal(JobExecutionContext context);
protected String cronExpression;
@Override
public void execute(JobExecutionContext context) {
try {
executeInternal(context);
} catch (Exception e) {
logger.error(e.getMessage(), e);
logger.error("job execute failed!");
}
}
public String getCronExpression() {
return cronExpression;
}
}
测试TestTask类,继承AbstractTask类,实现executeInternal方法。
/**
* @author wzx
* @time 2018/6/9
*/
@Component("testTask")
public class TestTask extends AbstractTask {
private Logger logger = LoggerFactory.getLogger(TestTask.class);
@PostConstruct
public void init() {
this.cronExpression = "0/2 * * * * ? ";
}
@Override
protected void executeInternal(JobExecutionContext context) {
logger.info("test task start execute.");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
logger.info("test task execute interrupted.");
}
logger.info("test task execute end.");
}
}
测试用例,测试增加、更新、删除功能。
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootdemoApplicationTests {
private Logger logger = LoggerFactory.getLogger(SpringbootdemoApplicationTests.class);
@Autowired
private QuartzManager quartzManager;
@Autowired
private TestTask testTask;
@Test
public void testCronTest() throws InterruptedException {
quartzManager.start();
TimeUnit.SECONDS.sleep(10);
logger.info("start update job");
//修改任务
quartzManager.updateJob("testTask", "0/3 * * * * ? ");
logger.info("end update job");
TimeUnit.SECONDS.sleep(10);
logger.info("start delete job");
//删除任务
quartzManager.deleteJob("testTask");
logger.info("end delete job");
TimeUnit.SECONDS.sleep(10);
//添加任务
logger.info("start add job");
quartzManager.addJob("testTask", testTask.getClass().getName(), "0/3 * * * * ?");
logger.info("end add job");
TimeUnit.SECONDS.sleep(10);
//修改任务
logger.info("start update job");
quartzManager.updateJob("testTask", "0/3 * * * * ?");
logger.info("end update job");
TimeUnit.SECONDS.sleep(10);
//删除任务
logger.info("start delete job");
quartzManager.deleteJob("testTask");
logger.info("end delete job");
logger.info("end.");
}
}
结果输出,符合预期。
2018-06-09 16:43:58.052 INFO 2884 --- [ main] org.quartz.impl.StdSchedulerFactory : Using default implementation for ThreadExecutor
2018-06-09 16:43:58.054 INFO 2884 --- [ main] org.quartz.simpl.SimpleThreadPool : Job execution threads will use class loader of thread: main
2018-06-09 16:43:58.066 INFO 2884 --- [ main] org.quartz.core.SchedulerSignalerImpl : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2018-06-09 16:43:58.066 INFO 2884 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.0 created.
2018-06-09 16:43:58.066 INFO 2884 --- [ main] org.quartz.simpl.RAMJobStore : RAMJobStore initialized.
2018-06-09 16:43:58.067 INFO 2884 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.0) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2018-06-09 16:43:58.067 INFO 2884 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
2018-06-09 16:43:58.067 INFO 2884 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.0
2018-06-09 16:43:58.077 INFO 2884 --- [ main] org.quartz.core.QuartzScheduler : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
2018-06-09 16:43:58.077 INFO 2884 --- [ main] c.e.s.service.task.QuartzManager : start jobs finished.
2018-06-09 16:43:58.081 INFO 2884 --- [eduler_Worker-1] c.e.s.service.task.TestTask : test task start execute.
2018-06-09 16:44:01.082 INFO 2884 --- [eduler_Worker-1] c.e.s.service.task.TestTask : test task execute end.
...
2018-06-09 16:44:08.077 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : start update job
2018-06-09 16:44:08.078 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : end update job
2018-06-09 16:44:09.001 INFO 2884 --- [eduler_Worker-7] c.e.s.service.task.TestTask : test task start execute.
2018-06-09 16:44:12.002 INFO 2884 --- [eduler_Worker-7] c.e.s.service.task.TestTask : test task execute end.
...
2018-06-09 16:44:18.078 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : start delete job
2018-06-09 16:44:18.078 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : end delete job
2018-06-09 16:44:28.079 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : start add job
2018-06-09 16:44:28.079 INFO 2884 --- [ main] org.quartz.core.QuartzScheduler : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
2018-06-09 16:44:28.079 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : end add job
2018-06-09 16:44:30.001 INFO 2884 --- [eduler_Worker-1] c.e.s.service.task.TestTask : test task start execute.
2018-06-09 16:44:33.001 INFO 2884 --- [eduler_Worker-1] c.e.s.service.task.TestTask : test task execute end.
...
2018-06-09 16:44:38.080 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : start update job
2018-06-09 16:44:38.080 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : end update job
2018-06-09 16:44:39.001 INFO 2884 --- [eduler_Worker-4] c.e.s.service.task.TestTask : test task start execute.
2018-06-09 16:44:42.003 INFO 2884 --- [eduler_Worker-4] c.e.s.service.task.TestTask : test task execute end.
...
2018-06-09 16:44:48.080 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : start delete job
2018-06-09 16:44:48.080 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : end delete job
2018-06-09 16:44:48.080 INFO 2884 --- [ main] c.e.s.SpringbootdemoApplicationTests : end.
2018-06-09 16:44:48.083 INFO 2884 --- [ Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@1da2cb77: startup date [Sat Jun 09 16:43:57 CST 2018]; root of context hierarchy
Job默认由Quartz管理,如果需要使用Spring容器管理bean,也就是依赖注入,需要指定JobFactory,也就是指定将Job由Spring容器管理。
/**
* @author wzx
* @time 2018/6/9
*/
@Component
public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
final Object jobInstance = super.createJobInstance(bundle);
beanFactory.autowireBean(jobInstance);
return jobInstance;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
}
来源:https://blog.csdn.net/Qgwperfect/article/details/100035073