zoukankan      html  css  js  c++  java
  • Quartz实现JAVA定时任务的动态配置

    什么是动态配置定时任务?

    首先说下这次主题,动态配置。没接触过定时任务的同学可以先看下此篇:JAVA定时任务实现的几种方式

    定时任务实现方式千人千种,不过基础的无外乎 1、JDK 的Timer类 2、Quartz 3、SpringTask  。生产上三种方式我都有使用过。但是使用过程中用的最多的便是xml配置的方式,这种方式最简单,无代码侵入,也比较好理解。
    但是却有个致命的缺点,比如你要改某个任务的触发时间,亦或是你要新增一个任务,暂停一个任务。怎么做?
    停应用!改XML配置!重新启动!
    是不是很致命。最近重新研究了下Quartz的配置,实现了不停机添加、暂停、删除、立即触发任务的方法,在本篇分享出来,其实也不算是完全的研究,在上家公司已经有大佬实现了,这次是也是基于大佬的理解重新实现下。

    国际惯例~先看效果图   ps:文末有彩蛋哦~~

    管理界面:

     效果图:实在不知道该跑什么了,每隔十秒打一段话吧

    技术实现

    maven依赖

    使用springboot做框架支持

    <!-- quartz -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
    </dependency>

    数据表

    CREATE TABLE `sys_task` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `job_name` varchar(255) DEFAULT NULL COMMENT '任务名',
      `description` varchar(255) DEFAULT NULL COMMENT '任务描述',
      `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron表达式',
      `bean_class` varchar(255) DEFAULT NULL COMMENT '任务执行时调用哪个类的方法 包名+类名',
      `job_status` varchar(255) DEFAULT NULL COMMENT '任务状态',
      `job_group` varchar(255) DEFAULT NULL COMMENT '任务分组',
      `create_user` varchar(64) DEFAULT NULL COMMENT '创建者',
      `create_time` datetime DEFAULT NULL COMMENT '创建时间',
      `update_user` varchar(64) DEFAULT NULL COMMENT '更新者',
      `update_time` datetime DEFAULT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`)
    ) ENGINE=MyISAM AUTO_INCREMENT=32 DEFAULT CHARSET=utf8;

    实现步骤

    ①启动项目,启动task监听

    ②读取数据库,将开启的任务job和trigger加载到scheduler调度器

    ③根据任务调度运行job类

    ④每次运行利用AdaptableJobFactory实例化job类,以便注入要运行的service

    听着是不是很简单,但却还是一头雾水,且听我慢慢道来~~

     代码逻辑

    第一步:启动项目,加载监听

    Quartz配置

    Springboot的配置方法,常规Spring项目可以在xml中配置

    @Configuration
    public class QuartzConfigration {
    
        @Autowired
        private JobFactory jobFactory;
    
        @Bean
        public SchedulerFactoryBean schedulerFactoryBean() {
            SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
            try {
                schedulerFactoryBean.setOverwriteExistingJobs(true);
                schedulerFactoryBean.setQuartzProperties(quartzProperties());
                schedulerFactoryBean.setJobFactory(jobFactory);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return schedulerFactoryBean;
        }
    
        // 指定quartz.properties,可在配置文件中配置相关属性
        @Bean
        public Properties quartzProperties() throws IOException {
            PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
            propertiesFactoryBean.setLocation(new ClassPathResource("/config/quartz.properties"));
            propertiesFactoryBean.afterPropertiesSet();
            return propertiesFactoryBean.getObject();
        }
    
        // 创建schedule
        @Bean(name = "scheduler")
        public Scheduler scheduler() {
            return schedulerFactoryBean().getScheduler();
        }
    }

    监听器

    @Component
    @Order(value = 1)
    public class ScheduleJobInitListener implements CommandLineRunner {
    
        @Autowired
        TaskService scheduleJobService;
    
        @Override
        public void run(String... arg0) throws Exception {
            try {
                scheduleJobService.initSchedule();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
    }
    CommandLineRunner类似Spring框架的ApplicationListener监听器。官方的解释是:
    Interface used to indicate that a bean should run when it is contained within a SpringApplication. Multiple CommandLineRunner beans can be defined within the same application context and can be ordered using the Ordered interface or Order @Order annotation.
    接口被用作将其加入spring容器中时执行其run方法。多个CommandLineRunner可以被同时执行在同一个spring上下文中并且执行顺序是以order注解的参数顺序一致。
    第二步:读取数据库,加载scheduler调度器
    job方法
        @Override
        public void initSchedule() throws SchedulerException {
            // 这里获取任务信息数据
            List<TaskDO> jobList = taskMapper.list();
            for (TaskDO task : jobList) {
                if (JobStatusEnum.RUNNING.getCode().equals(task.getJobStatus())) {
                    quartzManager.addJob(task);
                }
            }
        }

    添加任务到Quartz调度器

        /**
         * 添加任务*/    
        @SuppressWarnings("unchecked")
        public void addJob(TaskDO task) {
            try {
                // 创建jobDetail实例,绑定Job实现类
                // 指明job的名称,所在组的名称,以及绑定job类
                Class<? extends Job> jobClass = (Class<? extends Job>) (Class.forName(task.getBeanClass()).newInstance()
                        .getClass());
                JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(task.getJobName(), task.getJobGroup())// 任务名称和组构成任务key
                        .build();
                // 定义调度触发规则
                // 使用cornTrigger规则
                Trigger trigger = TriggerBuilder.newTrigger().withIdentity(task.getJobName(), task.getJobGroup())// 触发器key
                        .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))
                        .withSchedule(CronScheduleBuilder.cronSchedule(task.getCronExpression())).startNow().build();
                // 把作业和触发器注册到任务调度中
                scheduler.scheduleJob(jobDetail, trigger);
                // 启动
                if (!scheduler.isShutdown()) {
                    scheduler.start();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    Scheduler作为Quartz的核心调度器,有将近50多个API接口,包括任务的添加,暂停,恢复,删除等一系列的API,这里仅介绍一些常用的,想要了解更多可以稍后看下彩蛋部分。

    1、start()方法:只有调用start()方法后,Scheduler线程才开始启动触发器trigger,运行job

    2、pauseJob(JobKey jobKey) :根据指定的JobDetail key暂停job。

    3、resumeJob(JobKey jobKey) :根据指定的key恢复一个job。

    4、deleteJob(JobKey jobKey) :删除一个job

    5、triggerJob(JobKey jobKey) :触发一个JobDetail(现在执行)。

    6、rescheduleJob(TriggerKey triggerKey, Trigger newTrigger):

    用给定的键删除触发器,并存储新的触发器,它必须与同一个作业相关联(新触发器必须具有指定的作业名和组)-然而,新触发器不必具有与旧触发器相同的名称。

    第三步:根据任务调度运行job类
    其实这一步是不需要我们编写的,在我们将正确的JobDetail 和 Trigger 表达式加载到任务调度后,调度器会自动触发任务的执行
     

    第四步:实例化job类,注入要运行的service

    工厂类

    @Component
    public class JobFactory extends AdaptableJobFactory {
        //这个对象Spring会帮我们自动注入进来,也属于Spring技术范畴.
        //为什么需要这个类呢,在我写的这个demo中,大家可以将此类删掉,发现程序也可以正确运行,可是我为什么还是加上呢。
        //大家可以看下我们的任务类,大家可以看到Job对象的实例化过程是在Quartz中进行的,这时候我们将spring的东西注入进来,肯定是行不通的,所以需要这个类
        @Autowired
        private AutowireCapableBeanFactory capableBeanFactory;
    
        @Override
        protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
            //调用父类的方法
            Object jobInstance = super.createJobInstance(bundle);
            //进行注入
            capableBeanFactory.autowireBean(jobInstance);
            return jobInstance;
        }
    }

    任务类

    @DisallowConcurrentExecution //作业不并发
    @Component
    public class HelloWorldJob implements Job{
    
        @Override
        public void execute(JobExecutionContext arg0) throws JobExecutionException {
            
            System.out.println("欢迎使用yyblog,这是一个定时任务  --小卖铺的老爷爷!"+ DateUtils.fullTime(new Date()));
            
        }
    
    }

     好了,大功告成,一个简单的动态配置的定时任务已经完成。是不是so easy,下面我们再来简单实现下其他的几种常用的api吧。

    暂停一个job

        public void pauseJob(TaskDO task) throws SchedulerException {
            JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());
            scheduler.pauseJob(jobKey);
        }

    恢复一个job

        public void resumeJob(TaskDO task) throws SchedulerException {
            JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());
            scheduler.resumeJob(jobKey);
        }

    删除一个job

        public void deleteJob(TaskDO task) throws SchedulerException {
            JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());
            scheduler.deleteJob(jobKey);
        }

    立即触发job

        public void runJobNow(TaskDO task) throws SchedulerException {
            JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());
            scheduler.triggerJob(jobKey);
        }

    更新job表达式

        public void updateJobCron(TaskDO task) throws SchedulerException {
    
            TriggerKey triggerKey = TriggerKey.triggerKey(task.getJobName(), task.getJobGroup());
    
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
    
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCronExpression());
    
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
    
            scheduler.rescheduleJob(triggerKey, trigger);
        }

     彩蛋部分~

    好了,正文部分基本上就这些,不多废话了,本文没有太多的讲解原理,只是简单的应用,水平不够还望大家见谅。

    看了上面的效果图是不是还是有种看的见摸不着的感觉,老爷爷这里也做了个线上的实例供大家体验~有兴趣的同学可以亲自试验一下。

    实例地址:http://www.laoyeye.net/management/index  账号/密码:test/123456  菜单:系统设置/计划任务

     当然了还有最重要的源码和API文档我也不会忘的。

    源码地址:https://github.com/allanzhuo/yyblog

    Quartz文档地址:https://github.com/allanzhuo/yyblog/tree/master/doc

    最后的最后,看都看了,码字不易,顺手点个赞白~^_^

  • 相关阅读:
    算法与时间复杂度
    Pandas库
    数据分析之Numpy、Matplotlib库
    增量式爬虫
    分布式爬虫
    scrapy框架之CrawlSpider全站自动爬取
    django 修改 request 对象中的请求参数, 并重新赋值给 request 对象
    nginx 请求文件 进行用户认证/鉴权: internal(限制为内部调用)
    Django 缓存配置的多种方式
    Python 加入类型检查
  • 原文地址:https://www.cnblogs.com/laoyeye/p/9352002.html
Copyright © 2011-2022 走看看