一、Scheduled 定时任务
【1】添加 Scheduled相关依赖,它是 Spring自带的一个 jar包因此引入 Spring的依赖:
1 <dependency> 2 <groupId>org.springframework</groupId> 3 <artifactId>spring-context-support</artifactId> 4 </dependency>
【2】导入依赖之后,就可以在 Maven Dependencies中看到相关的依赖,如下:
【3】编写定时任务类:重点是 @Scheduled 注解和 cron 属性;
1 /** 2 * Scheduled 定时任务 3 * 定时任务不属于持久层也不属于业务层,所以应该使用 @Component 进行标记 4 * @author Administrator 5 * 6 */ 7 @Component 8 public class ScheduledDemo { 9 /** 10 * 定时任务方法,如果是定时任务方法,需要添加 scheduled注解 11 * scheduled:表示当前方法就是一个定时任务方法 12 * cron属性: 定时任务触发时间的一个字符串表达式 13 * 触发条件:每2秒触发一次,博客后面重点说 cron 表达式 14 */ 15 @Scheduled(cron="0/2 * * * * ?") 16 public void scheduledMethod() { 17 System.out.println("定时任务"+new Date()); 18 } 19 }
【4】在启动类中开启定时任务的启动: @EnableScheduling 注解
1 @SpringBootApplication 2 @EnableScheduling 3 public class ScheduledApplication { 4 5 public static void main(String[] args) { 6 SpringApplication.run(ScheduledApplication.class, args); 7 } 8 }
【5】cron 表达式:是一个字符串,分为 6 或 7 个域(建议使用 6个域),每个域代表一个含义 :
■ 7个域:Seconds Minutes Hours Day Month Week Year (秒、分、小时、月份中的日期、月分、星期中的日期、年)
■ 6个域(少一个 Year):Seconds Minutes Hours Day Month Week
【6】各cron 字段的含义:星期和日是有冲突的,一般会舍掉一个。表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感;
位置 | 时间域名 | 允许值 | 允许的特殊字符 |
1 | 秒 | 0-59 | , - * / |
2 | 分钟 | 0-59 | , - * / |
3 | 小时 | 0-23 | , - * / |
4 | 日 | 0-31 | , - * / ? L W C |
5 | 月 | 1-12 | , - * / |
6 | 星期 | 1-7 | , - * / ? L C # |
7 | 年 | 1970-2099 | , - * / |
①、星号(*):可用在所有字段中,表示对应时间域的每一个时刻。例如在秒时间域中表示每一秒;
②、问号(?):该字段只在星期和日期域中使用,它通常指定为“无意义的值”,相当于一个占位符;
③、减号(-):表示的是一个范围,如在小时字段中使用1-3,则表示 1,2,3点都执行;
④、逗号(,):表示一个列表值,如在秒域中使用 12,15表示第12秒和第15秒触发跑批;
⑤、斜杠(/):x/y 表示一个等步长序列,x为起始值,y为增量步长值,例如秒中使用 0/2 表示从0秒开始,没过2秒执行一次。
⑥、L:该字符只在日期和星期域中使用,但它在两个字段中的意思不同。L在日期字段表示月份的最后一天,如一月的31,二月的28等,如果在星期域中表示星期六(7),但是如果L出现在星期字段里,而且前面有一个数值 X,则表示这个月的最后 X 天,例如:6L表示该月的最后星期五;
⑦、W:该字符只能出现在日期域中,表示离该日期最近的工作日,例如 15W:表示离该月15号最近的工作日,如果15号是星期六则匹配星期五 14号。如果15号是星期日则匹配星期一 16日。如果15号是星期三则匹配星期三 15号本身。但需要注意关联的匹配不能够跨月,例如15号是2月的最后一天星期日,应该向下配置3月1日,但是不能跨月,只能匹配2月26星期五(2月最后一个工作日)W只能指定单一日期,不能指定范围;
⑧、LW组合:在日期字段组合使用,表示当月的最后一个工作日;
⑨、#号:该字符只能在星期域中使用,表示当月某个工作日。如6#3:表示当月的第三个星期五。
⑩、C:该字符只在日期和星期中使用,表示“Calendar” 的意思,表示计划所关联的日期。如果日期没有被关联,则表示所有日期。例如5C在日期域中表示 5日以后的第一天。1C在星期域中表示星期日后的第一天。
二、Springboot 整合Quartz 定时任务框架
Quartz 是 OpenSymphony开源组织在 Job scheduling(任务调度)领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz 可以用来创建简单或为运行十个,百个,甚至是好几万个 Jobs这样复杂的程序。Jobs 可以做成标准的 Java组件或 EJBs。Quartz是一个完全由 Java编写的开源作业调度框架。
【1】quartz 的 maven 依赖:
1 <dependency> 2 <groupId>org.quartz-scheduler</groupId> 3 <artifactId>quartz</artifactId> 4 <version>2.3.2</version> 5 </dependency>
【2】 Job(任务:你要做什么事):
1 import org.quartz.Job; 2 import org.quartz.JobExecutionContext; 3 import org.quartz.JobExecutionException; 4 5 /** 6 * 定时任务类 7 * @author zzx 8 * 9 */ 10 public class QuartzDemo implements Job{ 11 /** 12 * 任务触发时所执行的方法 13 */ 14 public void execute(JobExecutionContext context) throws JobExecutionException { 15 System.out.println("任务调度"+new Date()); 16 } 17 }
【3】Trigger(触发器:什么时候去做):有两种形式进行表达,其中一种就是 cron 表达式
【4】scheduler(任务调度:你什么时候需要做什么事):将 job 与 Trigger 进行整合。下面是一个例子:
1 import org.quartz.CronScheduleBuilder; 2 import org.quartz.JobBuilder; 3 import org.quartz.JobDetail; 4 import org.quartz.Scheduler; 5 import org.quartz.SimpleScheduleBuilder; 6 import org.quartz.Trigger; 7 import org.quartz.TriggerBuilder; 8 import org.quartz.impl.StdSchedulerFactory; 9 10 public class QuartzMain { 11 public static void main(String[] args) throws Exception { 12 //1、Job(任务:你要做什么事),这里使用的是建造者模式 13 JobDetail job = JobBuilder.newJob(QuartzDemo.class).build(); 14 //2、Trigger(触发器:什么时候去做),这里 triggerbuilder 也是用建造者模式封装。触发条件分为两种 15 //3.1 第一种:简单的 trigger 触发时间,通过 Quartz 提供方法完成简单的重复调用。如下一个例子:每秒触发一次 16 //Trigger build = TriggerBuilder.newTrigger().withSchedule(SimpleScheduleBuilder.repeatSecondlyForever()).build(); 17 //3.2 第二种:按照 cron 的表达式来给定触发时间 18 Trigger trigger= TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")).build(); 19 //3、scheduler(任务调度:你什么时候需要做什么事) 将上面的job 和 trigger进行组装 这里使用工厂模式。 20 Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler(); 21 defaultScheduler.scheduleJob(job, trigger); 22 //4、启动 23 defaultScheduler.start(); 24 } 25 }
三、SpringBoot 整合 Quartz
【1】整合时相关依赖一下:
1 <dependencies> 2 <dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-web</artifactId> 5 </dependency> 6 7 <dependency> 8 <groupId>org.springframework.boot</groupId> 9 <artifactId>spring-boot-starter-test</artifactId> 10 <scope>test</scope> 11 <exclusions> 12 <exclusion> 13 <groupId>org.junit.vintage</groupId> 14 <artifactId>junit-vintage-engine</artifactId> 15 </exclusion> 16 </exclusions> 17 </dependency> 18 <dependency> 19 <groupId>org.springframework</groupId> 20 <artifactId>spring-context-support</artifactId> 21 </dependency> 22 23 <!--Quartz 坐标--> 24 <dependency> 25 <groupId>org.quartz-scheduler</groupId> 26 <artifactId>quartz</artifactId> 27 <version>2.3.0</version> 28 <exclusions> 29 <exclusion> 30 <groupId>org.slf4j</groupId> 31 <artifactId>slf4j-api</artifactId> 32 </exclusion> 33 </exclusions> 34 </dependency> 35 36 <!--事务--> 37 <dependency> 38 <groupId>org.springframework</groupId> 39 <artifactId>spring-tx</artifactId> 40 </dependency> 41 </dependencies>
【2】 创建一个 Job 类:实现 Job 接口
1 /** 2 * 定时任务类 3 * @author zzx 4 * 5 */ 6 public class QuartzDemo implements Job { 7 /** 8 * 任务触发时所执行的方法 9 */ 10 public void execute(JobExecutionContext context) throws JobExecutionException { 11 System.out.println("任务调度"+new Date()); 12 } 13 }
【3】编写 Quartz 的配置参数
1 import com.example.demo.scheduled.QuartzDemo; 2 import org.springframework.context.annotation.Bean; 3 import org.springframework.context.annotation.Configuration; 4 import org.springframework.scheduling.quartz.CronTriggerFactoryBean; 5 import org.springframework.scheduling.quartz.JobDetailFactoryBean; 6 import org.springframework.scheduling.quartz.SchedulerFactoryBean; 7 import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; 8 9 /** 10 * quartz 配置类 11 */ 12 @Configuration 13 public class QuartzConfig { 14 //1、创建 Job 对象 15 @Bean 16 public JobDetailFactoryBean jobDetailFactoryBean(){ 17 JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean(); 18 //通过反射的方式实例化 Job,并没有经过 spring 处理,所以依赖的对象不能通过 autowrite 注入 19 jobDetailFactoryBean.setJobClass(QuartzDemo.class); 20 return jobDetailFactoryBean; 21 } 22 23 //2、创建 Trigger 对象:也是分为两种:下面是创建一个简单的 trigger 24 //这里需要传入 JOB 对象,作为参数关联 25 /* @Bean 26 public SimpleTriggerFactoryBean simpleTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean){ 27 SimpleTriggerFactoryBean simpleTriggerFactoryBean = new SimpleTriggerFactoryBean(); 28 //获取 JobDetail 关联 29 simpleTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject()); 30 // 触发条件 该参数表示一个执行的毫秒数 31 simpleTriggerFactoryBean.setRepeatInterval(2000); 32 //设置重复次数 33 simpleTriggerFactoryBean.setRepeatCount(5); 34 return simpleTriggerFactoryBean; 35 }*/ 36 37 //通过 cron 表达式表示执行 trigger 38 @Bean 39 public CronTriggerFactoryBean cronTriggerFactoryBean(){ 40 CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); 41 //获取 JobDetail 关联 42 cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean().getObject()); 43 // 触发条件 该参数表示一个执行的毫秒数 44 cronTriggerFactoryBean.setCronExpression("0/2 * * * * ?"); 45 return cronTriggerFactoryBean; 46 } 47 48 // 创建 scheduler 对象 49 @Bean 50 public SchedulerFactoryBean schedulerFactoryBean(){ 51 SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); 52 //关联 Trigger 53 schedulerFactoryBean.setTriggers(cronTriggerFactoryBean().getObject()); 54 return schedulerFactoryBean; 55 } 56 }
【3】修改启动类
1 /** 2 * 整合 Quartz 3 */ 4 @SpringBootApplication 5 @EnableScheduling 6 public class ScheduledApplication { 7 8 public static void main(String[] args) { 9 SpringApplication.run(ScheduledApplication.class, args); 10 } 11 }
四、Job 类中注入对象
【1】给 Job 类中注入 Service 类型的对象;
1 /** 2 * 定时任务类 3 * @author zzx 4 * 5 */ 6 public class QuartzDemo implements Job { 7 8 @Autowired 9 private UserService userService; 10 /** 11 * 任务触发时所执行的方法 12 */ 13 public void execute(JobExecutionContext context) throws JobExecutionException { 14 userService.test(); 15 System.out.println("任务调度"+new Date()); 16 } 17 }
【2】当运行时会出现空指针异常:userService 对象为空;
1 Caused by: java.lang.NullPointerException: null 2 at com.example.demo.scheduled.QuartzDemo.execute(QuartzDemo.java:25) ~[classes/:na] 3 at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.3.0.jar:na] 4 ... 1 common frames omitted
【3】没有注入 userService 的原因如下,它是通过反射创建 Job 对象,并没有经过 SpringIOC 处理,所以依赖的对象不能通过 autowrite 注入;
1 protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { 2 Class<?> jobClass = bundle.getJobDetail().getJobClass(); 3 return ReflectionUtils.accessibleConstructor(jobClass, new Class[0]).newInstance(new Object[0]); 4 }
【4】解决方案:重写创建 Job 的方式:并进行实例化 @Component;
1 @Component 2 public class MyAdaptableJobFactory extends AdaptableJobFactory { 3 //将对象添加到SpringIoc容器中,并且完成该对象的属性注入 4 @Autowired 5 private AutowireCapableBeanFactory autowireCapableBeanFactory; 6 7 /** 8 * 该方法需要将实例化的 job 对象手动添加到SpringIOC 容器中并且完成实例化; 9 */ 10 @Override 11 public Object createJobInstance(TriggerFiredBundle bundle) throws Exception { 12 Object jobInstance = super.createJobInstance(bundle); 13 //将该对象加入 IOC 容器中 14 autowireCapableBeanFactory.autowireBean(jobInstance); 15 return jobInstance; 16 } 17 }
【5】 将其注入到 Quartz 的配置类中,注入到 SchedulerFactoryBean 中;
1 // 修改 SchedulerFactoryBean 类,set 创建 job 的factory 类 2 @Bean 3 public SchedulerFactoryBean schedulerFactoryBean(MyAdaptableJobFactory myAdaptableJobFactory){ 4 SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); 5 //关联 Trigger 6 schedulerFactoryBean.setTriggers(cronTriggerFactoryBean().getObject()); 7 schedulerFactoryBean.setJobFactory(myAdaptableJobFactory); 8 return schedulerFactoryBean; 9 }
【6】测试结果:
【7】解决方案:在 SSM 的项目中可以通过如下方式获取依赖对象;
1 //根据spring的配置文件得到ioc容器对象 2 ApplicationContext context = 3 new ClassPathXmlApplicationContext("spring.xml"); 4 Person person = context.getBean(Person.class);