zoukankan      html  css  js  c++  java
  • springboot和quartz整合实现动态定时任务(持久化单节点)

      Quartz是一个完全由java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制,它支持定时任务持久化到数据库,从而避免了重启服务器时任务丢失,支持分布式多节点,大大的提高了单节点定时任务的容错性。springboot在2.0版本以前没有对quartz做自动配置,因此需要我们自己去手动配置,网上找了许多资料,但是大都不能完全满足自己的需要,因此自己整合了一下方便以后参考(copy),整合代码基于springboot1.5.9和quartz2.3.0,过程如下:

      1、pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>powerx.io</groupId>
        <artifactId>springboot-quartz</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>springboot-quartz</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.5.9.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
            <druid.version>1.1.5</druid.version>
            <quartz.version>2.3.0</quartz.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!--quartz相关依赖-->
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz</artifactId>
                <version>${quartz.version}</version>
            </dependency>
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz-jobs</artifactId>
                <version>${quartz.version}</version>
            </dependency>
            <!--定时任务需要依赖context模块-->
            <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>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    
    </project>

      2、整合配置类

      采用jobDetail使用Spring Ioc托管方式来完成整合,我们可以在定时任务实例中使用Spring注入注解完成业务逻辑处理,代码如下

    package com.example.demo.config;
    
    import org.quartz.spi.JobFactory;
    import org.quartz.spi.TriggerFiredBundle;
    import org.springframework.beans.factory.annotation.Autowire;
    import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.scheduling.quartz.SchedulerFactoryBean;
    import org.springframework.scheduling.quartz.SpringBeanJobFactory;
    
    import javax.sql.DataSource;
    
    @Configuration
    @EnableScheduling
    public class QuartzConfiguration {
        /**
         * 继承org.springframework.scheduling.quartz.SpringBeanJobFactory
         * 实现任务实例化方式
         */
        public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
                ApplicationContextAware {
    
            private transient AutowireCapableBeanFactory beanFactory;
    
            @Override
            public void setApplicationContext(final ApplicationContext context) {
                beanFactory = context.getAutowireCapableBeanFactory();
            }
    
            /**
             * 将job实例交给spring ioc托管
             * 我们在job实例实现类内可以直接使用spring注入的调用被spring ioc管理的实例
             *
             * @param bundle
             * @return
             * @throws Exception
             */
            @Override
            protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
                final Object job = super.createJobInstance(bundle);
                /**
                 * 将job实例交付给spring ioc
                 */
                beanFactory.autowireBean(job);
                return job;
            }
        }
    
        /**
         * 配置任务工厂实例
         *
         * @return
         */
        @Bean
        public JobFactory jobFactory() {
            /**
             * 采用自定义任务工厂 整合spring实例来完成构建任务*/
            AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
            return jobFactory;
        }
    
        /**
         * 配置任务调度器
         * 使用项目数据源作为quartz数据源
         *
         * @param jobFactory 自定义配置任务工厂
         * @param dataSource 数据源实例
         * @return
         * @throws Exception
         */
        @Bean(destroyMethod = "destroy", autowire = Autowire.NO)
        public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception {
            SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
            //将spring管理job自定义工厂交由调度器维护
            schedulerFactoryBean.setJobFactory(jobFactory);
            //设置覆盖已存在的任务
            schedulerFactoryBean.setOverwriteExistingJobs(true);
            //项目启动完成后,等待2秒后开始执行调度器初始化
            schedulerFactoryBean.setStartupDelay(2);
            //设置调度器自动运行
            schedulerFactoryBean.setAutoStartup(true);
            //设置数据源,使用与项目统一数据源
            schedulerFactoryBean.setDataSource(dataSource);
            //设置上下文spring bean name
            schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
            //设置配置文件位置
            schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
            return schedulerFactoryBean;
        }
    }

      AutowiringSpringBeanJobFactory:可以看到上面配置类中,AutowiringSpringBeanJobFactory我们继承了SpringBeanJobFactory类,并且通过实现ApplicationContextAware接口获取ApplicationContext设置方法,通过外部实例化时设置ApplicationContext实例对象,在createJobInstance方法内,我们采用AutowireCapableBeanFactory来托管SpringBeanJobFactory类中createJobInstance方法返回的定时任务实例,这样我们就可以在定时任务类内使用Spring Ioc相关的注解进行注入业务逻辑实例了。

      JobFactory:自定义任务工厂

      SchedulerFactoryBean:使用项目内部数据源的方式来设置调度器的jobSotre,官方quartz有两种持久化的配置方案。

      第一种:采用quartz.properties配置文件配置独立的定时任务数据源,可以与使用项目的数据库完全独立。
      第二种:采用与创建项目统一个数据源,定时任务持久化相关的表与业务逻辑在同一个数据库内。

      可以根据实际的项目需求采取不同的方案,我采用了第二种方案,在上面配置类中可以看到方法schedulerFactoryBean内自动注入了JobFactory实例,也就是我们自定义的AutowiringSpringBeanJobFactory任务工厂实例,另外一个参数就是DataSource,在我们引入spring-boot-starter-jdbc依赖后会根据application.yml文件内的数据源相关配置自动实例化DataSource实例,这里直接注入是没有问题的。我们通过调用SchedulerFactoryBean对象的setConfigLocation方法来设置quartz定时任务框架的基本配置,配置文件所在位置:resources/quartz.properties => classpath:/quartz.properties下。

      quartz.properties内容如下:

    org.quartz.scheduler.instanceName = schedulerFactoryBean
    
    org.quartz.scheduler.instanceId = AUTO
    
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
    
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    
    org.quartz.jobStore.tablePrefix = QRTZ_
    
    org.quartz.jobStore.useProperties = false
    
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    
    org.quartz.threadPool.threadCount = 10
    
    org.quartz.threadPool.threadPriority = 5

      在上面配置中org.quartz.jobStore.classorg.quartz.jobStore.driverDelegateClass是定时任务持久化的关键配置,配置了数据库持久化定时任务以及采用MySQL数据库进行连接,当然你也可以连接别的数据库org.quartz.jobStore.tablePrefix属性配置了定时任务数据表的前缀,在quartz官方提供的创建表SQL脚本默认就是qrtz_,我们需要解压quartz2.3.0的jar,在quartz-2.2.3/docs/dbTables下找到tables_mysql_innodb.sql,然后在mysql客户端执行来创建相应的表。

      3、动态定时任务demo

      QuartzService.java

    package com.example.demo.service;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    import javax.annotation.PostConstruct;
    
    import org.quartz.CronScheduleBuilder;
    import org.quartz.CronTrigger;
    import org.quartz.DateBuilder;
    import org.quartz.DateBuilder.IntervalUnit;
    import org.quartz.JobBuilder;
    import org.quartz.JobDetail;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobKey;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerException;
    import org.quartz.SimpleScheduleBuilder;
    import org.quartz.Trigger;
    import org.quartz.TriggerBuilder;
    import org.quartz.TriggerKey;
    import org.quartz.impl.matchers.GroupMatcher;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    import org.springframework.stereotype.Service;
    
    @Service
    public class QuartzService {
    
        @Autowired
        private Scheduler scheduler;
    
        @PostConstruct
        public void startScheduler() {
            try {
                scheduler.start();
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 增加一个job
         * 
         * @param jobClass
         *            任务实现类
         * @param jobName
         *            任务名称
         * @param jobGroupName
         *            任务组名
         * @param jobTime
         *            时间表达式 (这是每隔多少秒为一次任务)
         * @param jobTimes
         *            运行的次数 (<0:表示不限次数)
         */
        public void addJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, int jobTime,
                int jobTimes) {
            try {
                JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)// 任务名称和组构成任务key
                        .build();
                // 使用simpleTrigger规则
                Trigger trigger = null;
                if (jobTimes < 0) {
                    trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
                            .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withIntervalInSeconds(jobTime))
                            .startNow().build();
                } else {
                    trigger = TriggerBuilder
                            .newTrigger().withIdentity(jobName, jobGroupName).withSchedule(SimpleScheduleBuilder
                                    .repeatSecondlyForever(1).withIntervalInSeconds(jobTime).withRepeatCount(jobTimes))
                            .startNow().build();
                }
                scheduler.scheduleJob(jobDetail, trigger);
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 增加一个job
         * 
         * @param jobClass
         *            任务实现类
         * @param jobName
         *            任务名称
         * @param jobGroupName
         *            任务组名
         * @param jobTime
         *            时间表达式 (如:0/5 * * * * ? )
         */
        public void addJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String jobTime) {
            try {
                // 创建jobDetail实例,绑定Job实现类
                // 指明job的名称,所在组的名称,以及绑定job类
                JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)// 任务名称和组构成任务key
                        .build();
                // 定义调度触发规则
                // 使用cornTrigger规则
                Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)// 触发器key
                        .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))
                        .withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).startNow().build();
                // 把作业和触发器注册到任务调度中
                scheduler.scheduleJob(jobDetail, trigger);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 修改 一个job的 时间表达式
         * 
         * @param jobName
         * @param jobGroupName
         * @param jobTime
         */
        public void updateJob(String jobName, String jobGroupName, String jobTime) {
            try {
                TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
                CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
                        .withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).build();
                // 重启触发器
                scheduler.rescheduleJob(triggerKey, trigger);
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 删除任务一个job
         * 
         * @param jobName
         *            任务名称
         * @param jobGroupName
         *            任务组名
         */
        public void deleteJob(String jobName, String jobGroupName) {
            try {
                scheduler.deleteJob(new JobKey(jobName, jobGroupName));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 暂停一个job
         * 
         * @param jobName
         * @param jobGroupName
         */
        public void pauseJob(String jobName, String jobGroupName) {
            try {
                JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
                scheduler.pauseJob(jobKey);
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 恢复一个job
         * 
         * @param jobName
         * @param jobGroupName
         */
        public void resumeJob(String jobName, String jobGroupName) {
            try {
                JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
                scheduler.resumeJob(jobKey);
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 立即执行一个job
         * 
         * @param jobName
         * @param jobGroupName
         */
        public void runAJobNow(String jobName, String jobGroupName) {
            try {
                JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
                scheduler.triggerJob(jobKey);
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 获取所有计划中的任务列表
         * 
         * @return
         */
        public List<Map<String, Object>> queryAllJob() {
            List<Map<String, Object>> jobList = null;
            try {
                GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
                Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
                jobList = new ArrayList<Map<String, Object>>();
                for (JobKey jobKey : jobKeys) {
                    List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                    for (Trigger trigger : triggers) {
                        Map<String, Object> map = new HashMap<>();
                        map.put("jobName", jobKey.getName());
                        map.put("jobGroupName", jobKey.getGroup());
                        map.put("description", "触发器:" + trigger.getKey());
                        Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                        map.put("jobStatus", triggerState.name());
                        if (trigger instanceof CronTrigger) {
                            CronTrigger cronTrigger = (CronTrigger) trigger;
                            String cronExpression = cronTrigger.getCronExpression();
                            map.put("jobTime", cronExpression);
                        }
                        jobList.add(map);
                    }
                }
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
            return jobList;
        }
    
        /**
         * 获取所有正在运行的job
         * 
         * @return
         */
        public List<Map<String, Object>> queryRunJob() {
            List<Map<String, Object>> jobList = null;
            try {
                List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
                jobList = new ArrayList<Map<String, Object>>(executingJobs.size());
                for (JobExecutionContext executingJob : executingJobs) {
                    Map<String, Object> map = new HashMap<String, Object>();
                    JobDetail jobDetail = executingJob.getJobDetail();
                    JobKey jobKey = jobDetail.getKey();
                    Trigger trigger = executingJob.getTrigger();
                    map.put("jobName", jobKey.getName());
                    map.put("jobGroupName", jobKey.getGroup());
                    map.put("description", "触发器:" + trigger.getKey());
                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                    map.put("jobStatus", triggerState.name());
                    if (trigger instanceof CronTrigger) {
                        CronTrigger cronTrigger = (CronTrigger) trigger;
                        String cronExpression = cronTrigger.getCronExpression();
                        map.put("jobTime", cronExpression);
                    }
                    jobList.add(map);
                }
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
            return jobList;
        }
    
    }

      UserService.java

    package com.example.demo.service;
    
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserService {
    
        public void play() {
            System.out.println("user id play");
        }
        
        public void study() {
            System.out.println("user id study");
        }
    }

      TestJob1.java

    package com.example.demo.job;
    
    import java.util.Date;
    
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    
    public class TestJob1 extends QuartzJobBean {
    
        @Override
        protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
    
            System.out.println(new Date() + "    job执行");
        }
    
    }

      TestJob2.java

    package com.example.demo.job;
    
    import java.util.Date;
    
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    import org.springframework.stereotype.Component;
    
    import com.example.demo.service.UserService;
    @Component
    public class TestJob2 extends QuartzJobBean {
    
        //注入业务service,完成定时任务逻辑
        @Autowired
        private UserService service;
        @Override
        protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
            System.out.println(new Date() + "    job2执行");
            service.play();
        }
    
    }

      TestController.java

    package com.example.demo.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.example.demo.job.TestJob1;
    import com.example.demo.job.TestJob2;
    import com.example.demo.service.QuartzService;
    
    @RestController
    public class TestController {
    
        @Autowired
        private QuartzService quartzService;
        @RequestMapping("/addjob")
        public void startJob(String type) {
            if("TestJob1".equals(type)) {
                quartzService.addJob(TestJob1.class, "job1", "test", "0/5 * * * * ?");
                
            }else {
                quartzService.addJob(TestJob2.class, "job2", "test", "0/5 * * * * ?");
            }
        }
        
        @RequestMapping("/updatejob")
        public void updatejob() {
                quartzService.updateJob("job1", "test", "0/10 * * * * ?");
        }
        
        @RequestMapping("/deletejob")
        public void deletejob() {
                quartzService.deleteJob("job1", "test");
        }
        
        @RequestMapping("/pauseJob")
        public void pauseJob() {
                quartzService.pauseJob("job1", "test");
        }
        
        @RequestMapping("/resumeJob")
        public void resumeJob() {
                quartzService.resumeJob("job1", "test");
        }
        
        @RequestMapping("/queryAllJob")
        public Object queryAllJob() {
                return quartzService.queryAllJob();
        }
        
    
        @RequestMapping("/queryRunJob")
        public Object queryRunJob() {
                return quartzService.queryRunJob();
        }
    }

      4、application.yml

    spring:
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
        username: root
        password: root
      jpa:
        hibernate:
          ddl-auto: update #ddl-auto:设为update表示每次都不会重新建表
        show-sql: true
      application:
        name: quartz-cluster-node-first
    server:
      port: 8081
    # 打印日志
    logging:
      level:
        root: INFO
        org.hibernate: INFO
        org.hibernate.type.descriptor.sql.BasicBinder: TRACE
        org.hibernate.type.descriptor.sql.BasicExtractor: TRACE
        com.springms: DEBUG

      至此,springboot整合quartz实现动态定时任务和任务持久化完毕,各功能我都测试过,符合预期,具体结果不再贴出,小伙伴们在使用时要注意springboot和quartz的版本问题,很多时候并不是代码有问题,而是jar不匹配。springboot2.0以后,直接有了spring-boot-starter-quartz包,我们只需要把原来的jar替换掉,无需任何配置就完成了整合。

  • 相关阅读:
    Educational Codeforces Round 33 (Rated for Div. 2) B. Beautiful Divisors【进制思维/打表】
    Educational Codeforces Round 33 (Rated for Div. 2) A. Chess For Three【模拟/逻辑推理】
    java中的BigInteger
    动态规划-最长上升子序列(LIS模板)多解+变形
    Rain on your Parade---hdu2389(HK求最大匹配)
    Air Raid---hdu1151(最小路径覆盖)
    Swap---hdu2819(最大匹配)
    棋盘游戏---hdu1281(最大匹配)
    The Accomodation of Students---hdu2444(二分图,最大匹配)
    COURSES---poj1469 hdu1083(最大匹配)
  • 原文地址:https://www.cnblogs.com/hhhshct/p/9707710.html
Copyright © 2011-2022 走看看