一、快速开始:
Springboot方式:
-
pom引入quartz包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
-
写一个类,继承QuartzJobBean,并重写executeInternal方法,在这个方法中,写你想要定时任务执行的业务逻辑。
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.quartz.QuartzJobBean; public class DemoJob extends QuartzJobBean { private static final Logger logger = LoggerFactory.getLogger(DemoJob.class); @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { logger.info("开始执行业务逻辑"); } }
-
写一个类,这个类的作用是,设定定时任务执行的频率
@Configuration public class GlobalSheduler { @Bean public JobDetail demoJobDetail(){ return JobBuilder.newJob(DemoJob.class) .storeDurably() .build(); } @Bean public Trigger demoJobTrigger(){ SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever(); return TriggerBuilder.newTrigger().forJob(demoJobDetail()) .withSchedule(simpleScheduleBuilder) .build(); } }
-
启动application,可以看出,定时任务成功每2秒执行一次了。
2021-12-11 07:50:38.890 [quartzScheduler_Worker-9] INFO com.example.jobtest.job.DemoJob - 开始执行业务逻辑 2021-12-11 07:50:40.894 [quartzScheduler_Worker-10] INFO com.example.jobtest.job.DemoJob - 开始执行业务逻辑 2021-12-11 07:50:42.883 [quartzScheduler_Worker-1] INFO com.example.jobtest.job.DemoJob - 开始执行业务逻辑
普通方式:
-
写一个实现Job接口的类,放业务逻辑。
import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HelloJob implements Job { private static final Logger logger = LoggerFactory.getLogger(HelloJob.class); @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { logger.info("开始执行业务逻辑"); } }
-
写main方法,执行实现逻辑。
import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; public class TestHelloJob { public static void main(String[] args) throws SchedulerException { //创建一个scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.getContext().put("skey", "svalue"); //创建一个Trigger Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .usingJobData("t1", "tv1") .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3) .repeatForever()).build(); trigger.getJobDataMap().put("t2", "tv2"); //创建一个job JobDetail job = JobBuilder.newJob(HelloJob.class) .usingJobData("j1", "jv1") .withIdentity("myjob", "mygroup").build(); job.getJobDataMap().put("j2", "jv2"); //注册trigger并启动scheduler scheduler.scheduleJob(job,trigger); scheduler.start(); } }
-
执行main方法,可以看到跟上次一样。
2021-12-11 08:43:29.097 [DefaultQuartzScheduler_Worker-4] INFO com.example.jobtest.job.HelloJob - 开始执行业务逻辑 2021-12-11 08:43:32.093 [DefaultQuartzScheduler_Worker-5] INFO com.example.jobtest.job.HelloJob - 开始执行业务逻辑 2021-12-11 08:43:35.091 [DefaultQuartzScheduler_Worker-6] INFO com.example.jobtest.job.HelloJob - 开始执行业务逻辑
二、Scheduler、Job、Trigger、JobDetail都是干啥的
-
Scheduler
可以把这个类理解成一个执行器,它是整个定时任务的总调度,可以看见方法二中job和trigger都加入到它中,并且调用了它的start方法,定时器才能执行,这个可以类比多线程的启动方式,两种方法也可以类比多线程的两种方法。
-
Job
这里job的概念很简单,就是你希望执行的业务放的类,其中executeInternal()方法可以类比于Thread的run()方法,这样就好理解了。
这里重点关注针对job有两个注解:
关于job的状态数据(即JobDataMap)和并发性,还有一些地方需要注意。在job类上可以加入一些注解,这些注解会影响job的状态和并发性。
@DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。请注意这里的用词。所以该限制是针对JobDetail的,而不是job类的。但是我们认为(在设计Quartz的时候)应该将该注解放在job类上,因为job类的改变经常会导致其行为发生变化。
@PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。和 @DisallowConcurrentExecution注解一样,尽管注解是加在job类上的,但其限制作用是针对job实例的,而不是job类的。由job类来承载注解,是因为job类的内容经常会影响其行为状态(比如,job类的execute方法需要显式地“理解”其”状态“)。
如果你使用了@PersistJobDataAfterExecution注解,我们强烈建议你同时使用@DisallowConcurrentExecution注解,因为当同一个job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。
-
JobDetail
这个是要重点说的,先说为什么有job的情况下,还要个detail,它存在的意义是什么:
因为scheduler在执行job的时候,实际上是每次执行都新new了一个job对象,每个job都是无状态的,执行完每个job的exec方法后,就把这个对象给丢弃了。
验证每次执行job都是新的对象:
2021-12-11 09:01:05.673 [DefaultQuartzScheduler_Worker-2] INFO com.example.jobtest.job.HelloJob - 开始执行业务逻辑com.example.jobtest.job.HelloJob@213e0fad 2021-12-11 09:01:08.666 [DefaultQuartzScheduler_Worker-3] INFO com.example.jobtest.job.HelloJob - 开始执行业务逻辑com.example.jobtest.job.HelloJob@43b21dc7 2021-12-11 09:01:11.667 [DefaultQuartzScheduler_Worker-4] INFO com.example.jobtest.job.HelloJob - 开始执行业务逻辑com.example.jobtest.job.HelloJob@7f580c2e 2021-12-11 09:01:14.671 [DefaultQuartzScheduler_Worker-5] INFO com.example.jobtest.job.HelloJob - 开始执行业务逻辑com.example.jobtest.job.HelloJob@24772307
而我们日常使用的时候,同一段逻辑,可能要接受不同参数,并根据请求参数不同执行逻辑不同。
比如现在我们有个场景,需要根据传入参数不同打印不同日志。
public class HelloJob implements Job { private static final Logger logger = LoggerFactory.getLogger(HelloJob.class); private String name; private int age; @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("我叫"+getName()+",我今年"+getAge()+"岁了"); logger.info("开始执行业务逻辑"+this.toString()); } public String getName() { return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } }
//创建一个Trigger Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .usingJobData("t1", "tv1") .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3) .repeatForever()).build(); trigger.getJobDataMap().put("t2", "tv2"); //创建一个jobDetail JobDetail job = JobBuilder.newJob(HelloJob.class) .withIdentity("myjob", "mygroup").build(); job.getJobDataMap().put("name","王小明"); job.getJobDataMap().put("age", 12); //创建一个Trigger Trigger trigger2 = TriggerBuilder.newTrigger() .withIdentity("trigger2", "group1") .usingJobData("t1", "tv1") .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3) .repeatForever()).build(); trigger2.getJobDataMap().put("t2", "tv2"); //创建一个job JobDetail job2 = JobBuilder.newJob(HelloJob.class) .withIdentity("myjob2", "mygroup").build(); job2.getJobDataMap().put("name","李晓红"); job2.getJobDataMap().put("age", 15); scheduler.scheduleJob(job,trigger); scheduler.scheduleJob(job2,trigger2); scheduler.start();
我叫王小明,我今年12岁了 我叫李晓红,我今年15岁了
可以看出,只多写jobDetail和trigger即可,不用再写一遍job中的内容了。而这里面主要实现的手段,就是通过jobDetail里的一个map,不管新建和销毁了多少个job,他们都可以共用jobdetail中的map里的值。
-
Trigger
这个可以理解成触发器,就是你定义你这个job啥时候执行,执行频率是什么样的,比如说每个月1号执行,每天凌晨执行等等。
trigger的使用:
Trigger trigger = newTrigger() .withIdentity("myTrigger", "group1") .startNow() .withSchedule(simpleSchedule() .withIntervalInSeconds(40) .repeatForever()) .build();
withIdentity():指定自己的key和所在组,也可以不指定组,那就默认default组。
startNow():表示立即执行,跟这个方法相对的,还有startAt(futureDate(5, IntervalUnit.MINUTE)) 或者endAt(dateOf(22, 0, 0)),表示在未来某个时间执行。
withSchedule():这个就是调度的核心方法了,指定了你任务执行的时间和重复的策略。这个方法可以选择两种不同的参数,这两个的使用场景不同,一种是SimpleTrigger,一种是CronTrigger。
SimpleTrigger适用于一些简单的重复场景,比如说我每隔一段固定的时间执行任务,或者在一个固定的时间执行一次任务,像我们日常用到的定时同步,或者隔一段时间发送一个消息之类的。
CronTrigger相较于SimpleTrigger功能更加强大,它基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。使用CronTrigger,你可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。
这两种Trigger的具体使用,这里不做赘述,看官方文档。