Java 定时任务quartz
1. java自带 java.util.Timer 实现定时任务
2. 使用线程池(ScheduledThreadPool-java.util.concurrent.ScheduledExecutorService)实现定时任务
3. 使用注解@Scheduled实现定时任务
4. 使用Quartz定时任务调度器
4.1 Quartz 特点
4.2 核心概念
4.3 常用对象
4.4 实例代码
4.5 具体对象和方法
4.5.1 ScheduleBuilder的具体对象和常用方法:
4.5.2 TriggerBuilder
4.5.3 Scheduler
4.5.4 StdSchedulerFactory
4.5.5 JobDetail
4.5.6 JobBuilder
1. java自带 java.util.Timer 实现定时任务
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("schedule--------------" + System.currentTimeMillis());
}
}, 10 * 1000, 3 * 1000);// 10秒后执行,执行频率为3秒一次
Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("schedule--------------" + System.currentTimeMillis()); } }, 10 * 1000, 3 * 1000);// 10秒后执行,执行频率为3秒一次3
schedule(TimerTask task, Date time):安排在指定的时间执行指定的任务。
schedule(TimerTask task, Date firstTime, long period) :安排指定的任务在指定的时间开始进行重复的固定延迟执行。
schedule(TimerTask task, long delay) :安排在指定延迟后执行指定的任务。
schedule(TimerTask task, long delay, long period) :安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
scheduleAtFixedRate(TimerTask task, Date firstTime, long period):安排指定的任务在指定的时间开始进行重复的固定速率执行。
scheduleAtFixedRate(TimerTask task, long delay, long period):安排指定的任务在指定的延迟后开始进行重复的固定速率执行。
2. 使用线程池(ScheduledThreadPool-java.util.concurrent.ScheduledExecutorService)实现定时任务
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("--------------" + System.currentTimeMillis());
}
}, 10, 3, TimeUnit.SECONDS);// 10秒后执行,执行频率为3秒一次
ScheduledExecutorService service = Executors.newScheduledThreadPool(1); service.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("--------------" + System.currentTimeMillis()); } }, 10, 3, TimeUnit.SECONDS);// 10秒后执行,执行频率为3秒一次
3. 使用注解@Scheduled实现定时任务
注解@Scheduled属性有:
cron : cron的表达式
zone : 解析cron表达式的时区
fixedDelay : 方法之间以毫秒为单位执行带注释的方法,返回延迟,以毫秒为单位
fixedDelayString : 方法之间以毫秒为单位执行带注释的方法,返回延迟,以毫秒为单位
fixedRate : 周期(以毫秒为单位)
fixedRateString : 周期(以毫秒为单位)
initialDelay : 在第一次执行a之前要延迟的毫秒数,初始延迟(以毫秒为单位)
initialDelayString : 在第一次执行a之前要延迟的毫秒数,初始延迟(以毫秒为单位)
@Component public class SpringScheduled { // 10秒后开始执行,每隔3秒执行一次 @Scheduled(initialDelay = 10 * 1000, fixedRate = 3 * 1000) public void execute() { System.out.println("------------" + System.currentTimeMillis()); } }
4. 使用Quartz定时任务调度器
4.1 Quartz 特点
具有强大的调度功能,与Spring容易集成,形成灵活的调度功能。
调度环境的持久化机制:可以保存并恢复调度现场,即使系统因为故障关闭,任务调度现场的数据并不会丢失。
灵活的应用方式:可以灵活的定义触发器调度时间表,并可对触发器与任务进行关联映射(通过Name和Group形式为一个的JobKey)。
分布式与集群能力
4.2 核心概念
Quartz核心概念包括:调度器(Scheduler)、任务(Job)、触发器(Trigger)。
任务:表示一个具体的可执行的调度程序,Job (实现Job接口的具体类)是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
调度器:一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
触发器:可配置调度参数。
4.3 常用对象
Job:一个接口,只有一个 execute(JobExecutionContext context) 方法,用于编写具体的任务业务逻辑。当调度器需要执行 job 时创建实例,调用完成后释放 job 实例。
JobDetail:描述 Job 实列的详细信息。name:job名称;group:job组。默认值为default;jobClass:job接口实现类的class;jobDataMap:存储Job实例的状态信息,调度器使用这些信息添加 Job 实例。
JobExecutionContext:Job能通过 JobExecutionContext 访问 Quartz 运行环境以及 Job 的明细数据,当 Scheduler 调用 Job 时能将数据传递给 execute() 方法。
JobDataMap:是一个JDK中Map接口实例,在任务调度时存在于 JobExecutionContext 中,可将参数对象传递给在运行的 Job 实例;而且,它自身有方便的方法可存取基本数据类型。
Trigger:触发器,可设置首次被触发事件 startTime,不再被触发事件 endTime。
ScheduleBuilder:设置任务执行计划,如多久执行一次,是否重复执行,延迟多久执行等。
Scheduler:调度器,管理任务调取容器。
4.4 实例代码
创建一个 Job 实现类,用于编写具体业务逻辑
import org.quartz.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class TestJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { LocalDateTime nowTime = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"); System.out.println("---------" + formatter.format(nowTime)); // 获取Scheduler Scheduler scheduler = context.getScheduler(); // 获取Trigger Trigger trigger = context.getTrigger(); // 通过Trigger获取JobDataMap参数 JobDataMap triggerJobData = trigger.getJobDataMap(); // 获取JobDetail JobDetail jobDetail = context.getJobDetail(); // 通过JobDetail获取JobDetail参数 JobDataMap jobDetailData = jobDetail.getJobDataMap(); // 合并获取JobDataMap,存在键值相同的,取Trigger JobDataMap的值 JobDataMap mergedJobDataMap = context.getMergedJobDataMap(); } }
具体调度实现代码
import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; public class TestScheduled { public static void main(String[] args) throws SchedulerException { // 创建一个Trigger实例 JobDataMap triggerData = new JobDataMap(); triggerData.put("trigger1", "我是trigger1"); triggerData.put("trigger2", "我是trigger2"); // 创建一个Schedule实例(每个10秒执行一次) ScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?"); /*ScheduleBuilder scheduleBuilder = SimpleScheduleBuilder .repeatSecondlyForever(10) //每个10秒执行一次 .repeatForever(); //重复执行*/ Trigger trigger = TriggerBuilder.newTrigger() // 获取trigger类 .withIdentity("triggerName", "triggerGroup") // 任务唯一标识和组别 .withDescription("triggerDescription") // 描述 .usingJobData(triggerData) // 自定义参数 .startNow() // 立即执行,startAt(Date triggerStartTime) -> 某个特定时间开始执行 // endAt(Date triggerEndTime) -> 某个特定时间结束执行 .withSchedule(scheduleBuilder) // 设置执行计划 .build(); // 创建一个jobDetil实例,该实例与 Job Class绑定 JobDataMap jobData = new JobDataMap(); jobData.put("job1", "我是job1"); jobData.put("job2", "我是job2"); JobDetail jobDetail = JobBuilder.newJob(TestJob.class) .withIdentity("jobName", "jobGroup") .withDescription("jobDescription") .usingJobData(jobData) .build(); // 创建Scheduler实例:javaWeb项目可以通过注入方式创建Scheduler实例,如(@Autowired private Scheduler scheduler;) SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); // 执行 } }
4.5 具体对象和方法
4.5.1 ScheduleBuilder的具体对象和常用方法:
1. CronScheduleBuilder(常用):它为触发器定义了基于 cronexpress 的调度。该对象的主要通过 Cron表达式 来进行创建,底层代码基本都是将对应参数转换为 Cron表达式 后进行创建。
cronSchedule(String cronExpression):通过指定 Cron表达式 创建对象
2. SimpleScheduleBuilder:它为触发器定义了基于严格/文字间隔的调度。能指定触发的间隔时间和执行次数;
build():构建实际的触发器
simpleSchedule():static 方法,获取一个 SimpleScheduleBuilder 对象
repeatForever():指定触发器将无限重复。
withRepeatCount(int triggerRepeatCount):设置重复执行次数
repeat___lyForever():设置对应时间单位执行一次,无限重复
repeat___lyForever(int):设置当前 对应时间单位 * int 执行一次,无限重复
withIntervalIn____s(int/long):根据对应时间单位设置执行间隔
3. CalendarIntervalScheduleBuilder:它为触发器定义基于间隔的日历时间(天、周、月、年)调度。
build():构建实际的触发器
calendarIntervalSchedule():创建一个 CalendarIntervalScheduleBuilder。
preserveHourOfDayAcrossDaylightSavings(boolean preserveHourOfDay):如果间隔是一天或更长的时间,这个属性(设置为 true )将导致触发器的触发总是在一天的同一时间发生,( startTime 的时间),而不考虑夏令时转换。
withInterval(int timeInterval, DateBuilder.IntervalUnit unit):指定要生成的触发器的时间单位和间隔。
withIntervalIn___s(int intervalInDays):在 IntervalUnit 中指定一个 interval。所产生的触发器将重复对应时间单位。
4. DailyTimeIntervalScheduleBuilder:此生成器为您提供了一个额外的方便方法来设置触发器的 endTimeOfDay。
build():构建实际的触发器
dailyTimeIntervalSchedule():创建一个 DailyTimeIntervalScheduleBuilder。
endingDailyAfterCount(int count):使用 count、interval 和 starTimeOfDay 计算和设置 endTimeOfDay。
endingDailyAt(TimeOfDay timeOfDay):设置此触发器的每日开始时间,以在给定的时间结束每天的触发。
onDaysOfTheWeek(onDaysOfWeek):将触发器设置为在一周中的特定日期触发。
onEveryDay():触发时间为一周中的任何一天。
onMondayThroughFriday():把周一到周五的日子设为触发时间。
onSaturdayAndSunday():触发时间为星期六和星期天。
startingDailyAt(TimeOfDay timeOfDay):设置触发开始每天在给定的时间。
withInterval(int timeInterval, DateBuilder.IntervalUnit unit):指定要生成的触发器的时间单位和间隔。
withIntervalIn____s(int):在 IntervalUnit 中指定一个 interval。所产生的触发器将重复的时间。
withRepeatCount(int repeatCount):设置间隔重复的次数。
4.5.2 TriggerBuilder
用于实例化触发器。构建器将始终保持自身处于有效状态,并为随时调用 build() 设置合理的缺省值。
newTrigger():创建一个新的 TriggerBuilder,用于定义触发器的规范。
build():构建触发器
endAt(Date triggerEndTime):设置触发器不再触发的时间——即使它的时间表还有剩余的重复。
startAt(Date triggerStartTime):根据为触发器配置的计划,设置触发器应该启动的时间——触发器可能启动,也可能不启动。
startNow():将触发器应该启动的时间设置为当前时刻—触发器可能启动,也可能不启动—这取决于为触发器配置的调度。
forJob:设置生成的触发器触发的作业的标识。
modifiedByCalendar(String calName):设置应该应用于此触发器的日程安排的日历的名称。
usingJobData:设置触发器的 JobDataMap。
withDescription(String triggerDescription):设置给定的(有意义的)触发器描述。
withIdentity:使用具有给定名称和组的 TriggerKey 来标识触发器。
withPriority(int triggerPriority):设置触发器的优先级。
withSchedule(ScheduleBuilder<SBT> schedBuilder):设置 ScheduleBuilder,它将用于定义触发器的调度。
4.5.3 Scheduler
这是Quartz调度器的主接口。
调度器维护 JobDetails 和触发器的注册表。一旦注册,调度器就负责在相关的触发器触发时(当它们的计划时间到达时)执行作业。
Scheduler 实例由 SchedulerFactory 生成。已经创建/初始化的调度器可以通过生成它的工厂找到并使用。创建调度器之后,它处于“备用”模式,必须先调用其 start() 方法,然后才会触发任何作业。
作业将由“客户端程序”创建,方法是定义一个实现作业接口的类。然后创建 JobDetail 对象(也由客户机创建)来定义作业的单个实例。然后,可以通过 scheduleJob(JobDetail,Trigger) 或addJob(JobDetail, boolean)方法向调度器注册JobDetail实例。
然后可以定义触发器来根据给定的调度触发单个作业实例。SimpleTrigger 对于一次性触发非常有用,或者在某个特定的时刻触发,在它们之间有一个给定的延迟,可以重复 N 次。CronTrigger 允许基于日、周、月、月的时间进行调度。
作业和触发器具有与它们关联的名称和组,这些名称和组应该在单个调度器中惟一地标识它们。“组”功能对于创建作业和 Triggerss 的逻辑分组或分类可能很有用。如果您不需要为给定的触发器作业分配组,那么您可以使用这个接口上定义的 DEFAULT_GROUP 常量。
存储的作业也可以通过 triggerJob(String jobName, String jobGroup) 函数“手动”触发。
客户端程序也可能对 Quartz 提供的“listener”接口感兴趣。JobListener 接口提供作业执行的通知。TriggerListener接口提供触发器触发的通知。SchedulerListener 接口提供调度器事件和错误的通知。侦听器可以通过 ListenerManager 接口与本地调度器关联。
addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers):将给定的日历添加(注册)到调度程序。
addJob(JobDetail jobDetail, boolean replace):将给定的作业添加到调度器—没有关联的触发器。
checkExists(JobKey jobKey):确定调度程序中是否已经存在具有给定标识符的作业。
checkExists(TriggerKey triggerKey):确定调度程序中是否已经存在具有给定标识符的触发器。
clear():清除(删除!)所有调度数据-所有作业,触发日历。
deleteCalendar(String calName):从调度程序中删除已标识的日历
deleteJob(JobKey jobKey):从调度程序中删除已标识的作业—以及任何关联的触发器。
deleteJobs(List<JobKey> jobKeys):从调度程序中删除已标识的作业—以及任何关联的触发器。
getCalendar(String calName):获取具有给定名称的日历实例。
getCalendarNames():获取所有已注册日历的名称。
getContext():返回调度器的调度器上下文。
getCurrentlyExecutingJobs():返回 JobExecutionContext 对象的列表,这些对象表示当前在此调度器实例中执行的所有作业。
getJobDetail(JobKey jobKey):使用给定的键获取作业实例的 JobDetail。
getJobGroupNames():获取所有已知 JobDetail 组的名称。
getJobKeys(GroupMatcher<JobKey> matcher):获取匹配组中所有 JobDetails 的键。
getListenerManager():获取调度程序的 ListenerManager 的引用,可以通过该引用注册侦听器。
getMetaData():获取描述调度器实例的设置和功能的 SchedulerMetaData 对象。
getPausedTriggerGroups():获取所有暂停的触发组的名称。
getSchedulerInstanceId():返回调度程序的实例Id。
getSchedulerName():返回调度程序的名称。
getTrigger(TriggerKey triggerKey):使用给定的键获取触发器实例。
getTriggerGroupNames():获取所有已知触发器组的名称。
getTriggerKeys(GroupMatcher<TriggerKey> matcher):获取给定组中所有触发器的名称。
getTriggerKeys(GroupMatcher<TriggerKey> matcher):获取给定组中所有触发器的名称。
getTriggersOfJob(JobKey jobKey):获取与标识的 JobDetail 关联的所有触发器。
getTriggerState(TriggerKey triggerKey):获取标识的触发器的当前状态。
interrupt(JobKey jobKey):在这个调度器实例中,请求中断标识的作业的所有当前正在执行的实例,这些实例必须是 InterruptableJob 接口的实现者。
interrupt(String fireInstanceId):在这个调度器实例中请求标识的正在执行的作业实例的中断,该实例必须是 InterruptableJob 接口的实现者。
isInStandbyMode():报告调度程序是否处于备用模式。
isShutdown():报告调度程序是否已关闭。
isStarted():调度程序是否已启动。
pauseAll():暂停所有触发器——类似于在每个组上调用 pauseTriggerGroup(group),但是,在使用此方法之后,必须调用 resumeAll() 来清除调度器的“记住”状态,即所有新触发器将在添加时暂停。
pauseJob(JobKey jobKey):使用给定的键暂停 JobDetail ——通过暂停它的所有当前触发器。
pauseJobs(GroupMatcher<JobKey> matcher):暂停匹配组中的所有 JobDetails—通过暂停它们的所有触发器。
pauseTrigger(TriggerKey triggerKey):使用给定的键暂停触发器。
pauseTriggers(GroupMatcher<TriggerKey> matcher):暂停匹配组中的所有触发器。
rescheduleJob(TriggerKey triggerKey, Trigger newTrigger):使用给定的键删除触发器,并存储新的给定的触发器——它必须与相同的作业相关联(新的触发器必须指定作业名称和组)——但是,新的触发器不需要与旧的触发器具有相同的名称。
resumeAll():恢复(非暂停)所有触发器-类似于调用 resumeTriggerGroup(组) 对每个组。
resumeJob(JobKey jobKey):使用给定的键恢复(取消暂停) JobDetail。
resumeJobs(GroupMatcher<JobKey> matcher):恢复(非暂停)匹配组中的所有 JobDetails。
resumeTrigger(TriggerKey triggerKey):使用给定的键恢复(取消暂停)触发器。
resumeTriggers(GroupMatcher<TriggerKey> matcher):恢复(取消暂停)匹配组中的所有触发器。
scheduleJob(JobDetail jobDetail, Trigger trigger):将给定的 JobDetail 添加到调度器中,并将给定的触发器与之关联。
scheduleJob(Trigger trigger):使用由触发器的设置标识的作业调度给定的触发器。
scheduleJobs(Map<JobDetail,List<Trigger>> triggersAndJobs, boolean replace):使用相关的一组触发器调度所有给定的作业。
setJobFactory(JobFactory factory):设置负责生成作业类实例的 JobFactory。
shutdown():停止调度器的触发器触发,并清理与调度器关联的所有资源。
shutdown(boolean waitForJobsToComplete):停止调度器的触发器触发,并清理与调度器关联的所有资源。根据 waitForJobsToComplete 判断是否等全部任务执行完后再停止、清理
standby():暂时停止调度程序的触发器触发。
start():启动引发触发器的调度器线程。
startDelayed(int seconds):在指定的秒数之后调用 {#start()}。
triggerJob(JobKey jobKey):触发标识的 JobDetail (现在执行它)。
triggerJob(JobKey jobKey, JobDataMap data):触发标识的 JobDetail (现在执行它)。并传递需要的 JobDataMap。
unscheduleJob(TriggerKey triggerKey):从调度程序中删除指定的触发器。
unscheduleJobs(List<TriggerKey> triggerKeys):从调度程序中删除所有指示的触发器。
4.5.4 StdSchedulerFactory
SchedulerFactory的实现,它根据属性文件的内容完成创建 Quartz 的调度器(Scheduler) 实例的所有工作。
默认情况下,一个名为 quartz.properties 的属性文件,属性是从“当前工作目录”加载的。如果失败,那么加载位于 org/quartz 包中的 quartz.properties 文件(作为资源)。如果希望使用这些默认值以外的文件,则必须定义系统属性 org.quartz.properties 。属性指向您想要的文件。
或者,您可以通过在调用 getScheduler() 之前调用 initialize(xx) 方法来显式地初始化工厂。
有关文件中可用的各种设置的信息,请参阅与Quartz一起发布的示例属性文件。完整的配置文档可以在 http://www.quartz-scheduler.org/docs/configuration/index.html 找到。
指定的 JobStore、ThreadPool 和其他 SPI 类的实例将按名称创建,然后在配置文件中为它们指定的任何附加属性将通过调用等效的“set”方法在实例上设置。例如,如果属性文件包含属性 org.quartz.jobStore.myProp = 10。然后在 JobStore 类实例化之后,setMyProp() 方法将被调用。在调用属性的 setter 方法之前,将类型转换为基本Java类型(int、long、float、double、boolean和String)。
一个属性可以通过指定一个遵循 [email protected] 约定的值来引用另一个属性的值,例如,要将调度器的实例名引用为其他属性的值,可以使用 [email protected]。
StdSchedulerFactory():创建一个未初始化的 StdSchedulerFactory。
StdSchedulerFactory(Properties props):创建通过 initialize(Properties) 初始化的 StdSchedulerFactory。
StdSchedulerFactory(String fileName):创建通过 initialize(fileName) 初始化的 StdSchedulerFactory。
getAllSchedulers():返回所有已知调度器(由任何 StdSchedulerFactory 实例生成)。
getDefaultScheduler():返回默认调度器,如果它还不存在,则创建它。
getScheduler():返回此工厂生成的调度器。
getScheduler(String schedName):返回具有给定名称的调度器(如果它存在(如果它已经被实例化))。
initialize():使用 Properties 文件的内容和覆盖系统属性初始化ScheduerFactory。
initialize(InputStream propertiesStream):使用用给定属性文件的 InputStream 初始化SchedulerFactory。
initialize(Properties props):使用给定 Properties 对象的内容初始化ScheduerFactory。
initialize(String filename):使用给定名称的 Properties 文件的内容初始化 SchedulerFactory。
4.5.5 JobDetail
传递给定 Job 实例的详细属性。JobDetails将使用JobBuilder创建/定义。
Quartz 并不存储 Job 类的实际实例,而是允许您通过使用 JobDetail 来定义 Job 类的实例。
Job 具有与其关联的名称和组,这些名称和组应该在单个调度器中惟一地标识它们。
触发器(Trigger)是调度 Job 的“机制”。许多 Trigger 可以指向同一 Job,但单个Trigger只能指向一个 Job。
getDescription():返回由其创建者(如果有的话)提供给 Job 实例的描述。
getJobBuilder():获得一个配置为生成与此 JobDetail 相同的 JobBuilder。
getJobClass():获取将要执行的作业的实例。
getJobDataMap():获取与作业关联的 JobDataMap。
getKey():返回 JobKey。
isConcurrentExectionDisallowed():是否禁止同时执行。
isDurable():Job 孤立后是否应该继续存储(没有 Trigger 指向它)。
isPersistJobDataAfterExecution():执行后是否保存 Job 数据。
requestsRecovery():指示调度器(Scheduler),如果遇到“recovery恢复”或“fail-over故障转移”情况,是否应重新执行 Job。
上面的方法,JobDetail 接口实现类 JobDetailImpl 都有之对应的 setter 方法。
4.5.6 JobBuilder
JobBuilder 用于实例化 JobDetails。
构建器将始终保持自身处于有效状态,并为随时调用 build() 设置合理的缺省值。例如,如果您没有调用 withIdentity(..),将为您生成一个作业名。
newJob():创建用于定义 JobDetail 的 JobBuilder。
ofType(Class<? extends Job> jobClazz):设置将在触发器触发与此 JobDetail 关联时实例化并执行的类。
newJob(Class<? extends Job> jobClass):创建用于定义 JobDetail 的 JobBuilder,并设置要执行的作业的类名。是 newJob() 和 ofType() 方法的合成。
build():生成此 JobBuilder 定义的 JobDetail 实例。
requestRecovery():指示调度器,如果遇到“恢复”或“故障转移”情况,应重新执行 Job。
requestRecovery(boolean jobShouldRecover):指示调度器,如果遇到“恢复”或“故障转移”情况,是否应重新执行 Job。true -> 重新执行,false -> 不重新执行。
storeDurably():作业孤立后应该继续存储(没有触发器指向它)。
storeDurably(boolean jobDurability):作业孤立后应该继续存储(没有触发器指向它)。true -> 继续存储,false -> 不继续存储。
usingJobData:设置 JobDetail 的 JobDataMap
withDescription(String jobDescription):设置作业的给定(有意义的)描述。
withIdentity(JobKey jobKey):使用 JobKey 来标识 JobDetail。
————————————————
版权声明:本文为CSDN博主「普通人zzz~」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_33375499/article/details/105856253