Dynamic management of scheduled tasks based on Spingboot+maven+Quartz

Posted by phpmixx on Tue, 18 Jan 2022 09:50:59 +0100

demo of dynamic management of scheduled tasks based on Spingboot+maven+Quartz

describe

This demo is a demo for dynamically creating scheduled tasks.
demo content: dynamically manage scheduled tasks (run, pause, delete, query status, update expression, etc.)
demo goal: it mainly supports the dynamic creation and management of multiple scheduled tasks according to different parameters or different job task classes.
demo principle: create a scheduled task factory, give the manager management class, and control the operation of the scheduled task through the id of the scheduled task and its group name (groupId).
Main contents of demo:
Note: 1. You can customize the job task and bind the customized job in the job entity creation (JobDetail), but this function needs to define multiple jobs
2. You can call subsequent operations in the execute method of each job by passing parameters.

implementation

<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>quartz</groupId>
	<artifactId>quartzDemo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>quartzDemo</name>
	<url>http://maven.apache.org</url>


	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<maven.compiler.source>${java.version}</maven.compiler.source>
		<maven.compiler.target>${java.version}</maven.compiler.target>
	</properties>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.5.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

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

		<!--quartz Timing scheduling dependency-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-quartz</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.2.0</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.10</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.4</version>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.75</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<fork>true</fork>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

Remember to update Maven after modification, otherwise an error will be reported, as shown in the figure below.

  • 3. Configure quartz to get the instance configuration of the job
    This object will be automatically injected into Spring for us, which also belongs to the category of Spring technology.
    But why do I need to write this class again? In the demo I wrote, you can delete this class and find that the program can run correctly, but why do I add it.
    During the creation of jobDetail, the instantiation of Job object will be executed. At this time, it must not be feasible for us to inject spring things, so we need this class.
    JobFactory.java:
@Component
public class JobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        //Call the method of the parent class
        Object jobInstance = super.createJobInstance(bundle);
        //Inject
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}
  • 4. Configure factory configuration for quartz
    New configuration document quartzconfiguration Java, add factory configuration, and create an instance of Scheduler:
@Configuration
public class QuartzConfigration {

    @Autowired
    private JobFactory jobFactory;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        try {
            schedulerFactoryBean.setOverwriteExistingJobs(true);
            schedulerFactoryBean.setQuartzProperties(quartzProperties());
            schedulerFactoryBean.setJobFactory(jobFactory);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return schedulerFactoryBean;
    }

    // Specify quartz Properties to configure related properties in the configuration file
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/config/quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

    // Create schedule
    @Bean(name = "scheduler")
    public Scheduler scheduler() {
        return schedulerFactoryBean().getScheduler();
    }
}
  • 5. Add custom configuration (blank file can be created)
    To add a configuration file, you can use a blank quartz Properties file.
# Fixed prefix org quartz
# It is mainly divided into scheduler, threadPool, jobStore, plugin, etc
#
#
#org.quartz.scheduler.instanceName = DefaultQuartzScheduler

#If you want the Quartz Scheduler to export itself as a server through RMI, set the "rmi.export" flag to true.
#'org. In the same configuration file quartz. scheduler. rmi. Export 'and' org quartz. scheduler. rmi. Proxy 'specifying a value of' true 'is meaningless. If you do, the' export 'option will be ignored
org.quartz.scheduler.rmi.export = false
#If you want to connect (use) the scheduler of the remote service, set the "org.quartz.scheduler.rmi.proxy" flag to true. You must also specify the host and port of the RMI registry process - typically "localhost" port 1099.
org.quartz.scheduler.rmi.proxy = false

org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

# When instantiating ThreadPool, the thread class used is SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

# threadCount and threadPriority will be injected into the ThreadPool instance as setter s
# Concurrent number if you only have a few jobs, one thread can be triggered several times a day. If you have thousands of jobs and many jobs per minute, it takes between 50-100 for such a long time
# Only numbers between 1 and 100 are very practical
org.quartz.threadPool.threadCount = 5

# Priority defaults to 5
org.quartz.threadPool.threadPriority = 5

#It can be "true" or "false", and the default is false.
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

#Before being considered "misfired", the scheduler will "tolerate (tolerate)" a Triggers (flip flop) to pass the next millisecond number of its startup time. The default value (if you do not enter this property in the configuration) is 60000 (60 seconds)
org.quartz.jobStore.misfireThreshold = 5000

# By default, it is stored in memory. RAMJobStore is fast and lightweight, but all scheduling information will be lost when the process terminates.
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

#Persistence
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

#You need to select a DriverDelegate for the JobStore to use. DriverDelegate is responsible for performing any JDBC work that may be required for a particular database.
# Stdjdbc delegate is a delegate that uses "vanilla" JDBC code (and SQL statements) to perform its work for fully JDBC compliant drivers
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate

#The "org.quartz.jobStore.useProperties" configuration parameter can be set to "true" (false by default) to instruct the JDBC jobstore to take all values in JobDataMaps as strings,
# Therefore, more complex objects can be stored as name value pairs rather than in BLOB columns in their serialized form. In the long run, this is safer because you avoid serializing non String classes into BLOB class versions.
org.quartz.jobStore.useProperties=true

#Table prefix
org.quartz.jobStore.tablePrefix = QRTZ_

#You need to set which DataSource the JobStore should use.
org.quartz.jobStore.dataSource = qzDS

org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver

org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC

org.quartz.dataSource.qzDS.user = root

org.quartz.dataSource.qzDS.password = 123456

org.quartz.dataSource.qzDS.maxConnections = 10

#Set to "true" to turn on clustering. If you have multiple Quartz instances using the same set of database tables, this property must be set to "true", otherwise you will encounter corruption.
#org.quartz.jobStore.isClustered=false

#To configure a cluster, see: https://www.w3cschool.cn/quartz_doc/quartz_doc-3x7u2doc.html
  • 6. Write management class QuartzManager
    Operate the scheduled task by obtaining the Scheduler example.
    Existing operations include:
    (1) Add task;
    (2) Suspend the task;
    (3) Recovery task;
    (4) Delete task;
    (5) Obtain the task list in all plans;
    (6) List of all running jobs (only when the job task is executing its internal process is it running);
    (7) Carry out the task immediately;
    (8) Update the time expression of task timing;
@Service
public class QuartzManager {
    public final Logger log = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private Scheduler scheduler;

    /**
     * Add task
     * @param object The incoming data can be transferred to the execution content of the job. The specific application of the content is to datetimejob Check in executeinternal
     */
    public void addJob(JSONObject object) {
        try {
        	JobDataMap jobDataMap=new JobDataMap();
        	jobDataMap.put("data",object.getString("jobname")+object.getString("jobgroup")+object.getString("jobCronExpression"));
        	
        	
//        	You can customize job tasks and bind customized jobs in job entity creation (JobDetail), but this function needs to define multiple jobs by itself
//          //The package address of the incoming class type: for example: com utils. QuartzManager
//          Class<? extends Job> jobClass = (Class<? extends Job>) (Class.forName(className).newInstance()
//                  .getClass());
//          JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(task.getJobName(), task.getJobGroup()) / / task name and group constitute task key
//                  .build();
        	
        	
        	//You can call subsequent operations in the execute method of each job by passing parameters
        	// Indicate the name of the job, the name of the group, and the bound job class
            JobDetail jobDetail = JobBuilder.newJob(DateTimeJob.class)
            		.withIdentity(object.getString("jobname"), object.getString("jobgroup"))// Task name and group constitute task key
            		.usingJobData(jobDataMap) //Store data
            		.build();
            // Define scheduling trigger rules
            // Using the cornTrigger rule
            Trigger trigger = TriggerBuilder.newTrigger().
            		withIdentity(object.getString("jobname"), object.getString("jobgroup"))// Trigger key
                    .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))
                    .withSchedule(CronScheduleBuilder.cronSchedule(object.getString("jobCronExpression")))
                    .build();
            // Register jobs and triggers in task scheduling
            scheduler.scheduleJob(jobDetail, trigger);
            // start-up
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Pause a job task
     * @param jobName Task name
     * @param jobGroup Task group
     * @throws SchedulerException
     */
	public void pauseJob(String jobName, String jobGroup) throws SchedulerException {
		JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
		scheduler.pauseJob(jobKey);
	}
	
	/**
	 * Restore a job
	 * @param jobName Task name
     * @param jobGroup Task group
     * @throws SchedulerException
	 */
	public void resumeJob(String jobName, String jobGroup) throws SchedulerException {
		JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
		scheduler.resumeJob(jobKey);
	}

	/**
	 * Delete a job
	 * @param jobName Task name
     * @param jobGroup Task group
     * @throws SchedulerException
	 */
	public void deleteJob(String jobName, String jobGroup) throws SchedulerException {
		JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
		scheduler.deleteJob(jobKey);

	}
	
	/**
	 * Get a list of all scheduled tasks
	 * 
	 * @return
	 * @throws SchedulerException
	 */
	public List<JSONObject> getAllJob() throws SchedulerException {
		GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
		Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
		List<JSONObject> jobList = new ArrayList<JSONObject>();
		for (JobKey jobKey : jobKeys) {
			List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
			for (Trigger trigger : triggers) {
				JSONObject job = new JSONObject();
				job.put("jobName", jobKey.getName());
				job.put("jobGroup", jobKey.getGroup());
				job.put("jobDescription", "trigger:"+trigger.getKey());
				Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
				job.put("jobStatus", triggerState.name());
				if (trigger instanceof CronTrigger) {
					CronTrigger cronTrigger = (CronTrigger) trigger;
					String cronExpression = cronTrigger.getCronExpression();
					job.put("jobCronExpression", cronExpression);
				}
				jobList.add(job);
			}
		}
		return jobList;
	}
	
	/**
	 * All running job s
	 * 
	 * @return
	 * @throws SchedulerException
	 */
	public List<JSONObject> getRunningJob() throws SchedulerException {
		List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
		List<JSONObject> jobList = new ArrayList<JSONObject>(executingJobs.size());
		for (JobExecutionContext executingJob : executingJobs) {
			JSONObject job = new JSONObject();
			JobDetail jobDetail = executingJob.getJobDetail();
			JobKey jobKey = jobDetail.getKey();
			Trigger trigger = executingJob.getTrigger();
			job.put("jobName", jobKey.getName());
			job.put("jobGroup", jobKey.getGroup());
			job.put("jobDescription", "trigger:"+trigger.getKey());
			Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
			job.put("jobStatus", triggerState.name());
			if (trigger instanceof CronTrigger) {
				CronTrigger cronTrigger = (CronTrigger) trigger;
				String cronExpression = cronTrigger.getCronExpression();
				job.put("jobCronExpression", cronExpression);
			}
			jobList.add(job);
		}
		return jobList;
	}
    

	/**
	 * Execute job now
	 * @param jobName Task name
     * @param jobGroup Task group
     * @throws SchedulerException
	 */
	public void runJobNow(String jobName, String jobGroup) throws SchedulerException {
		JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
		scheduler.triggerJob(jobKey);
	}

	/**
	 * Update job time expression
	 * @param jobName Task name
	 * @param jobGroup Task group
	 * @param cronExpression Timed cron expression
	 * @throws SchedulerException
	 */
	public void updateJobCron(String jobName, String jobGroup, String cronExpression) throws SchedulerException {

		TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);

		CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

		CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

		trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

		scheduler.rescheduleJob(triggerKey, trigger);
	   }
}
  • 7. Complete the job class
    service: TaskService.java
@Service
public class TaskService{

    @Autowired
    QuartzManager quartzManager;

    public void initSchedule() throws SchedulerException {
        // Here you can get the task information data
        //List<TaskDO> jobList = taskMapper.list();
        for (int i=8; i<11;i++) {
        	JSONObject object = new JSONObject();
        	object.put("jobname", "name"+i);
        	object.put("jobgroup", "group"+i);
        	object.put("jobCronExpression", "0/"+i+" * * * * ?");
        	
            quartzManager.addJob(object);
            }
     }
 }

controller: TestController.java

@RestController
public class TestController {

		@Resource
		private TaskService taskService;
	
		@Autowired
    	QuartzManager quartzManager;
	
		@GetMapping(value = "/testaddjob")
		public void test() {
			try {
				taskService.initSchedule();
			} catch (SchedulerException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

In the example, only the use cases of running timing are written, and the other use cases can call the methods in manager

Topics: Java Maven Spring Boot Quartz