✨Quartz✨开箱即用Starter包实现✨

766 阅读9分钟

你的名字-001

大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈


前言

定时任务框架Quartz提供了完备的分布式环境下的定时任务执行能力,本文将基于Quartz框架实现分布式定时任务Starter,以提供在Springboot框架下的开箱即用的分布式定时任务功能。

Github源码地址:honey-scheduler

正文

一. Starter实现

1. 思路概述

因为Quartz框架的核心就是调度器Scheduler,定时任务JobDetail和触发器Trigger,所以分布式定时任务Starter的核心功能就是在应用启动时,自动创建Scheduler,然后加载用户配置的JobDetailTrigger并注册到Scheduler中。

自动创建Scheduler可以基于SchedulerFactoryBean来实现,这是Spring专门为Quartz提供的用于创建SchedulerFactoryBean

加载用户配置的JobDetailTrigger可以借助ImportBeanDefinitionRegistrar来将用户配置的JobDetailTrigger全部放到Spring容器中,然后在创建SchedulerFactoryBean时将所有JobDetailTrigger设置进去。

2. 工程准备

工程目录结构如下所示。

工程目录结构图

使用到的常量类如下所示。

public class HoneySchedulerConstants {

    public static final String HONEY_QUARTZ_PROPERTIES_FILE_PATH = "classpath:honey-quartz.properties";
    public static final String HONEY_QUARTZ_PROPERTIES_PREFIX = "org.quartz.";
    public static final String HONEY_QUARTZ_PROPERTIES_INSTANCE_NAME = "org.quartz.scheduler.instanceName";

    public static final String HONEY_QUARTZ_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    public static final String HONEY_DEFAULT_SCHEDULER_INSTANCE_NAME = "honey-default-instance";

    public static final String HONEY_JOB_JOB_GROUP = "jobGroup";
    public static final String HONEY_JOB_JOB_NAME = "jobName";
    public static final String HONEY_JOB_TRIGGER_GROUP = "triggerGroup";
    public static final String HONEY_JOB_TRIGGER_NAME = "triggerName";
    public static final String HONEY_JOB_START = "start";
    public static final String HONEY_JOB_END = "end";
    public static final String HONEY_JOB_MISFIRE = "misfire";

    public static final String HONEY_SIMPLE_JOB_REPEAT_INTERVAL = "repeatInterval";
    public static final String HONEY_SIMPLE_JOB_REPEAT_COUNT = "repeatCount";

    public static final String HONEY_CRON_JOB_CRON = "cron";

}

父工程pom文件如下所示。

<?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>
    <packaging>pom</packaging>
    <modules>
        <module>honey-starter-scheduler</module>
        <module>honey-scheduler-example</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.6</version>
    </parent>

    <groupId>com.honey</groupId>
    <artifactId>honey-scheduler</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz</artifactId>
                <version>2.3.2</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.31</version>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.12.0</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

honey-starter-scheduler工程的pom文件如下所示。

<?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">
    <parent>
        <artifactId>honey-scheduler</artifactId>
        <groupId>com.honey</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>honey-starter-scheduler</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

3. 配置加载

我们定义如下注解。

/**
 * 定时任务开启注解。<br/>
 * 在启动类上添加该注解以启动定时任务。
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({HoneySchedulerPropertiesConfiguration.class})
public @interface EnableHoneyScheduling {
}

上述注解作为整个分布式定时任务模块的开关,如果要开启分布式定时任务,需要在启动类上添加@EnableHoneyScheduling注解。

注意到通过@EnableHoneyScheduling注解导入了HoneySchedulerPropertiesConfiguration,该类用于配置加载,下面看一下其实现。

/**
 * 主要作用是将application.yml文件配置的属性与honey-quartz.properties
 * 文件配置的属性进行组合。<br/>
 * application.yml文件配置优先级大于honey-quartz.properties。
 */
@Configuration
@EnableConfigurationProperties({HoneySchedulerProperties.class})
public class HoneySchedulerPropertiesConfiguration {

    @Bean
    public Properties schedulerProperties(HoneySchedulerProperties honeySchedulerProperties) {
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(
                HoneySchedulerPropertiesConfiguration.class.getClassLoader());

        // 去classpath下加载honey-quartz.properties配置文件的属性
        Properties honeyQuartzProperties = new Properties();
        try {
            honeyQuartzProperties.load(resourcePatternResolver.getResource(HONEY_QUARTZ_PROPERTIES_FILE_PATH)
                    .getInputStream());
        } catch (Exception e) {
            // ignore
        }

        // 将application.yml文件配置的属性转换成Properties
        Properties ymlQuartzProperties = new Properties();
        Map<String, String> quartzProperties = honeySchedulerProperties.getQuartz();
        if (null != quartzProperties && !quartzProperties.isEmpty()) {
            for (Map.Entry<String, String> entry : quartzProperties.entrySet()) {
                ymlQuartzProperties.put(HONEY_QUARTZ_PROPERTIES_PREFIX + entry.getKey(), entry.getValue());
            }
        }

        // 组合application.yml配置文件属性与honey-quartz.properties配置文件属性
        // 注意application.yml文件配置优先级大于honey-quartz.properties
        CollectionUtils.mergePropertiesIntoMap(ymlQuartzProperties, honeyQuartzProperties);

        return honeyQuartzProperties;
    }

}

用户自定义的配置使用HoneySchedulerProperties来表示,如下所示。

/**
 * Quartz定时任务框架的配置属性。<br/>
 * 配置的字段与原生Quartz的配置字段保持一致。
 */
@ConfigurationProperties(prefix = "org")
public class HoneySchedulerProperties {

    private Map<String, String> quartz = new HashMap<>();

    public Map<String, String> getQuartz() {
        return quartz;
    }

    public void setQuartz(Map<String, String> quartz) {
        this.quartz = quartz;
    }

}

honey-quartz.properties是框架提供的默认兜底配置,如下所示。

org.quartz.scheduler.instanceName=DefaultHoneySchedulerInstance
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.isClustered=true
org.quartz.jobStore.misfireThreshold=60000
org.quartz.jobStore.acquireTriggersWithinLock=true
org.quartz.threadPool.threadCount=5
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
org.quartz.scheduler.jmx.export=true

4. TriggerJobDetail加载

针对Simple执行的任务,定义如下注解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
@Import(HoneyJobRegistrar.class)
public @interface HoneySimpleJob {

    /**
     * 任务的group。
     */
    String jobGroup();

    /**
     * 任务的name。
     */
    String jobName();

    /**
     * 任务触发器的group。<br/>
     * 不配置时取jobGroup的值。
     */
    String triggerGroup() default StringUtils.EMPTY;

    /**
     * 任务触发器的name。<br/>
     * 不配置时取jobName的值。
     */
    String triggerName() default StringUtils.EMPTY;

    /**
     * Trigger的重复触发间隔。<br/>
     * 单位是毫秒。
     */
    String repeatInterval();

    /**
     * Trigger的重复触发次数。<br/>
     * 配置成-1表示一直执行。
     */
    String repeatCount();

    /**
     * 开始时间点。<br/>
     * 格式为yyyy-MM-dd HH:mm:ss。<br/>
     * 不配置时取当前时间。
     */
    String start() default StringUtils.EMPTY;

    /**
     * 结束时间点。<br/>
     * 格式为yyyy-MM-dd HH:mm:ss。
     */
    String end() default StringUtils.EMPTY;

    /**
     * Misfire策略。<br/>
     * 策略取值见{@link SimpleTrigger}。
     */
    int misfire() default 0;

}

针对Cron执行的任务,定义如下注解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
@Import(HoneyJobRegistrar.class)
public @interface HoneyCronJob {

    /**
     * 任务的group。
     */
    String jobGroup();

    /**
     * 任务的name。
     */
    String jobName();

    /**
     * 任务触发器的group。<br/>
     * 不配置时取jobGroup的值。
     */
    String triggerGroup() default StringUtils.EMPTY;

    /**
     * 任务触发器的name。<br/>
     * 不配置时取jobName的值。
     */
    String triggerName() default StringUtils.EMPTY;

    /**
     * 定时任务的Cron表达式。
     */
    String cron();

    /**
     * 开始时间点。<br/>
     * 格式为yyyy-MM-dd HH:mm:ss。<br/>
     * 不配置时取当前时间。
     */
    String start() default StringUtils.EMPTY;

    /**
     * 结束时间点。<br/>
     * 格式为yyyy-MM-dd HH:mm:ss。
     */
    String end() default StringUtils.EMPTY;

    /**
     * Misfire策略。<br/>
     * 策略取值见{@link CronTrigger}。
     */
    int misfire() default 0;

}

@HoneySimpleJob和@HoneyCronJob注解均复合了@Import注解并导入了HoneyJobRegistrar,并且HoneyJobRegistrar实现了ImportBeanDefinitionRegistrar接口,所以每有一个任务类由@HoneySimpleJob和@HoneyCronJob注解修饰,那么我们就可以在HoneyJobRegistrar中读取被修饰的类的信息然后注册TriggerJobDetailSpring容器中,HoneyJobRegistrar实现如下。

/**
 * 将由{@link HoneySimpleJob}和{@link HoneyCronJob}注解修饰的任务注册为
 * Spring容器的bean。<br/>
 * 注解中的配置内容支持配置解析。
 */
public class HoneyJobRegistrar implements EnvironmentAware, ImportBeanDefinitionRegistrar {

    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (importingClassMetadata.hasAnnotation(HoneySimpleJob.class.getName())) {
            processSimpleJob(importingClassMetadata, registry);
        } else if (importingClassMetadata.hasAnnotation(HoneyCronJob.class.getName())) {
            processCronJob(importingClassMetadata, registry);
        }
    }

    private void processSimpleJob(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes annotationAttributes = AnnotationAttributes
                .fromMap(importingClassMetadata.getAnnotationAttributes(HoneySimpleJob.class.getName()));

        if (null == annotationAttributes) {
            return;
        }

        // 得到任务的Class对象
        Class<?> clazz;
        String className = importingClassMetadata.getClassName();
        try {
            clazz = ClassUtils.forName(className,
                    Thread.currentThread().getContextClassLoader());
        } catch (Exception e) {
            throw new IllegalStateException();
        }

        // 任务类必须是QuartzJobBean类型
        if (!QuartzJobBean.class.isAssignableFrom(clazz)) {
            throw new IllegalStateException();
        }

        String jobGroup = resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_JOB_GROUP), true);
        String jobName = resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_JOB_NAME), true);
        String triggerGroup = resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_TRIGGER_GROUP), true);
        String triggerName = resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_TRIGGER_NAME), true);
        Date startTime = parseDateStr(resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_START), false), true);
        Date endTime = parseDateStr(resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_END), false), false);
        String interval = resolvePlaceholder(annotationAttributes.getString(HONEY_SIMPLE_JOB_REPEAT_INTERVAL), false);
        long repeatInterval = StringUtils.isEmpty(interval) ? 0 : Long.parseLong(interval);
        String repeat = resolvePlaceholder(annotationAttributes.getString(HONEY_SIMPLE_JOB_REPEAT_COUNT), false);
        int repeatCount = StringUtils.isEmpty(repeat) ? SimpleTrigger.REPEAT_INDEFINITELY : Integer.parseInt(repeat);
        int misfire = annotationAttributes.getNumber(HONEY_JOB_MISFIRE);

        BeanDefinition jobDetailBeanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(JobDetailFactoryBean.class)
                .addPropertyValue("group", jobGroup)
                .addPropertyValue("name", jobName)
                .addPropertyValue("jobClass", clazz)
                .addPropertyValue("durability", true)
                .addPropertyValue("requestsRecovery", true)
                .setScope(BeanDefinition.SCOPE_SINGLETON)
                .getBeanDefinition();
        registry.registerBeanDefinition(className, jobDetailBeanDefinition);

        if (registry instanceof BeanFactory) {
            JobDetail jobDetail = ((BeanFactory) registry).getBean(className, JobDetail.class);
            BeanDefinition triggerBeanDefinition = BeanDefinitionBuilder
                    .genericBeanDefinition(SimpleTriggerImpl.class)
                    .addPropertyValue("group", StringUtils.isEmpty(triggerGroup) ? jobGroup : triggerGroup)
                    .addPropertyValue("name", StringUtils.isEmpty(triggerName) ? jobName : triggerName)
                    .addPropertyValue("jobKey", jobDetail.getKey())
                    .addPropertyValue("startTime", startTime)
                    .addPropertyValue("endTime", endTime)
                    .addPropertyValue("repeatInterval", repeatInterval)
                    .addPropertyValue("repeatCount", repeatCount)
                    .addPropertyValue("misfireInstruction", misfire)
                    .setScope(BeanDefinition.SCOPE_SINGLETON)
                    .getBeanDefinition();
            registry.registerBeanDefinition(assembleTriggerBeanName(className), triggerBeanDefinition);
        }
    }

    private void processCronJob(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes annotationAttributes = AnnotationAttributes
                .fromMap(importingClassMetadata.getAnnotationAttributes(HoneyCronJob.class.getName()));

        if (null == annotationAttributes) {
            return;
        }

        // 得到任务的Class对象
        Class<?> clazz;
        String className = importingClassMetadata.getClassName();
        try {
            clazz = ClassUtils.forName(className,
                    Thread.currentThread().getContextClassLoader());
        } catch (Exception e) {
            throw new IllegalStateException();
        }

        // 任务类必须是QuartzJobBean类型
        if (!QuartzJobBean.class.isAssignableFrom(clazz)) {
            throw new IllegalStateException();
        }

        String jobGroup = resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_JOB_GROUP), true);
        String jobName = resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_JOB_NAME), true);
        String triggerGroup = resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_TRIGGER_GROUP), true);
        String triggerName = resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_TRIGGER_NAME), true);
        String cron = resolvePlaceholder(annotationAttributes.getString(HONEY_CRON_JOB_CRON), true);
        Date startTime = parseDateStr(resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_START), false), true);
        Date endTime = parseDateStr(resolvePlaceholder(annotationAttributes.getString(HONEY_JOB_END), false), false);
        int misfire = annotationAttributes.getNumber(HONEY_JOB_MISFIRE);

        BeanDefinition jobDetailBeanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(JobDetailFactoryBean.class)
                .addPropertyValue("group", jobGroup)
                .addPropertyValue("name", jobName)
                .addPropertyValue("jobClass", clazz)
                .addPropertyValue("durability", true)
                .addPropertyValue("requestsRecovery", true)
                .setScope(BeanDefinition.SCOPE_SINGLETON)
                .getBeanDefinition();
        registry.registerBeanDefinition(className, jobDetailBeanDefinition);

        if (registry instanceof BeanFactory) {
            JobDetail jobDetail = ((BeanFactory) registry).getBean(className, JobDetail.class);
            BeanDefinition triggerBeanDefinition = BeanDefinitionBuilder
                    .genericBeanDefinition(CronTriggerImpl.class)
                    .addPropertyValue("group", StringUtils.isEmpty(triggerGroup) ? jobGroup : triggerGroup)
                    .addPropertyValue("name", StringUtils.isEmpty(triggerName) ? jobName : triggerName)
                    .addPropertyValue("jobKey", jobDetail.getKey())
                    .addPropertyValue("startTime", startTime)
                    .addPropertyValue("endTime", endTime)
                    .addPropertyValue("cronExpression", cron)
                    .addPropertyValue("misfireInstruction", misfire)
                    .setScope(BeanDefinition.SCOPE_SINGLETON)
                    .getBeanDefinition();
            registry.registerBeanDefinition(assembleTriggerBeanName(className), triggerBeanDefinition);
        }
    }

    private String resolvePlaceholder(String text, boolean notAllowEmpty) {
        String resultText = text;
        if (StringUtils.isNotEmpty(resultText)) {
            resultText = this.environment.resolvePlaceholders(resultText);
        }
        if (notAllowEmpty && StringUtils.isEmpty(resultText)) {
            throw new IllegalStateException();
        }
        return resultText;
    }

    private Date parseDateStr(String dateStr, boolean nowWhenEmpty) {
        if (StringUtils.isEmpty(dateStr)) {
            return nowWhenEmpty ? new Date() : null;
        }
        SimpleDateFormat sdf = new SimpleDateFormat(HoneySchedulerConstants.HONEY_QUARTZ_DATE_FORMAT);
        sdf.setLenient(false);
        try {
            return new Date(sdf.parse(dateStr).getTime());
        } catch (Exception e) {
            throw new IllegalStateException();
        }
    }

    private String assembleTriggerBeanName(String jobClassName) {
        return jobClassName + ".trigger";
    }

}

代码稍微有点长,概括下来就是。

  1. 得到任务的Class对象
  2. 解析@HoneySimpleJob@HoneyCronJob注解的属性。其中@HoneySimpleJob和@HoneyCronJob注解支持占位符;
  3. 注册JobDetail
  4. JobDetail注册对应的Trigger

5. Scheduler创建

修改@EnableHoneyScheduling注解如下所示。

/**
 * 定时任务开启注解。<br/>
 * 在启动类上添加该注解以启动定时任务。
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({HoneySchedulerPropertiesConfiguration.class, HoneySchedulerConfiguration.class})
public @interface EnableHoneyScheduling {
}

多引入的HoneySchedulerConfiguration用于创建Scheduler,如下所示。

/**
 * 构建{@link Scheduler}调度器。
 */
@Configuration
public class HoneySchedulerConfiguration {

    @Bean
    @ConditionalOnMissingBean(SchedulerFactoryBean.class)
    public SchedulerFactoryBean SchedulerFactoryBean(List<JobDetail> jobDetails, List<Trigger> triggers,
                                                     Properties schedulerProperties) {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();

        // 设置实例名
        schedulerFactoryBean.setSchedulerName(schedulerProperties.getProperty(
                HONEY_QUARTZ_PROPERTIES_INSTANCE_NAME, HONEY_DEFAULT_SCHEDULER_INSTANCE_NAME));
        // 设置JobFactory
        schedulerFactoryBean.setJobFactory(honeyJobBeanFactory());
        // 注册JobDetail
        if (null != jobDetails && !jobDetails.isEmpty()) {
            schedulerFactoryBean.setJobDetails(jobDetails.toArray(new JobDetail[0]));
        }
        // 注册Trigger
        if (null != triggers && !triggers.isEmpty()) {
            schedulerFactoryBean.setTriggers(triggers.toArray(new Trigger[0]));
        }
        // 默认覆盖已经存在的定时任务
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        // 将配置设置给调度器
        schedulerFactoryBean.setQuartzProperties(schedulerProperties);

        return schedulerFactoryBean;
    }

    @Bean
    public HoneyJobBeanFactory honeyJobBeanFactory() {
        return new HoneyJobBeanFactory();
    }

}

创建Scheduler是基于SchedulerFactoryBean来完成,和使用Quartz原生的api一样,我们需要将所有创建出来的TriggerJobDetail,以及加载到的配置属性全部设置给Scheduler。注意到上述实现中额外设置了一个JobFactory,这个的作用是什么呢,下面先看一下其实现。

/**
 * 将任务对象注册为容器中的bean。<br/>
 * {@link SpringBeanJobFactory}能够将{@link JobDetail}注册为容器中的bean,
 * 但是依赖注入是构造函数的方式,在{@link HoneyJobBeanFactory}中主要是设置
 * 能够支持注解方式的依赖注入。
 */
public class HoneyJobBeanFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private AutowireCapableBeanFactory autowireCapableBeanFactory;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object jobInstance = super.createJobInstance(bundle);
        autowireCapableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }

}

首先说明一下SpringBeanJobFactorySpringBeanJobFactory会将每一个被执行的JobDetail注册为容器中的bean,此时我们的JobDetail是可以通过构造函数的方式来依赖注入其它bean,然后HoneyJobBeanFactory继承于SpringBeanJobFactory,让JobDetail能够额外的通过注解的方式来依赖注入其它bean

二. 功能演示

现在来搭建一个示例demo

首先honey-scheduler-examplepom文件如下所示。

<?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">
    <parent>
        <artifactId>honey-scheduler</artifactId>
        <groupId>com.honey</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>honey-scheduler-example</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.honey</groupId>
            <artifactId>honey-starter-scheduler</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

创建两个任务类,一个由@HoneySimpleJob注解修饰,一个由@HoneyCronJob注解修饰,如下所示。

@HoneySimpleJob(jobGroup = "testSimpleJobGroup", jobName = "testSimpleJobName" ,
        triggerGroup = "testSimpleTriggerGroup", triggerName = "testSimpleTriggerName",
        repeatInterval = "${test.simple.job.repeat-interval}",
        repeatCount = "${test.simple.job.repeat-count}")
public class TestSimpleJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {
        System.out.println(LocalDateTime.now().toString() + " Simple-Hello");
    }

}

@HoneyCronJob(jobGroup = "testCronJobGroup", jobName = "testCronJobName" ,
        triggerGroup = "testCronTriggerGroup", triggerName = "testCronTriggerName",
        cron = "*/10 * * * * ?")
public class TestCronJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {
        System.out.println(LocalDateTime.now().toString() + " Cron-Hello");
    }

}

启动类添加@EnableHoneyScheduling注解。

@EnableHoneyScheduling
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

最后是配置文件,如下所示。

org:
  quartz:
    jobStore:
      dataSource: qzDS
    dataSource:
      qzDS:
        driver: com.mysql.cj.jdbc.Driver
        URL: jdbc:mysql://localhost:3307/test
        user: root
        password: root
    scheduler:
      instanceName: local-demo

test:
  simple:
    job:
      repeat-interval: 10000
      repeat-count: -1

运行程序,打印如下。

2024-06-26T15:26:08.005 Simple-Hello
2024-06-26T15:26:10.010 Cron-Hello
2024-06-26T15:26:16.859 Simple-Hello
2024-06-26T15:26:20.007 Cron-Hello
2024-06-26T15:26:26.859 Simple-Hello
2024-06-26T15:26:30.008 Cron-Hello
2024-06-26T15:26:36.857 Simple-Hello
2024-06-26T15:26:40.008 Cron-Hello

定时任务是正常执行的。

总结

Starter的核心实现思路就是将Quartz框架需要的配置SchedulerTriggerJobDetail全部加载出来并完成设置,剩下的就完全由Quartz框架来提供功能。


大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈

你的名字-002