Quartz simple application (Springboot environment)

Keywords: Java Spring Boot Quartz

Requirements: in the project, it is inevitable to use scheduled tasks to perform some automatic operations. For simple scheduled tasks, you can add @ Schedule annotation to the method to execute scheduled tasks. However, if multiple modules of the same level execute the same method at different time points, there is no way to use @ Schedule only.
Scene: now a system has different user levels, such as krypton King vip, leopard head zero charge poor and poor vip comprehensive. Experience acquisition is different for users of different levels (experience acquisition methods are implemented at different time intervals), and benefits are also different (benefits are distributed at different times)
Quartz is another open source project of OpenSymphony open source organization in the field of Job scheduling. It is an open source task schedule management system completely developed by java. The "task schedule manager" is a system responsible for executing (or notifying) other software components when a predetermined (included in the schedule) time arrives.

1. Simple use of Quartz

1.1 first, you need to import related dependencies.

The spring boot version I use here is version 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 implement the interface Job required by the task component you want the scheduler to execute

There is only one method execute() in the Job interface, which writes your actual logic. Here we use simple output to see the effect.

public class UserLevelJob implements Job{
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // The actual logic is implemented here
        System.out.println("Start calling actual logic[" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +  "]");
    }
}

1.3 define Scheduler, JobDetail and Trigger

Scheduler is the main interface of Quartz Scheduler and represents an independent running container. The scheduler maintains a registry of JobDetails and triggers. Once registered, the scheduler is responsible for executing jobs when their associated triggers are triggered (when their scheduled time arrives).
We need to create a custom Scheduler class and create a Scheduler in it

@Component
public class UserLevelScheduler {

    public void scheduleJob() throws SchedulerException {
        // Create Scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
    }
}

The JobDetail object is created by the client program (your program) when adding a job to the scheduler. It contains various property settings of a job and a JobDataMap for storing job instance status information. The JobDetail instance is created through the JobBuilder class and is bound to the job defined above when it is created.

@Component
public class UserLevelScheduler {

    public void scheduleJob() throws SchedulerException {
        // To create a Scheduler, see the above steps

        // Create a JobDetail instance and bind it to the Job class (Job execution content)
        String jobName = "userLevelJob";
        JobDetail jobDetail = JobBuilder.newJob(UserLevelJob.class).withIdentity(jobName, "group1").build();
    }
}

Trigger is used to trigger the execution of a job. When you are ready to schedule a job, you create an instance of trigger and set the scheduling related properties. Quartz comes with various types of triggers. The most commonly used are SimpleTrigger and CronTrigger. We use CronTrigger here. First build a cron expression that we need to perform tasks into a CronScheduleBuilder instance, and then use TriggerBuilder and CronScheduleBuilder to build CronTrigger

@Component
public class UserLevelScheduler {

    public void scheduleJob() throws SchedulerException {
        // Create Scheduler
        // Create a JobDetail instance and bind it to the Job class (Job execution content). See the above steps

        // Build a Trigger instance and execute it according to the cron expression
        String triggerName = "userLevelTrigger";
        String cron = "*/10 * * * * ?";
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerName, "group1").withSchedule(scheduleBuilder).build();
    }
}

After the build is completed, configure our JobDetail and Trigger to the Scheduler and start the scheduling

@Component
public class UserLevelScheduler {

    public void scheduleJob() throws SchedulerException {
        // Create Scheduler
        // Create a JobDetail instance and bind it to the Job class (Job execution content)
        // Build a Trigger instance and execute it according to the cron expression. See the above steps
        // Scheduling
        scheduler.scheduleJob(jobDetail,cronTrigger);
        scheduler.start();        
    }
}

1.4 testing

After the Quartz simple application environment is set up, we need to call the scheduleJob() method of the scheduler UserLevelScheduler to start it. Write a test controller here

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

After calling the controller interface, the following contents will appear in the log, indicating that our scheduler has been officially started

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.

Next, wait for the time specified by the cron expression to execute the actual logic

2. Integration of demand and other elements

2.1 related configuration

According to the requirements, the same type of methods at different user levels have different timing execution times. Here, a configuration file is created for users at each level, taking vip as an example. Here, the configuration file is placed under resources

# vip.properties
userLevel=vip
experienceCron=
welfareCron=

Then, define the user level to be enabled, and then read the contents of the above used configuration file through the definition here. Here we write in application.yml

userLevels: vip,poor

2.2 read configuration

First, create a new configuration class, read the defined userLevels from the main configuration file, and then read the contents of the relevant configuration file according to these values and encapsulate them in Properties. Finally, all the Properties are defined in the Bean, and these Properties can be used by injection in the later part.

@Configuration
public class UserLevelConfig {

    @Value("${userLevels}")
    // Read the list of user levels to be opened from the main configuration file
    private String userLevels;

    @Autowired
    ResourceLoader resourceLoader;

    @Bean
    // Inject the user level configuration information that needs to be enabled as a 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) {// Operate for each user level that is turned on
            Properties properties = new Properties();
            // Get the name of the configuration file, which is placed under resources by default
            String propertiesFilePath = "classpath:" + u + ".properties";
            // Read configuration file
            Resource resource = resourceLoader.getResource(propertiesFilePath);
            InputStream inputStream = resource.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            // Load into properties
            properties.load(bufferedReader);
            // If the reading is successful, it will be added to the list
            userLevelPropertiesList.add(properties);
        }

        return userLevelPropertiesList;
    }
}

Here, we can write an operation class, inject the obtained Properties, and obtain its corresponding Properties through the passed in user type value

@Component
public class UserLevelPropertiesOperation {

    @Autowired
    @Qualifier("userLevelPropertiesList")
    // bean to get user configuration list
    private List<Properties> userLevelPropertiesList;

    /**
     * Obtain the corresponding configuration information according to the user level
     * @param userLevel Incoming user level
     * @return Return configuration information
     */
    public Properties getUserLevelProperties(String userLevel){
        if(StringUtils.isEmpty(userLevel)) {
            System.out.println("Invalid value passed in");
            return null;
        }
        // Loop judgment and return matching configuration information
        for (Properties properties : userLevelPropertiesList) {
            if(userLevel.equals(properties.getProperty("userLevel"))){
                return properties;
            }
        }
        return null;
    }

    /**
     * Obtain the corresponding configuration information according to the user level
     * @param userLevel Incoming user level
     * @return Return configuration information of Map type
     */
    public Map<String, String> getUserLevelPropertiesMap(String userLevel){
        Properties properties = getUserLevelProperties(userLevel);
        if(properties == null){
            return null;
        }
        return (Map) properties;
    }

}

2.3 configuring event listening

In 1.4, we use an interface to start the task scheduler. Here, we can write an event listener. When the project startup ApplicationContext is initialized or refreshed, we can start the task scheduler

@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 transformation of task Scheduler

For each cron task, the creation steps are similar. We extract the repetitive content into a method

@Component
public class UserLevelScheduler {

    @Autowired
    @Qualifier("userLevelPropertiesList")
    // bean to get user configuration list
    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;
        // Create a JobDetail instance and bind it to the Job class (Job execution content)
        JobDetail jobDetail = JobBuilder.newJob(UserLevelJob.class).withIdentity(jobName, GroupName).build();
        
        // Build a Trigger instance and execute it according to the cron expression
        String triggerName = level + "Trigger-" + type;
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerName, GroupName).withSchedule(scheduleBuilder).build();
        // Scheduling
        scheduler.scheduleJob(jobDetail,cronTrigger);
    }
}

When scheduling tasks, we can add properties or configuration to the job instance. The JobDataMap can contain unlimited (serialized) data objects, and the data in it can be used when the job instance is executed; JobDataMap is an implementation of Java Map interface, which adds some methods to facilitate access to basic types of data.

    public void setJob(Scheduler scheduler, String jobNameSuffix, String cron, String type, String level) throws SchedulerException {
        // Create a JobDetail instance and bind it to the Job class (Job execution content)
        
        // Set the information to be passed to JobDetail
        jobDetail.getJobDataMap().put("userLevel", level);
        jobDetail.getJobDataMap().put("type", type);
        
        // Build a Trigger instance and execute it according to the cron expression
        // Scheduling
    }

Then we can create different jobdetails and their corresponding triggers for the configuration of each user level, and then schedule the tasks

@Component
public class UserLevelScheduler {

    @Autowired
    @Qualifier("userLevelPropertiesList")
    // bean to get user configuration list
    private List<Properties> userLevelPropertiesList;

    public void scheduleJob() throws SchedulerException {
        // Create 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 transformation task Job

In the task scheduler, we set some parameters in JobDetail, where we can get the set values

public class UserLevelJob implements Job{
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // Get the information passed by JobDetail
        String userLevel = context.getJobDetail().getJobDataMap().getString("userLevel");
        String type = context.getJobDetail().getJobDataMap().getString("type");
        // The actual logic is implemented here
        System.out.println(userLevel + "Start calling actual" + type + "logic[" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +  "]");
    }
}

reference material:
w3cschool Quartz official document.
Timed task framework Quartz - (I) introduction to Quartz and Demo construction

Posted by vietnamese on Thu, 04 Nov 2021 11:18:21 -0700