Quartz简单应用(Springboot环境)

需求: 在项目中免不了会使用定时任务来执行一些自动化的操作。简单的定时任务可以在方法上加上@Schedule的注解来执行定时任务。但是如果有多个同级的模块在不同的时间点执行同一个方法,就没办法仅仅使用@Schedule来执行了。
侃场景: 现在某一个系统有不同的用户等级,如氪金大佬-vip、豹子头零充-poor和穷逼vip-complimentary。不同等级的用户经验获取情况不一样(不同的时间间隔执行获得经验方法),获得福利的情况也不一样(发放福利的时间不同)
Quartz 是OpenSymphony开源组织在Job scheduling领域又一个开源项目,是完全由java开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。

1、Quartz的简单使用

1.1 首先需要导入相关依赖。

我这里使用的springboot版本是2.1.4版本的

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.1.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.1.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
</dependency>

1.2 实现你想要调度器执行的任务组件需要实现的接口Job

Job接口中只有一个方法execute(),这里面编写你实际的逻辑,这里我们用简单的输出来看效果。

public class UserLevelJob implements Job{
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 这里进行实际逻辑的实现
        System.out.println("开始调用实际逻辑[" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +  "]");
    }
}

1.3 定义Scheduler,JobDetail和Trigger

Scheduler 是Quartz Scheduler的主要接口,代表一个独立运行容器。调度程序维护JobDetails和触发器的注册表。 一旦注册,调度程序负责执行作业,当他们的相关联的触发器触发(当他们的预定时间到达时)。
我们需要新建一个自定义的Scheduler类,并在其中创建一个调度器

@Component
public class UserLevelScheduler {

    public void scheduleJob() throws SchedulerException {
        // 创建调度器Scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
    }
}

JobDetail 对象是在将 job 加入 scheduler 时,由客户端程序(你的程序)创建的。它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap。JobDetail实例是通过JobBuilder类创建的,并且在创建的时候绑定我们在上面定义的job。

@Component
public class UserLevelScheduler {

    public void scheduleJob() throws SchedulerException {
        // 创建调度器Scheduler,见以上步骤

        // 创建JobDetail实例,并与Job类绑定(Job执行内容)
        String jobName = "userLevelJob";
        JobDetail jobDetail = JobBuilder.newJob(UserLevelJob.class).withIdentity(jobName, "group1").build();
    }
}

Trigger 用于触发 Job 的执行。当你准备调度一个 job 时,你创建一个 Trigger 的实例,然后设置调度相关的属性。Quartz 自带了各种不同类型的 Trigger,最常用的主要是 SimpleTrigger 和 CronTrigger。我们这里使用CronTrigger。先将我们需要执行任务的cron表达式构建一个CronScheduleBuilder实例,然后使用TriggerBuilder和CronScheduleBuilder构建出CronTrigger

@Component
public class UserLevelScheduler {

    public void scheduleJob() throws SchedulerException {
        // 创建调度器Scheduler
        // 创建JobDetail实例,并与Job类绑定(Job执行内容),见以上步骤

        // 构建Trigger实例,根据cron表达式进行执行
        String triggerName = "userLevelTrigger";
        String cron = "*/10 * * * * ?";
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerName, "group1").withSchedule(scheduleBuilder).build();
    }
}

构建完成后,将我们的JobDetail和Trigger配置到Scheduler并且启动调度

@Component
public class UserLevelScheduler {

    public void scheduleJob() throws SchedulerException {
        // 创建调度器Scheduler
        // 创建JobDetail实例,并与Job类绑定(Job执行内容)
        // 构建Trigger实例,根据cron表达式进行执行,见以上步骤
        // 进行调度
        scheduler.scheduleJob(jobDetail,cronTrigger);
        scheduler.start();        
    }
}

1.4 测试

Quartz简单应用环境搭建好之后,我们需要调用调度器UserLevelScheduler的scheduleJob()方法来开启,这里随便写一个测试controller

@RestController
public class TestController {
    @Autowired
    private UserLevelScheduler scheduler;
    @GetMapping("/jobTest")
    public void jobTest() throws SchedulerException {
        scheduler.scheduleJob();
    }
}

调用controller的接口后,日志会出现以下内容,表示我们的调度器已经正式开启了

INFO 22424 --- [           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.

INFO 22424 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
INFO 22424 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler version: 2.3.0
INFO 22424 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.

接下来就等待cron表达式指定的时间执行实际逻辑即可

2、需求其他要素的整合

2.1 相关配置

按照需求的要求,不同的用户等级的相同类型的方法有不同的定时执行时间,这里给每个等级的用户建一个配置文件,以vip为例。这里配置文件放在resources下

# vip.properties
userLevel=vip
experienceCron=
welfareCron=

然后定义需要启用的用户等级,后续会通过这里的定义读取到上面的对用的配置文件的内容。这里我们写在application.yml中

userLevels: vip,poor

2.2 读取配置

首先新建一个配置类,并从主配置文件中读取定义到的userLevels,然后根据这些值读取相关的配置文件的内容并封装到Propertits中。最后把所有的的Properties定义到Bean中,在后面的部分可以通过注入使用这些Properties。

@Configuration
public class UserLevelConfig {

    @Value("${userLevels}")
    // 从主配置文件中读取需要开启的用户级别列表
    private String userLevels;

    @Autowired
    ResourceLoader resourceLoader;

    @Bean
    // 将需要开启的用户级别配置信息作为Bean注入
    public List<Properties> userLevelPropertiesList() throws IOException{
        List<String> userLevelList = Arrays.asList(userLevels.split(","));
        if(userLevelList.size() == 0){
            return null;
        }

        List<Properties> userLevelPropertiesList = new ArrayList<>();
        for (String u : userLevelList) {// 针对开启的每个用户级别进行操作
            Properties properties = new Properties();
            // 获得配置文件名称,默认放在resources下
            String propertiesFilePath = "classpath:" + u + ".properties";
            // 读取配置文件
            Resource resource = resourceLoader.getResource(propertiesFilePath);
            InputStream inputStream = resource.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            // 加载到properties中
            properties.load(bufferedReader);
            // 读取成功则加到列表中
            userLevelPropertiesList.add(properties);
        }

        return userLevelPropertiesList;
    }
}

这里我们可以写个操作类,注入获取到的Properties并通过传入的用户类型值来获得其对应的Properties

@Component
public class UserLevelPropertiesOperation {

    @Autowired
    @Qualifier("userLevelPropertiesList")
    // 获得用户配置列表的bean
    private List<Properties> userLevelPropertiesList;

    /**
     * 根据用户级别获得对应的配置信息
     * @param userLevel 传入用户级别
     * @return 返回配置信息
     */
    public Properties getUserLevelProperties(String userLevel){
        if(StringUtils.isEmpty(userLevel)) {
            System.out.println("传入无效值");
            return null;
        }
        // 循环判断,返回匹配的配置信息
        for (Properties properties : userLevelPropertiesList) {
            if(userLevel.equals(properties.getProperty("userLevel"))){
                return properties;
            }
        }
        return null;
    }

    /**
     * 根据用户级别获得对应的配置信息
     * @param userLevel 传入用户级别
     * @return 返回Map类型的配置信息
     */
    public Map<String, String> getUserLevelPropertiesMap(String userLevel){
        Properties properties = getUserLevelProperties(userLevel);
        if(properties == null){
            return null;
        }
        return (Map) properties;
    }

}

2.3 配置事件监听

在1.4中我们使用一个接口来开启任务调度器,在这里我们可以写一个事件监听,在项目启动ApplicationContext 被初始化或刷新时,就可以启动任务调度器

@Configuration
public class UserLevelSchedulerListener implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private UserLevelScheduler scheduler;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        try {
            scheduler.scheduleJob();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

2.4 改造任务调度器Scheduler

针对于每个cron任务,其创建步骤都是类似的,我们将这个重复性的内容抽取成一个方法

@Component
public class UserLevelScheduler {

    @Autowired
    @Qualifier("userLevelPropertiesList")
    // 获得用户配置列表的bean
    private List<Properties> userLevelPropertiesList;

    public void scheduleJob() throws SchedulerException {
    }

    public void setJob(Scheduler scheduler, String jobNameSuffix, String cron, String type, String level) throws SchedulerException {
        String jobName = jobNameSuffix + "-" + type;
        String GroupName = "group-" + type;
        // 创建JobDetail实例,并与Job类绑定(Job执行内容)
        JobDetail jobDetail = JobBuilder.newJob(UserLevelJob.class).withIdentity(jobName, GroupName).build();
        
        // 构建Trigger实例,根据cron表达式进行执行
        String triggerName = level + "Trigger-" + type;
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerName, GroupName).withSchedule(scheduleBuilder).build();
        // 进行调度
        scheduler.scheduleJob(jobDetail,cronTrigger);
    }
}

在我们调度任务的时候,可以给job实例增加属性或配置。JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。

    public void setJob(Scheduler scheduler, String jobNameSuffix, String cron, String type, String level) throws SchedulerException {
        // 创建JobDetail实例,并与Job类绑定(Job执行内容)
        
        // 向JobDetail设置需要传递的信息
        jobDetail.getJobDataMap().put("userLevel", level);
        jobDetail.getJobDataMap().put("type", type);
        
        // 构建Trigger实例,根据cron表达式进行执行
        // 进行调度
    }

然后我们就可以针对每个用户等级的配置来创建不同的JobDetail及其相对应的Trigger,然后进行任务调度

@Component
public class UserLevelScheduler {

    @Autowired
    @Qualifier("userLevelPropertiesList")
    // 获得用户配置列表的bean
    private List<Properties> userLevelPropertiesList;

    public void scheduleJob() throws SchedulerException {
        // 创建调度器Scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();

        for (Properties properties : userLevelPropertiesList) {
            String level = (String) properties.get("userLevel");
            String jobName = level + "Job";

            if (properties.get("experienceCron") != null && !"".equals(properties.get("experienceCron").toString().trim())){
                setJob(scheduler, jobName, properties.get("experienceCron").toString(), "experience", level);
            }
            if (properties.get("welfareCron") != null && !"".equals(properties.get("welfareCron").toString().trim())){
                setJob(scheduler, jobName, properties.get("welfareCron").toString(), "welfare", level);
            }

            scheduler.start();
        }
    }
}

2.5 改造任务Job

在任务调度器中,我们在JobDetail中设置了一些参数,可以在这里获取到设置的值

public class UserLevelJob implements Job{
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 获取JobDetail传递的信息
        String userLevel = context.getJobDetail().getJobDataMap().getString("userLevel");
        String type = context.getJobDetail().getJobDataMap().getString("type");
        // 这里进行实际逻辑的实现
        System.out.println(userLevel + "开始调用实际" + type + "逻辑[" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +  "]");
    }
}

参考资料:
w3cschool Quartz官方文档.
定时任务框架Quartz-(一)Quartz入门与Demo搭建

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值