<dependency>
<groupId>org.quartz-schedulergroupId>
<artifactId>quartzartifactId>
<exclusions>
<exclusion>
<groupId>com.mchangegroupId>
<artifactId>c3p0artifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
配置数据库连接和mybatis-plus。
spring:
datasource:
url: jdbc:mysql://localhost:3306/quartz?serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
mybatis-plus:
#要扫描的xml路径
mapper-locations: classpath:mapping/*.xml
#要扫描的包路径
type-aliases-package: com.quartz.domain
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
#关缓存
cache-enabled: false
共计12张表,只有最后一张表需要使用者自己创建,其他以qrtz
开头的表使用者不必操作。
/*
Navicat MySQL Data Transfer
Source Server : root
Source Server Version : 80018
Source Host : localhost:3306
Source Database : quartz
Target Server Type : MYSQL
Target Server Version : 80018
File Encoding : 65001
Date: 2020-04-23 17:47:42
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for qrtz_blob_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_blob_triggers`;
CREATE TABLE `qrtz_blob_triggers` (
`sched_name` varchar(120) NOT NULL,
`trigger_name` varchar(200) NOT NULL,
`trigger_group` varchar(200) NOT NULL,
`blob_data` blob,
PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
CONSTRAINT `qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for qrtz_calendars
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_calendars`;
CREATE TABLE `qrtz_calendars` (
`sched_name` varchar(120) NOT NULL,
`calendar_name` varchar(200) NOT NULL,
`calendar` blob NOT NULL,
PRIMARY KEY (`sched_name`,`calendar_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for qrtz_cron_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_cron_triggers`;
CREATE TABLE `qrtz_cron_triggers` (
`sched_name` varchar(120) NOT NULL,
`trigger_name` varchar(200) NOT NULL,
`trigger_group` varchar(200) NOT NULL,
`cron_expression` varchar(200) NOT NULL,
`time_zone_id` varchar(80) DEFAULT NULL,
PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
CONSTRAINT `qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for qrtz_fired_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_fired_triggers`;
CREATE TABLE `qrtz_fired_triggers` (
`sched_name` varchar(120) NOT NULL,
`entry_id` varchar(95) NOT NULL,
`trigger_name` varchar(200) NOT NULL,
`trigger_group` varchar(200) NOT NULL,
`instance_name` varchar(200) NOT NULL,
`fired_time` bigint(13) NOT NULL,
`sched_time` bigint(13) NOT NULL,
`priority` int(11) NOT NULL,
`state` varchar(16) NOT NULL,
`job_name` varchar(200) DEFAULT NULL,
`job_group` varchar(200) DEFAULT NULL,
`is_nonconcurrent` varchar(1) DEFAULT NULL,
`requests_recovery` varchar(1) DEFAULT NULL,
PRIMARY KEY (`sched_name`,`entry_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for qrtz_job_details
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_job_details`;
CREATE TABLE `qrtz_job_details` (
`sched_name` varchar(120) NOT NULL,
`job_name` varchar(200) NOT NULL,
`job_group` varchar(200) NOT NULL,
`description` varchar(250) DEFAULT NULL,
`job_class_name` varchar(250) NOT NULL,
`is_durable` varchar(1) NOT NULL,
`is_nonconcurrent` varchar(1) NOT NULL,
`is_update_data` varchar(1) NOT NULL,
`requests_recovery` varchar(1) NOT NULL,
`job_data` blob,
PRIMARY KEY (`sched_name`,`job_name`,`job_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for qrtz_locks
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_locks`;
CREATE TABLE `qrtz_locks` (
`sched_name` varchar(120) NOT NULL,
`lock_name` varchar(40) NOT NULL,
PRIMARY KEY (`sched_name`,`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for qrtz_paused_trigger_grps
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_paused_trigger_grps`;
CREATE TABLE `qrtz_paused_trigger_grps` (
`sched_name` varchar(120) NOT NULL,
`trigger_group` varchar(200) NOT NULL,
PRIMARY KEY (`sched_name`,`trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for qrtz_scheduler_state
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_scheduler_state`;
CREATE TABLE `qrtz_scheduler_state` (
`sched_name` varchar(120) NOT NULL,
`instance_name` varchar(200) NOT NULL,
`last_checkin_time` bigint(13) NOT NULL,
`checkin_interval` bigint(13) NOT NULL,
PRIMARY KEY (`sched_name`,`instance_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for qrtz_simple_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_simple_triggers`;
CREATE TABLE `qrtz_simple_triggers` (
`sched_name` varchar(120) NOT NULL,
`trigger_name` varchar(200) NOT NULL,
`trigger_group` varchar(200) NOT NULL,
`repeat_count` bigint(7) NOT NULL,
`repeat_interval` bigint(12) NOT NULL,
`times_triggered` bigint(10) NOT NULL,
PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for qrtz_simprop_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_simprop_triggers`;
CREATE TABLE `qrtz_simprop_triggers` (
`sched_name` varchar(120) NOT NULL,
`trigger_name` varchar(200) NOT NULL,
`trigger_group` varchar(200) NOT NULL,
`str_prop_1` varchar(512) DEFAULT NULL,
`str_prop_2` varchar(512) DEFAULT NULL,
`str_prop_3` varchar(512) DEFAULT NULL,
`int_prop_1` int(11) DEFAULT NULL,
`int_prop_2` int(11) DEFAULT NULL,
`long_prop_1` bigint(20) DEFAULT NULL,
`long_prop_2` bigint(20) DEFAULT NULL,
`dec_prop_1` decimal(13,4) DEFAULT NULL,
`dec_prop_2` decimal(13,4) DEFAULT NULL,
`bool_prop_1` varchar(1) DEFAULT NULL,
`bool_prop_2` varchar(1) DEFAULT NULL,
PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
CONSTRAINT `qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for qrtz_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_triggers`;
CREATE TABLE `qrtz_triggers` (
`sched_name` varchar(120) NOT NULL,
`trigger_name` varchar(200) NOT NULL,
`trigger_group` varchar(200) NOT NULL,
`job_name` varchar(200) NOT NULL,
`job_group` varchar(200) NOT NULL,
`description` varchar(250) DEFAULT NULL,
`next_fire_time` bigint(13) DEFAULT NULL,
`prev_fire_time` bigint(13) DEFAULT NULL,
`priority` int(11) DEFAULT NULL,
`trigger_state` varchar(16) NOT NULL,
`trigger_type` varchar(8) NOT NULL,
`start_time` bigint(13) NOT NULL,
`end_time` bigint(13) DEFAULT NULL,
`calendar_name` varchar(200) DEFAULT NULL,
`misfire_instr` smallint(2) DEFAULT NULL,
`job_data` blob,
PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
KEY `sched_name` (`sched_name`,`job_name`,`job_group`),
CONSTRAINT `qrtz_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `job_name`, `job_group`) REFERENCES `qrtz_job_details` (`sched_name`, `job_name`, `job_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for sys_job
-- ----------------------------
DROP TABLE IF EXISTS `sys_job`;
CREATE TABLE `sys_job` (
`job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID',
`job_name` varchar(64) NOT NULL DEFAULT '' COMMENT '任务名称',
`job_group` varchar(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名',
`invoke_target` varchar(500) NOT NULL COMMENT '调用目标字符串',
`cron_expression` varchar(255) DEFAULT '' COMMENT 'cron执行表达式',
`status` char(1) DEFAULT '0' COMMENT '状态(0正常 1暂停)',
PRIMARY KEY (`job_id`,`job_name`,`job_group`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8 COMMENT='定时任务调度表';
任务信息存入数据库不易丢失。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.util.Properties;
/**
* 定时任务配置
*/
@Configuration
public class ScheduleConfig{
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource){
SchedulerFactoryBean factory = new SchedulerFactoryBean();
// 数据源
factory.setDataSource(dataSource);
// quartz参数
Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName", "CustomScheduler");
prop.put("org.quartz.scheduler.instanceId", "AUTO");
// 线程池配置
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "20");
prop.put("org.quartz.threadPool.threadPriority", "5");
// JobStore配置
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
// 集群配置
prop.put("org.quartz.jobStore.isClustered", "false");
prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
// sqlserver 启用
prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
prop.put("org.quartz.jobStore.misfireThreshold", "12000");
prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
factory.setQuartzProperties(prop);
factory.setSchedulerName("CustomScheduler");
// 延时启动(单位:s)
factory.setStartupDelay(5);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
// 可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
// 设置自动启动,默认为true
factory.setAutoStartup(true);
return factory;
}
}
/**
* 通用常量
*/
public interface ScheduleConstants {
/** 执行目标 */
String TASK_NAME = "TASK_NAME";
/** 执行目标 */
String TASK_PARA = "TASK_PARA";
/** 正常 */
String NORMAL = "0";
/** 暂停 */
String PAUSE = "1";
}
方便获取bean。
package com.quartz.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
/**
* 获取applicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取 Bean.
*/
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*/
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*/
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}
从数据库中加载任务信息,并创建。数据库使用mybatis-plus作为持久层,其相关类当前不做展示。
import com.quartz.domain.SysJob;
import com.quartz.mapper.SysJobMapper;
import com.quartz.util.QuartzJobUtil;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 项目启动时初始化调度任务
*/
@Component
public class JobCommandLineRunner implements CommandLineRunner {
private final QuartzJobUtil quartzJobUtil;
private final SysJobMapper jobMapper;
public JobCommandLineRunner(SysJobMapper jobMapper, QuartzJobUtil quartzJobUtil) {
this.jobMapper = jobMapper;
this.quartzJobUtil = quartzJobUtil;
}
@Override
public void run(String... args) throws Exception {
quartzJobUtil.clear();
List<SysJob> jobList = jobMapper.selectJobAll();
for (SysJob job : jobList){
quartzJobUtil.createScheduleJob(job);
}
}
}
包括创建、恢复、删除任务等操作。
package com.quartz.util;
import com.quartz.config.ScheduleConstants;
import com.quartz.domain.SysJob;
import com.quartz.task.CustomJobListener;
import com.quartz.task.QuartzJobExecutor;
import org.quartz.*;
import org.quartz.impl.matchers.EverythingMatcher;
import org.quartz.impl.matchers.KeyMatcher;
import org.springframework.stereotype.Component;
/**
* 定时任务工具类
*/
@Component
public class QuartzJobUtil {
private final CustomJobListener customJobListener;
private final Scheduler scheduler;
public QuartzJobUtil(Scheduler scheduler, CustomJobListener customJobListener) {
this.scheduler = scheduler;
this.customJobListener = customJobListener;
}
/**
* 清除定时任务
*/
public void clear() throws SchedulerException {
scheduler.clear();
}
/**
* 恢复定时任务
*/
public void resumeJob(Long jobId,String jobGroup) throws SchedulerException {
scheduler.resumeJob(JobKey.jobKey(ScheduleConstants.TASK_NAME + jobId, jobGroup));
}
/**
* 触发定时任务
*/
public void triggerJob(Long jobId,String jobGroup,SysJob sysJob) throws SchedulerException {
JobDataMap dataMap = new JobDataMap();
dataMap.put(ScheduleConstants.TASK_PARA, sysJob);
scheduler.triggerJob(JobKey.jobKey(ScheduleConstants.TASK_NAME + jobId, jobGroup), dataMap);
}
/**
* 判断定时任务是否存在
*/
public boolean checkExists(Long jobId,String jobGroup) throws SchedulerException {
return scheduler.checkExists(JobKey.jobKey(ScheduleConstants.TASK_NAME + jobId, jobGroup));
}
/**
* 删除定时任务
*/
public void deleteJob(Long jobId, String jobGroup) throws SchedulerException {
scheduler.deleteJob(JobKey.jobKey(ScheduleConstants.TASK_NAME + jobId, jobGroup));
}
/**
* 暂停定时任务
*/
public void pauseJob(Long jobId, String jobGroup) throws SchedulerException {
scheduler.pauseJob(JobKey.jobKey(ScheduleConstants.TASK_NAME + jobId, jobGroup));
}
/**
* 创建定时任务
*/
public void createScheduleJob(SysJob job) throws Exception {
Class<? extends Job> jobClass = QuartzJobExecutor.class;
// 构建job信息
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobKey jobKey=JobKey.jobKey(ScheduleConstants.TASK_NAME + jobId, jobGroup);
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobKey).build();
// 表达式调度构建器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(TriggerKey.triggerKey(ScheduleConstants.TASK_NAME + jobId, jobGroup))
.withSchedule(cronScheduleBuilder).build();
// 放入参数,业务执行时获取
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PARA, job);
// 判断是否存在
if (scheduler.checkExists(jobKey)){
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(jobKey);
}
// 安排任务
scheduler.scheduleJob(jobDetail, trigger);
// 暂停任务
if (job.getStatus().equals(ScheduleConstants.PAUSE)){
scheduler.pauseJob(jobKey);
}
// // 全局任务监听器
// scheduler.getListenerManager().addJobListener(new CustomJobListener(), EverythingMatcher.allJobs());
// 局部任务监听器
scheduler.getListenerManager().addJobListener(customJobListener, KeyMatcher.keyEquals(JobKey.jobKey(ScheduleConstants.TASK_NAME +2,jobGroup)));
}
}
利用反射机制执行指定类的指定方法。
import com.quartz.config.ScheduleConstants;
import com.quartz.domain.SysJob;
import com.quartz.util.SpringUtil;
import org.apache.commons.lang3.StringUtils;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.lang.reflect.Method;
/**
* 定时任务处理(@DisallowConcurrentExecution注解不允许并发执行)
*/
@DisallowConcurrentExecution
public class QuartzJobExecutor extends QuartzJobBean {
/**
* 反射执行
*/
@Override
protected void executeInternal(JobExecutionContext context) {
// 获取之前存入的job信息
SysJob sysJob = (SysJob)context.getMergedJobDataMap().get(ScheduleConstants.TASK_PARA);
try {
invokeMethod(sysJob);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 执行方法
*/
private void invokeMethod(SysJob sysJob) throws Exception{
// 解析实体名和方法名
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
String methodName = getMethodName(invokeTarget);
// 获取实体类和方法参数
Object bean = SpringUtil.getBean(beanName);
// 分有参和无参执行方法
if (invokeTarget.contains("(")){
// 参数值
Object[] methodParams=params(invokeTarget);
Method method = bean.getClass().getDeclaredMethod(methodName, classes(methodParams));
method.invoke(bean, methodParams);
}else{
Method method = bean.getClass().getDeclaredMethod(methodName);
method.invoke(bean);
}
}
/**
* 获取bean名称
*/
private String getBeanName(String invokeTarget) {
String beanName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringBeforeLast(beanName, ".");
}
/**
* 获取bean方法
*/
private String getMethodName(String invokeTarget) {
String methodName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringAfterLast(methodName, ".");
}
/**
* 获取参数值
*/
private Object[] params(String invokeTarget){
String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")").replace("'","");
return methodStr.split(",");
}
/**
* 获取参数类型
*/
private Class<?>[] classes(Object[] methodParams){
// 参数类型
Class<?>[] classes = new Class<?>[methodParams.length];
int index = 0;
for (Object ignored : methodParams){
classes[index] = String.class;
index++;
}
return classes;
}
}
可以监听调度任务执行的流程。
import com.quartz.config.ScheduleConstants;
import com.quartz.domain.SysJob;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.springframework.stereotype.Component;
/**
* 定时任务监听
*/
@Component
public class CustomJobListener implements JobListener {
@Override
public String getName() {
return this.getClass().getName();
}
@Override
public void jobToBeExecuted(JobExecutionContext context) {
SysJob sysJob = (SysJob)context.getMergedJobDataMap().get(ScheduleConstants.TASK_PARA);
String name = sysJob.getInvokeTarget();
System.out.println("即将调用的方法:"+name);
}
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
SysJob sysJob = (SysJob)context.getMergedJobDataMap().get(ScheduleConstants.TASK_PARA);
String name = sysJob.getInvokeTarget();
System.out.println("方法"+"'"+name+"'"+"被拒绝执行");
}
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException e) {
SysJob sysJob = (SysJob)context.getMergedJobDataMap().get(ScheduleConstants.TASK_PARA);
String name = sysJob.getInvokeTarget();
System.out.println("方法"+"'"+name+"'"+"执行完毕");
}
}
用于存放调度任务
import org.springframework.stereotype.Component;
/**
* 定时任务
*/
@Component
public class CustomTask {
public void params(String params1,String params2){
System.out.println("执行有参方法,参数:" + params1+","+params2);
}
public void noParams(){
System.out.println("执行无参方法");
}
}
在最后一张表,即sys_job
表中存入任务相关信息。
当前往数据库中存入两个任务,一个有参一个无参,其中有参方法params
,每6秒执行一次,参数为param1
和param2
,无参方法noParams
每5秒执行一次。
启动SpringBoot。
可以看到两个任务开始循环执行。由于有参任务处于被监听状态(在创建任务处设置),所以打印出了有参方法的监听信息。
最后附上完整代码https://download.csdn.net/download/weixin_43424932/12480618。