Quartz使用

本文详细介绍了如何使用Quartz进行作业调度,包括Maven依赖的引入、quartz.properties配置、Druid数据库连接池的设定、Spring管理Job、Quartz类配置、Job操作以及Cron表达式的使用。为确保任务持久化,建议导入Quartz提供的Mysql数据库脚本。参考链接提供了更多关于Cron表达式的详细信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、Maven包的导入

        <quartz.starter.version>2.0.0.M2</quartz.starter.version>

        .............

        <!--quartz 版本号为2.3.0-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
            <version>${quartz.starter.version}</version>
        </dependency>

2、quartz.properties

# 使用默认的调度器
#org.quartz.scheduler.instanceName=DefaultQuartzScheduler
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.rmi.export=false
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false
#线程池配置
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=10
org.quartz.threadPool.threadPriority=5
#配置是否启动自动加载数据库内的定时任务
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
#持久化配置
org.quartz.jobStore.misfireThreshold=50000
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.dataSource=qzDS
org.quartz.dataSource.qzDS.connectionProvider.class=com.cainiao.transform.config.DruidConnectionProvider
org.quartz.dataSource.qzDS.maxConnections=10
org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.validationQuery=SELECT 1 FROM DUAL
org.quartz.dataSource.qzDS.URL=jdbc:mysql://localhost:3306/transform?useSSL=false
org.quartz.dataSource.qzDS.user=root
org.quartz.dataSource.qzDS.password=123456
org.quartz.dataSource.qzDS.validateOnCheckout=false
#支持集群
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.clusterCheckinInterval=15000

3、定义Druid数据库连接池

/**
 * Quartz默认采用Innodb作为数据库连接池,此为自定义数据库连接池配置。
 *
 * @author songfan
 * @since 2019-06-06 9:44 PM
 */
public class DruidConnectionProvider implements ConnectionProvider {

    /**
     * JDBC驱动
     */
    public String driver;
    /**
     * JDBC连接串
     */
    public String URL;
    /**
     * 数据库用户名
     */
    public String user;
    /**
     * 数据库用户密码
     */
    public String password;
    /**
     * 数据库最大连接数
     */
    public Integer maxConnections;
    /**
     * 数据库SQL查询每次连接返回执行到连接池,以确保它仍然是有效的。
     */
    public String validationQuery;

    private Boolean validateOnCheckout;

    public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 20;

    /**
     * Druid连接池
     */
    public DruidDataSource datasource;

    @Override
    public Connection getConnection() throws SQLException {
        return datasource.getConnection();
    }

    @Override
    public void shutdown() throws SQLException {
        datasource.close();
    }

    @Override
    public void initialize() throws SQLException {
        if (this.URL == null) {
            throw new SQLException("DBPool could not be created: DB URL cannot be null");
        }

        if (this.driver == null) {
            throw new SQLException("DBPool driver could not be created: DB driver class name cannot be null!");
        }

        if (this.maxConnections == null) {
            throw new SQLException(
                "DBPool maxConnectins could not be created: Max connections must be greater than zero!");
        }

        datasource = new DruidDataSource();
        try {
            datasource.setDriverClassName(this.driver);
        } catch (Exception e) {
            try {
                throw new SchedulerException("Problem setting driver class name on datasource: " + e.getMessage(), e);
            } catch (SchedulerException e1) {
            }
        }

        datasource.setUrl(this.URL);
        datasource.setUsername(this.user);
        datasource.setPassword(this.password);
        datasource.setInitialSize(5);
        datasource.setMinIdle(5);
        datasource.setMaxActive(this.maxConnections);
        datasource.setMaxWait(10000);
        datasource.setTimeBetweenEvictionRunsMillis(600000);
        datasource.setKeepAlive(true);
        datasource.setMinEvictableIdleTimeMillis(300000);
        datasource.setTestWhileIdle(true);
        datasource.setRemoveAbandoned(true);
        datasource.setRemoveAbandonedTimeout(80);

        datasource.setMaxPoolPreparedStatementPerConnectionSize(DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION);

        if (this.validationQuery != null) {
            datasource.setValidationQuery(this.validationQuery);
        }
        if (this.validateOnCheckout != null) {
            datasource.setTestOnReturn(this.validateOnCheckout);
            datasource.setTestOnBorrow(this.validateOnCheckout);
        }
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getURL() {
        return URL;
    }

    public void setURL(String URL) {
        this.URL = URL;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * Getter method for property maxConnections.
     *
     * @return property value of maxConnections
     */
    public int getMaxConnections() {
        return maxConnections;
    }

    /**
     * Setter method for property maxConnections.
     *
     * @param maxConnections value to be assigned to property maxConnections
     */
    public void setMaxConnections(int maxConnections) {
        this.maxConnections = maxConnections;
    }

    public String getValidationQuery() {
        return validationQuery;
    }

    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }

    public boolean isValidateOnCheckout() {
        return validateOnCheckout;
    }

    public void setValidateOnCheckout(boolean validateOnCheckout) {
        this.validateOnCheckout = validateOnCheckout;
    }

    public DruidDataSource getDatasource() {
        return datasource;
    }

    public void setDatasource(DruidDataSource datasource) {
        this.datasource = datasource;
    }
}

4、将Job交给Spring来管理

/**
 * 把作业交给spring处理
 *
 * @author songfan
 * @since 2019-06-06 9:13 PM
 */
@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;
    }
}

5、配置Quartz类

@Configuration
public class SchedulerConfig implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private JobFactory jobFactory;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("任务已经启动..." + event.getSource());
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        //获取配置属性
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        //在quartz.properties中的属性被读取并注入后再初始化对象
        propertiesFactoryBean.afterPropertiesSet();
        //创建SchedulerFactoryBean
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setQuartzProperties(propertiesFactoryBean.getObject());
        factory.setJobFactory(jobFactory);
        // 当spring关闭时,会等待所有已经启动的quartz job结束后spring才能完全shutdown。
        factory.setWaitForJobsToCompleteOnShutdown(true);
        factory.setOverwriteExistingJobs(false);
        factory.setStartupDelay(1);
        return factory;
    }

    /**
     * quartz初始化监听器,工程停止再重启后依然保证已有的Job运行
     *
     * @return QuartzInitializerListener
     */
    @Bean
    public QuartzInitializerListener executorListener() {
        return new QuartzInitializerListener();
    }

    /**
     * 通过SchedulerFactoryBean获取Scheduler的实例
     *
     * @return Scheduler
     * @throws IOException
     * @throws SchedulerException
     */
    @Bean(name = "scheduler")
    public Scheduler scheduler() throws IOException, SchedulerException {
        return schedulerFactoryBean().getScheduler();
    }

}

6、增、删、停止、启动Job类

@Service
public class QuartzManagerImpl implements QuartzManager {
    @Autowired
    @Qualifier("scheduler")
    private Scheduler scheduler;

    @Autowired
    private JobConfigDAO jobConfigDAO;

    @Transactional
    @Override
    public Long createJob(JobConfig config) throws Exception {
        JobDetail jobDetail = JobBuilder.newJob(getClass(config.getJobProcessor()).getClass()).withIdentity(
            config.getJobName(), config.getGroupName()).build();
        if (!CronExpression.isValidExpression(config.getCronExpression())) {
            // 抛出异常,表达式格式不正确。
        }
        //表达式调度构建器(即任务执行的时间,不立即执行)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(config.getCronExpression())
            .withMisfireHandlingInstructionIgnoreMisfires();
        Date startTime = null;
        if (config.getStartTime() == null) {
            startTime = new Date();
        } else {
            startTime = new Date(config.getStartTime());
        }
        //按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(config.getJobName(), config.getGroupName())
            .startAt(startTime)
            .withSchedule(scheduleBuilder).build();
        // 传递参数
        if (StringUtils.isNotBlank(config.getInvokeParam())) {
            trigger.getJobDataMap().put("invokeParam", config.getInvokeParam());
        }
        scheduler.scheduleJob(jobDetail, trigger);
        jobConfigDAO.insert(config);
        return config.getJobId();
    }

    @Override
    public boolean updateJob(JobConfig config) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(config.getJobName(), config.getGroupName());
        JobKey jobKey = new JobKey(config.getJobName(), config.getGroupName());
        if (scheduler.checkExists(jobKey) && scheduler.checkExists(triggerKey)) {
            CronTrigger trigger = (CronTrigger)scheduler.getTrigger(triggerKey);
            //表达式调度构建器,不立即执行
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(config.getCronExpression())
                .withMisfireHandlingInstructionDoNothing();
            //按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
                .withSchedule(scheduleBuilder).build();
            //修改参数
            if (!trigger.getJobDataMap().get("invokeParam").equals(config.getInvokeParam())) {
                trigger.getJobDataMap().put("invokeParam", config.getInvokeParam());
            }
            //按新的trigger重新设置job执行
            scheduler.rescheduleJob(triggerKey, trigger);
            return true;
        }
        return false;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteJob(Long jobId) throws SchedulerException {
        JobConfig jobConfig = jobConfigDAO.queryById(jobId);
        JobKey jobKey = new JobKey(jobConfig.getJobName(), jobConfig.getGroupName());
        TriggerKey triggerKey = new TriggerKey(jobConfig.getJobName(), jobConfig.getGroupName());
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null) {
            // TODO 抛个异常,job detail不存在。
            return false;
        } else if (!scheduler.checkExists(jobKey)) {
            // TODO 抛个异常,调度器里面的job key不存在。
            return false;
        } else {
            if (triggerKey != null) {
                // 停止触发器
                scheduler.pauseTrigger(triggerKey);
                // 移除触发器
                scheduler.unscheduleJob(triggerKey);
            }
            scheduler.deleteJob(jobKey);
            jobConfigDAO.delete(jobId);
            return true;
        }
    }

    @Override
    public boolean disableJob(Long jobId) throws SchedulerException {
        JobConfig jobConfig = jobConfigDAO.queryById(jobId);
        JobKey jobKey = new JobKey(jobConfig.getJobName(), jobConfig.getGroupName());
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null) {
            // TODO 抛个异常,job detail不存在。
            return false;
        } else if (!scheduler.checkExists(jobKey)) {
            // TODO 抛个异常,job key不存在。
            return false;
        } else {
            scheduler.pauseJob(jobKey);
            return true;
        }
    }

    @Override
    public boolean enableJob(Long jobId) throws SchedulerException {
        JobConfig jobConfig = jobConfigDAO.queryById(jobId);
        JobKey jobKey = new JobKey(jobConfig.getJobName(), jobConfig.getGroupName());
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null) {
            // TODO 抛个异常,job detail不存在。
            return false;
        } else if (!scheduler.checkExists(jobKey)) {
            // TODO 抛个异常,job key不存在。
            return false;
        } else {
            scheduler.resumeJob(jobKey);
            return true;
        }
    }

    private static Job getClass(String jobProcessor) throws Exception {
        Class<?> clazz = Class.forName(jobProcessor);
        return (Job)clazz.newInstance();
    }
}

7、运行态的Job类

public class JobOne implements Job {
    private static final Logger log = LoggerFactory.getLogger(JobOne.class);

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDataMap jobDataMap = jobExecutionContext.getTrigger().getJobDataMap();
        String invokeParam = (String)jobDataMap.get("invokeParam");
        log.info("job中所传入的参数为:" + invokeParam);
    }
}

8、如果任务Job需要做持久化,也就是宕机后依然可以重新运行,则需要导入Quartz默认的数据库文件

具体见官网:

http://www.quartz-scheduler.org/ 下载完搜索里面的Mysql文件直接导入即可。

9、Cron表达式的具体写法可参考 https://blog.csdn.net/AlvinTech14/article/details/45505351

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值