diff --git a/README.md b/README.md index abac556c..278a43fd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ -# xxl-job -任务调度框架xxl-job +# 任务调度框架xxl-job + + Scheduler + Trigger + JobDetail diff --git a/xxl-job-admin/src/test/java/Test.java b/xxl-job-admin/src/test/java/Test.java new file mode 100644 index 00000000..74d954e7 --- /dev/null +++ b/xxl-job-admin/src/test/java/Test.java @@ -0,0 +1,65 @@ +import java.util.Date; +import java.util.Timer; + +import org.apache.commons.lang.time.FastDateFormat; + + +public class Test { + + static class DemoTimeTask extends java.util.TimerTask { + public void run() { + System.out.println("run:" + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss").format(new Date())); + } + } + + // ??? 某一个时间段内,重复执行 + + // runTime:第一次执行时间 + // delay: 延迟执行的毫秒数,即在delay毫秒之后第一次执行 + // period:重复执行的时间间隔 + public static Timer mainTimer; + public static void main(String[] args) { + // 调度器 + mainTimer = new Timer(); + // Demo任务 + DemoTimeTask timeTask = new DemoTimeTask(); + System.out.println("now:" + FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss").format(new Date())); + + // 1、在特定时间执行任务,只执行一次 + //Date runTime = DateUtils.addSeconds(new Date(), 1); + //mainTimer.schedule(timeTask, runTime); // runTime + + // 2、在特定时间之后执行任务,只执行一次 + //long delay = 1000; + //mainTimer.schedule(timeTask, delay); // delay/ms + + // 3、指定第一次执行的时间,然后按照间隔时间,重复执行 + //Date firstTime = DateUtils.addSeconds(new Date(), 1); + //long period = 1000; + //mainTimer.schedule(timeTask, firstTime, period); // "period/ms" after "firstTime" + + // 4、在特定延迟之后第一次执行,然后按照间隔时间,重复执行 + //long delay = 1000; + //long period = 1000; + //mainTimer.schedule(timeTask, delay, period); // "period/ms" after "delay/ms" + + // 5、第一次执行之后,特定频率执行,与3同 + //Date firstTime = DateUtils.addSeconds(new Date(), 1); + //long period = 1000; + //mainTimer.scheduleAtFixedRate(timeTask, firstTime, period); + + // 6、在delay毫秒之后第一次执行,后按照特定频率执行 + long delay = 1000; + long period = 1000; + mainTimer.scheduleAtFixedRate(timeTask, delay, period); + /** + * <1>schedule()方法更注重保持间隔时间的稳定:保障每隔period时间可调用一次 + * <2>scheduleAtFixedRate()方法更注重保持执行频率的稳定:保障多次调用的频率趋近于period时间,如果任务执行时间大于period,会在任务执行之后马上执行下一次任务 + */ + + // Timer注销 + mainTimer.cancel(); + + } + +} diff --git a/xxl-job-demo/pom.xml b/xxl-job-demo/pom.xml index 4a7bce85..118a158f 100644 --- a/xxl-job-demo/pom.xml +++ b/xxl-job-demo/pom.xml @@ -135,6 +135,8 @@ 0.0.1-SNAPSHOT + + diff --git a/xxl-job-demo/src/main/java/com/xxl/quartz/DynamicSchedulerUtil.java b/xxl-job-demo/src/main/java/com/xxl/quartz/DynamicSchedulerUtil.java new file mode 100644 index 00000000..9d086528 --- /dev/null +++ b/xxl-job-demo/src/main/java/com/xxl/quartz/DynamicSchedulerUtil.java @@ -0,0 +1,90 @@ +package com.xxl.quartz; + +import org.quartz.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +import java.util.Date; + +public final class DynamicSchedulerUtil implements InitializingBean { + private static final Logger logger = LoggerFactory.getLogger(DynamicSchedulerUtil.class); + + // Scheduler + private static Scheduler scheduler; + public static void setScheduler(Scheduler scheduler) { + DynamicSchedulerUtil.scheduler = scheduler; + } + + @Override + public void afterPropertiesSet() throws Exception { + Assert.notNull(scheduler, "quartz scheduler is null"); + logger.info(">>>>>>>>> init quartz scheduler success.[{}]", scheduler); + } + + // Add 新增 + public static boolean addJob(JobModel job) throws SchedulerException { + final TriggerKey triggerKey = job.getTriggerKey(); + if (scheduler.checkExists(triggerKey)) { + final Trigger trigger = scheduler.getTrigger(triggerKey); + logger.info(">>>>>>>>> Already exist trigger [" + trigger + "] by key [" + triggerKey + "] in Scheduler"); + return false; + } + + final CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); + final CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey) + .withSchedule(cronScheduleBuilder) + .build(); + + final JobDetail jobDetail = job.getJobDetail(); + final Date date = scheduler.scheduleJob(jobDetail, cronTrigger); + + logger.debug("Register DynamicJob {} on [{}]", job, date); + return true; + } + + // Pause 暂停-指定Job + public static boolean pauseJob(JobModel existJob) throws SchedulerException { + final TriggerKey triggerKey = existJob.getTriggerKey(); + boolean result = false; + if (scheduler.checkExists(triggerKey)) { + scheduler.pauseTrigger(triggerKey); + result = true; + logger.debug("Pause exist DynamicJob {}, triggerKey [{}] successful", existJob, triggerKey); + } else { + logger.debug("Failed pause exist DynamicJob {}, because not fount triggerKey [{}]", existJob, triggerKey); + } + return result; + } + + // Resume 重启-指定Job + public static boolean resumeJob(JobModel existJob) throws SchedulerException { + final TriggerKey triggerKey = existJob.getTriggerKey(); + boolean result = false; + if (scheduler.checkExists(triggerKey)) { + final CronTrigger newTrigger = existJob.cronTrigger(); + final Date date = scheduler.rescheduleJob(triggerKey, newTrigger); + + result = true; + logger.debug("Resume exist DynamicJob {}, triggerKey [{}] on [{}] successful", existJob, triggerKey, date); + } else { + logger.debug("Failed resume exist DynamicJob {}, because not fount triggerKey [{}]", existJob, triggerKey); + } + return result; + } + + // Remove exists job 移除-指定Job + public static boolean removeJob(JobModel existJob) throws SchedulerException { + final TriggerKey triggerKey = existJob.getTriggerKey(); + boolean result = false; + if (scheduler.checkExists(triggerKey)) { + result = scheduler.unscheduleJob(triggerKey); + } + + logger.debug("Remove DynamicJob {} result [{}]", existJob, result); + return result; + } + + +} \ No newline at end of file diff --git a/xxl-job-demo/src/main/java/com/xxl/quartz/JobModel.java b/xxl-job-demo/src/main/java/com/xxl/quartz/JobModel.java new file mode 100644 index 00000000..d809e9d5 --- /dev/null +++ b/xxl-job-demo/src/main/java/com/xxl/quartz/JobModel.java @@ -0,0 +1,77 @@ +package com.xxl.quartz; + +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.Scheduler; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; + +/** + * 任务model + * @author xuxueli 2015-12-1 16:01:19 + */ +public class JobModel { + + // param + private String group; + private String name; + private String cronExpression; + private Class jobClass; + + public JobModel(String name, String cronExpression, Class jobClass) { + this.group = Scheduler.DEFAULT_GROUP; + this.name = name; + this.cronExpression = cronExpression; + this.jobClass = jobClass; + } + + public String getGroup() { + return group; + } + public void setGroup(String group) { + this.group = group; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getCronExpression() { + return cronExpression; + } + public void setCronExpression(String cronExpression) { + this.cronExpression = cronExpression; + } + public Class getJobClass() { + return jobClass; + } + public void setJobClass(Class jobClass) { + this.jobClass = jobClass; + } + + // TriggerKey + public TriggerKey getTriggerKey() { + return TriggerKey.triggerKey(this.name, this.group); + } + // JobDetail + public JobDetail getJobDetail() { + return JobBuilder.newJob(jobClass).withIdentity(this.name, this.group).build(); + } + // JobDataMap.add + public JobModel addJobData(String key, Object value) { + JobDataMap jobDataMap = this.getJobDetail().getJobDataMap(); + jobDataMap.put(key, value); + return this; + } + // CronTrigger + public CronTrigger cronTrigger() { + CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(this.cronExpression); + return TriggerBuilder.newTrigger().withIdentity(this.getTriggerKey()).withSchedule(cronScheduleBuilder).build(); + } + +} \ No newline at end of file diff --git a/xxl-job-demo/src/main/java/com/xxl/service/impl/TriggerServiceImpl.java b/xxl-job-demo/src/main/java/com/xxl/service/impl/TriggerServiceImpl.java index e8936414..ec0a2fd8 100644 --- a/xxl-job-demo/src/main/java/com/xxl/service/impl/TriggerServiceImpl.java +++ b/xxl-job-demo/src/main/java/com/xxl/service/impl/TriggerServiceImpl.java @@ -1,5 +1,8 @@ package com.xxl.service.impl; +import java.util.Date; + +import org.apache.commons.lang.time.FastDateFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -19,12 +22,7 @@ public class TriggerServiceImpl implements ITriggerService { * 全站静态化 */ public void generateNetHtml() { - long start = System.currentTimeMillis(); - logger.info("全站静态化... start:{}", start); - - - long end = System.currentTimeMillis(); - logger.info("全站静态化... end:{}, cost:{}", end, end - start); + logger.info("全站静态化 run at :{}", FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss").format(new Date())); } } diff --git a/xxl-job-demo/src/main/java/com/xxl/service/job/JobDetailDemo.java b/xxl-job-demo/src/main/java/com/xxl/service/job/JobDetailDemo.java new file mode 100644 index 00000000..6a493066 --- /dev/null +++ b/xxl-job-demo/src/main/java/com/xxl/service/job/JobDetailDemo.java @@ -0,0 +1,21 @@ +package com.xxl.service.job; + +import java.util.Date; + +import org.apache.commons.lang.time.FastDateFormat; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.quartz.QuartzJobBean; + +public class JobDetailDemo extends QuartzJobBean { + private static Logger logger = LoggerFactory.getLogger(JobDetailDemo.class); + + @Override + protected void executeInternal(JobExecutionContext context) + throws JobExecutionException { + logger.info("全站静态化[DB] run at :{}", FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss").format(new Date())); + } + +} diff --git a/xxl-job-demo/src/main/resources/applicationcontext-trigger-db.xml b/xxl-job-demo/src/main/resources/applicationcontext-trigger-db.xml new file mode 100644 index 00000000..f3ba1019 --- /dev/null +++ b/xxl-job-demo/src/main/resources/applicationcontext-trigger-db.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xxl-job-demo/src/main/resources/applicationcontext-trigger.xml b/xxl-job-demo/src/main/resources/applicationcontext-trigger-local.xml similarity index 92% rename from xxl-job-demo/src/main/resources/applicationcontext-trigger.xml rename to xxl-job-demo/src/main/resources/applicationcontext-trigger-local.xml index 972ebbf6..ffc06c6c 100644 --- a/xxl-job-demo/src/main/resources/applicationcontext-trigger.xml +++ b/xxl-job-demo/src/main/resources/applicationcontext-trigger-local.xml @@ -22,7 +22,7 @@ - + diff --git a/xxl-job-demo/src/main/resources/quartz.properties b/xxl-job-demo/src/main/resources/quartz.properties new file mode 100644 index 00000000..a1e68509 --- /dev/null +++ b/xxl-job-demo/src/main/resources/quartz.properties @@ -0,0 +1,24 @@ +# Default Properties file for use by StdSchedulerFactory +# to create a Quartz Scheduler Instance, if a different +# properties file is not explicitly specified. +# + +org.quartz.scheduler.instanceName: DefaultQuartzScheduler +org.quartz.scheduler.rmi.export: false +org.quartz.scheduler.rmi.proxy: false +org.quartz.scheduler.wrapJobExecutionInUserTransaction: false + +org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool +org.quartz.threadPool.threadCount: 10 +org.quartz.threadPool.threadPriority: 5 +org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true + +org.quartz.jobStore.misfireThreshold: 60000 + +#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore + +# for cluster +org.quartz.scheduler.instanceId: AUTO +org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX +org.quartz.jobStore.isClustered: true +org.quartz.jobStore.clusterCheckinInterval: 1000 diff --git a/xxl-job-demo/src/test/java/quartz/JunitTest.java b/xxl-job-demo/src/test/java/quartz/JunitTest.java new file mode 100644 index 00000000..4415a63f --- /dev/null +++ b/xxl-job-demo/src/test/java/quartz/JunitTest.java @@ -0,0 +1,30 @@ +package quartz; + + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.quartz.SchedulerException; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.xxl.quartz.DynamicSchedulerUtil; +import com.xxl.quartz.JobModel; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "classpath*:applicationcontext-*.xml") +public class JunitTest { + + @Test + public void addJob() throws SchedulerException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InterruptedException { + boolean ret = DynamicSchedulerUtil.addJob(new JobModel("Jost-job", "0/1 * * * * ?", TestDynamicJob.class)); + System.out.println(ret); + TimeUnit.SECONDS.sleep(30); + } + + + + +} diff --git a/xxl-job-demo/src/test/java/quartz/TestDynamicJob.java b/xxl-job-demo/src/test/java/quartz/TestDynamicJob.java new file mode 100644 index 00000000..8e4f93de --- /dev/null +++ b/xxl-job-demo/src/test/java/quartz/TestDynamicJob.java @@ -0,0 +1,17 @@ +package quartz; + +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +import java.util.Date; + +public class TestDynamicJob implements Job { + + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + final Object mailGuid = context.getMergedJobDataMap().get("mailGuid"); + System.out.println("[Dynamic-Job] It is " + new Date() + " now, mailGuid=" + mailGuid); + } +} \ No newline at end of file