Quartz+Spring Boot实现动态管理定时任务

梦想与她 提交于 2019-11-28 07:39:36

在实践中遇到动态管理定时任务的需求,场景通常是动态添加、更新、删除任务,借助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();
    }
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!