简介
Spring提供了任务调度的@Scheduled注解
入门
-
安装依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> -
编写任务类
@Component public class ScheduleTask { private final Logger log = LoggerFactory.getLogger(ScheduleTask.class); @Scheduled(cron = "1/2 * * * * ?") public void task1() throws InterruptedException { log.info("task-1, 我需要执行10秒的时间, 线程Id:{}, 线程名:{},时间:{}", Thread.currentThread().getId(), Thread.currentThread().getName(), LocalDateTime.now()); Thread.sleep(10000); log.error("task-1 ending....., 线程Id:{}, 线程名:{},时间:{}", Thread.currentThread().getId(), Thread.currentThread().getName(), LocalDateTime.now()); } } -
配置主启动类
@SpringBootApplication // 开启定时任务功能 @EnableScheduling public class SchedulerApplication { public static void main(String[] args) { SpringApplication.run(SchedulerApplication.class, args); } } -
启动运行
@Scheduled
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
String CRON_DISABLED = "-";
String cron() default "";
String zone() default "";
long fixedDelay() default -1;
String fixedDelayString() default "";
long fixedRate() default -1;
String fixedRateString() default "";
long initialDelay() default -1;
String initialDelayString() default "";
}
cron :corn表达式可以灵活的配置任务的执行时间
@Scheduled(cron = "0 * * * * MON-FRI")
public void scheduleTaskUsingCronExpression() {
// 省略
}
fixedDelay :配置一个固定延迟后运行的任务。 控制最后一次执行完成时的下一个执行时间 ,上一个任务执行完成后,过多长时间执行下一个任务
@Scheduled(fixedDelay = 1000)
public void scheduleFixedDelayTask() {
// 省略
}
fixedRate :使Spring定期运行任务,即配置固定的时间间隔执行任务。如果上一个任务阻塞,下一个任务也不会执行,默认使用的同一个线程,可以开启异步注解*@Async*,但是可能会产生数据不一致问题
@Scheduled(fixedRate = 1000)
public void scheduleFixedRateTask() {
// 省略
}
zone :时区 initialDelay :容器启动后,延迟多久执行任务
多任务并发执行
现在再添加一个定时任务,测试当跑多个定时任务
@Scheduled(cron = "1/4 * * * * ?")
public void task2() throws InterruptedException {
log.info("task-2, 我需要执行两秒时间, 线程Id:{}, 线程名:{},时间:{}",
Thread.currentThread().getId(), Thread.currentThread().getName(), LocalDateTime.now());
Thread.sleep(2000);
log.error("task-2 ending....., 线程Id:{}, 线程名:{},时间:{}",
Thread.currentThread().getId(), Thread.currentThread().getName(), LocalDateTime.now());
}
执行结果
可以发现两个任务是串行执行的,其他任务必须等到另一任务执行完成才能执行 默认情况下,Spring Boot将仅使用一个线程来运行所有计划的任务
创建了一个新的配置类,该类扩展了SchedulingConfigurer。能够配置任务计划程序,并传入我们要使用的线程池大小
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
private int corePoolSize = 10;
private String threadNamePrefix = "scheduled-task-pool-";
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(corePoolSize);
executor.setThreadNamePrefix(threadNamePrefix);
executor.initialize();
taskRegistrar.setScheduler(executor);
}
}
配置完成,执行可以发现两个定时任务可以并发执行
动态配置执行时间
之前的执行时间都是写死在代码中的,现在通过数据库动态配置执行时间。 数据库脚本
drop table if exists `spring_scheduled_cron`;
create table `spring_scheduled_cron` (
`cron_id` int primary key auto_increment
comment '主键id',
`cron_key` varchar(128) not null unique
comment '定时任务完整类名',
`cron_expression` varchar(20) not null
comment 'cron表达式',
`task_explain` varchar(50) not null default ''
comment '任务描述',
`status` tinyint not null default 1
comment '状态,1:正常;2:停用',
unique index cron_key_unique_idx(`cron_key`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COMMENT = '定时任务表';
insert into `spring_scheduled_cron`
values (1, 'com.kxj.task.DynamicPrintTask', '*/5 * * * * ?', '定时任务描述', 1);
insert into `spring_scheduled_cron`
values (2, 'com.kxj.task.DynamicPrintTask1', '*/5 * * * * ?', '定时任务描述1', 1);
insert into `spring_scheduled_cron`
values (3, 'com.kxj.task.DynamicPrintTask2', '*/5 * * * * ?', '定时任务描述2', 1);
实体类
@Data
@ToString
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "spring_scheduled_cron")
public class SpringScheduledCron {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cron_id")
private Integer cronId;
@Column(name = "cron_key", unique = true)
private String cronKey;
@Column(name = "cron_expression")
private String cronExpression;
@Column(name = "task_explain")
private String taskExplain;
@Column(name = "status")
private Integer status;
}
Scheduled配置类
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
private int corePoolSize = 10;
@Autowired
SpringScheduledCronRepository springScheduledCronRepository;
@Autowired
ApplicationContext context;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler executor = getThreadPoolTaskScheduler();
// 设置线程池
taskRegistrar.setScheduler(executor);
List<SpringScheduledCron> springScheduledCrons = springScheduledCronRepository.findAll();
for (SpringScheduledCron springScheduledCron : springScheduledCrons) {
Class<?> clazz = null;
Object task = null;
try {
// 利用反射读取数据库配置的定时任务
clazz = Class.forName(springScheduledCron.getCronKey());
task = context.getBean(clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
taskRegistrar.addTriggerTask(((Runnable) task), triggerContext -> {
String cronExpression = springScheduledCronRepository.findByCronKey(springScheduledCron.getCronKey()).getCronExpression();
return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
});
}
}
private ThreadPoolTaskScheduler getThreadPoolTaskScheduler() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(corePoolSize);
executor.setThreadNamePrefix("scheduled-task-pool-");
executor.initialize();
return executor;
}
}
定时任务
// 也可以直接实现Runnable接口,重写run方法
@Component
public class DynamicPrintTask implements ScheduledOfTask {
private Logger logger = LoggerFactory.getLogger(getClass());
private int i;
@Override
public void execute() {
logger.info("thread id:{},DynamicPrintTask execute times:{}", Thread.currentThread().getId(), ++i);
}
}
// ScheduledOfTask1和ScheduledOfTask2同ScheduledOfTask
public interface ScheduledOfTask extends Runnable {
/**
* 定时任务方法
*/
void execute();
/**
* 实现控制定时任务启用或禁用的功能
*/
@Override
default void run() {
SpringScheduledCronRepository repository = SpringUtils.getBean(SpringScheduledCronRepository.class);
SpringScheduledCron scheduledCron = repository.findByCronKey(this.getClass().getName());
if (StatusEnum.DISABLED.getCode().equals(scheduledCron.getStatus())) {
// 任务是禁用状态
return;
}
execute();
}
}
public enum StatusEnum {
ENABLED(1),
DISABLED(2) ;
private Integer code;
StatusEnum(int code) {
this.code = code;
}
public Integer getCode() {
return code;
}
}