各位被Thread.sleep()
和while(true)
折磨的Spring道友们!今天要解锁的是Spring生态自带的定时任务三件套——@Scheduled
、TaskScheduler
、@Async
定时组合技!无需引入外部依赖,轻松实现从简单定时到分布式调度的全场景覆盖!准备好抛弃Quartz的复杂配置了吗? ⏰
一、筑基篇:@Scheduled基础用法
1.1 开启定时任务(激活灵脉)
@SpringBootApplication
@EnableScheduling // 关键注解!放在启动类上
public class MyApp {public static void main(String[] args) {SpringApplication.run(MyApp.class, args);}
}
1.2 定时方法配置(三套仙术)
@Component
public class MyTask {// 1. 固定速率(上次开始后间隔固定时间)@Scheduled(fixedRate = 5000) // 每5秒执行public void task1() {System.out.println("固定速率任务:" + LocalDateTime.now());}// 2. 固定延迟(上次结束后间隔固定时间)@Scheduled(fixedDelay = 3000) // 每次执行完等3秒public void task2() throws InterruptedException {Thread.sleep(1000); // 模拟耗时System.out.println("固定延迟任务:" + LocalDateTime.now());}// 3. Cron表达式(天庭历法)@Scheduled(cron = "0 0/5 9-18 * * MON-FRI") // 工作日9-18点每5分钟public void task3() {System.out.println("Cron任务:" + LocalDateTime.now());}
}
二、金丹篇:高级配置技巧
2.1 动态修改定时规则(天机可变)
@RestController
public class TaskController {@Autowiredprivate ScheduledTaskRegistrar taskRegistrar;@PostMapping("/update-task")public String updateTask(@RequestParam String newCron) {// 取消原有任务taskRegistrar.destroy();// 添加新任务taskRegistrar.addCronTask(() -> System.out.println("动态任务执行: " + LocalDateTime.now()),newCron);taskRegistrar.afterPropertiesSet();return "任务已更新为: " + newCron;}
}
2.2 异步定时任务(分身术)
@Configuration
@EnableAsync // 开启异步支持
public class AsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(100);executor.initialize();return executor;}
}@Component
public class AsyncTask {@Async@Scheduled(fixedRate = 3000)public void asyncTask() {System.out.println("异步任务线程:" + Thread.currentThread().getName());}
}
三、元婴篇:分布式定时任务
3.1 基于Redis的分布式锁(防重复执行)
@Component
public class DistributedTask {@Autowiredprivate RedissonClient redisson;@Scheduled(cron = "0 0/5 * * * ?")public void distributedJob() {RLock lock = redisson.getLock("scheduled:lock");try {if (lock.tryLock(0, 30, TimeUnit.SECONDS)) {System.out.println("获取锁成功,执行任务...");// 业务代码...}} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}
}
3.2 结合ShedLock实现(推荐)
<!-- 添加依赖 -->
<dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-spring</artifactId><version>4.42.0</version>
</dependency>
<dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-provider-jdbc-template</artifactId><version>4.42.0</version>
</dependency>
@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
public class ShedLockConfig {@Beanpublic LockProvider lockProvider(DataSource dataSource) {return new JdbcTemplateLockProvider(dataSource);}
}@Service
public class ShedLockTask {@Scheduled(cron = "0 0/5 * * * ?")@SchedulerLock(name = "reportTask", lockAtLeastFor = "5m")public void scheduledTask() {// 保证同一时间只有一个实例执行}
}
四、化神篇:监控与管理
4.1 暴露执行端点(Spring Boot Actuator)
# application.yml
management:endpoints:web:exposure:include: scheduledtasksendpoint:scheduledtasks:enabled: true
访问/actuator/scheduledtasks
查看所有定时任务:
{"cron": [{"runnable": {"target": "com.example.MyTask.task3"},"expression": "0 0/5 9-18 * * MON-FRI"}],"fixedDelay": [],"fixedRate": []
}
4.2 自定义任务监控(天眼通)
@Bean
public ScheduledTaskPostProcessor taskPostProcessor() {return new ScheduledTaskPostProcessor() {@Overridepublic void postProcessAfterInitialization(Object bean, String beanName) {// 监控所有定时任务初始化System.out.println("已加载定时任务: " + beanName);}};
}
五、大乘篇:最佳实践
5.1 任务执行日志记录
@Aspect
@Component
public class TaskLogAspect {@Around("@annotation(scheduled)")public Object logTask(ProceedingJoinPoint pjp, Scheduled scheduled) throws Throwable {String taskName = pjp.getSignature().toShortString();long start = System.currentTimeMillis();try {System.out.println("任务开始: " + taskName);return pjp.proceed();} finally {System.out.printf("任务结束: %s, 耗时: %dms%n", taskName, System.currentTimeMillis() - start);}}
}
5.2 异常处理策略
@Scheduled(fixedRate = 5000)
public void errorProneTask() {try {// 业务代码...} catch (Exception e) {// 1. 记录异常到数据库// 2. 发送报警邮件// 3. 根据策略决定是否重试System.err.println("任务执行失败: " + e.getMessage());}
}
渡劫指南:常见问题
问题 | 解决方案 |
---|---|
任务不执行 | 检查@EnableScheduling 是否启用 |
Cron表达式无效 | 使用在线工具校验表达式 |
多任务串行执行 | 配置TaskScheduler 线程池 |
分布式环境重复执行 | 集成ShedLock或Redis分布式锁 |
时区问题 | 设置spring.task.scheduling.pool.size |