任务调度
Spring 提供了强大的任务调度支持,包括定时任务和异步任务。本章介绍 Spring 任务调度的配置和使用方法。
启用任务调度
@Configuration
@EnableScheduling
@EnableAsync
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-");
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(60);
return scheduler;
}
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
定时任务
@Scheduled 注解
@Scheduled 注解用于声明定时任务,支持多种配置方式。
固定延迟:任务完成后等待指定时间再执行下一次:
@Component
public class ScheduledTasks {
@Scheduled(fixedDelay = 5000)
public void taskWithFixedDelay() {
System.out.println("固定延迟任务: " + LocalDateTime.now());
}
}
固定频率:按固定频率执行,不管任务是否完成:
@Scheduled(fixedRate = 5000)
public void taskWithFixedRate() {
System.out.println("固定频率任务: " + LocalDateTime.now());
}
初始延迟:首次执行前等待指定时间:
@Scheduled(initialDelay = 10000, fixedRate = 5000)
public void taskWithInitialDelay() {
System.out.println("初始延迟任务: " + LocalDateTime.now());
}
Cron 表达式:使用 Cron 表达式定义执行时间:
@Scheduled(cron = "0 0 12 * * ?")
public void taskAtNoon() {
System.out.println("每天中午12点执行");
}
@Scheduled(cron = "0 0 9-17 * * MON-FRI")
public void taskOnWorkHours() {
System.out.println("工作日9-17点每小时执行");
}
@Scheduled(cron = "0 0 0 1 * ?")
public void taskOnFirstDayOfMonth() {
System.out.println("每月1号零点执行");
}
Cron 表达式
Cron 表达式格式:秒 分 时 日 月 周 [年]
| 字段 | 允许值 | 允许的特殊字符 |
|---|---|---|
| 秒 | 0-59 | , - * / |
| 分 | 0-59 | , - * / |
| 时 | 0-23 | , - * / |
| 日 | 1-31 | , - * ? / L W |
| 月 | 1-12 或 JAN-DEC | , - * / |
| 周 | 1-7 或 SUN-SAT | , - * ? / L # |
| 年 | 1970-2099 | , - * / |
特殊字符说明:
| 字符 | 说明 |
|---|---|
| * | 所有值 |
| ? | 不指定值(日和周互斥) |
| - | 范围 |
| , | 列举 |
| / | 增量 |
| L | 最后 |
| W | 工作日 |
| # | 第几个 |
常用 Cron 表达式示例:
0 0 12 * * ? 每天12点
0 15 10 ? * * 每天10:15
0 15 10 * * ? 每天10:15
0 15 10 * * ? * 每天10:15
0 15 10 * * ? 2024 2024年每天10:15
0 * 14 * * ? 每天14:00-14:59每分钟
0 0/5 14 * * ? 每天14:00-14:55每5分钟
0 0/5 14,18 * * ? 每天14:00-14:55和18:00-18:55每5分钟
0 0-5 14 * * ? 每天14:00-14:05每分钟
0 10,44 14 ? 3 WED 3月每周三14:10和14:44
0 15 10 ? * MON-FRI 周一到周五10:15
0 15 10 15 * ? 每月15号10:15
0 15 10 L * ? 每月最后一天10:15
0 15 10 ? * 6L 每月最后一个周五10:15
0 15 10 ? * 6#3 每月第三个周五10:15
配置化 Cron 表达式
@Component
public class ConfigurableScheduledTask {
@Scheduled(cron = "${task.cleanup.cron}")
public void cleanupTask() {
System.out.println("执行清理任务");
}
@Scheduled(fixedDelayString = "${task.report.delay:60000}")
public void reportTask() {
System.out.println("执行报表任务");
}
}
application.properties:
task.cleanup.cron=0 0 2 * * ?
task.report.delay=300000
异步任务
@Async 注解
@Async 注解用于声明异步方法,方法会在单独的线程中执行。
@Service
public class EmailService {
@Async
public void sendEmail(String to, String subject, String content) {
try {
Thread.sleep(2000);
System.out.println("发送邮件到: " + to + ", 线程: " + Thread.currentThread().getName());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Async
public CompletableFuture<String> sendEmailAsync(String to, String subject, String content) {
try {
Thread.sleep(2000);
return CompletableFuture.completedFuture("邮件已发送到: " + to);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return CompletableFuture.failedFuture(e);
}
}
}
调用异步方法
@Service
public class NotificationService {
@Autowired
private EmailService emailService;
public void notifyUser(User user) {
emailService.sendEmail(user.getEmail(), "通知", "您有新消息");
System.out.println("通知已发送");
}
public void notifyUsers(List<User> users) {
List<CompletableFuture<String>> futures = users.stream()
.map(user -> emailService.sendEmailAsync(user.getEmail(), "通知", "您有新消息"))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> System.out.println("所有邮件发送完成"));
}
}
自定义异步线程池
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("emailExecutor")
public Executor emailExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("email-");
executor.initialize();
return executor;
}
@Bean("reportExecutor")
public Executor reportExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(3);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("report-");
executor.initialize();
return executor;
}
}
@Service
public class EmailService {
@Async("emailExecutor")
public void sendEmail(String to, String subject, String content) {
}
}
@Service
public class ReportService {
@Async("reportExecutor")
public void generateReport() {
}
}
异步异常处理
@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
System.err.println("异步任务异常: " + method.getName());
System.err.println("异常信息: " + ex.getMessage());
ex.printStackTrace();
}
}
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncExceptionHandler();
}
}
实际应用示例
数据清理任务
@Component
@Slf4j
public class DataCleanupTask {
@Autowired
private LogDao logDao;
@Autowired
private TempFileDao tempFileDao;
@Scheduled(cron = "0 0 2 * * ?")
public void cleanOldLogs() {
log.info("开始清理旧日志数据");
LocalDateTime threshold = LocalDateTime.now().minusDays(30);
int deleted = logDao.deleteByCreatedAtBefore(threshold);
log.info("清理完成,删除 {} 条日志记录", deleted);
}
@Scheduled(cron = "0 0 3 * * ?")
public void cleanTempFiles() {
log.info("开始清理临时文件");
LocalDateTime threshold = LocalDateTime.now().minusDays(7);
int deleted = tempFileDao.deleteByCreatedAtBefore(threshold);
log.info("清理完成,删除 {} 个临时文件", deleted);
}
}
报表生成任务
@Component
@Slf4j
public class ReportTask {
@Autowired
private ReportService reportService;
@Autowired
private EmailService emailService;
@Scheduled(cron = "0 0 8 * * MON")
public void generateWeeklyReport() {
log.info("开始生成周报");
LocalDate endDate = LocalDate.now();
LocalDate startDate = endDate.minusWeeks(1);
Report report = reportService.generateWeeklyReport(startDate, endDate);
emailService.sendEmail("[email protected]", "周报",
"本周报表已生成,请查看附件");
log.info("周报生成完成");
}
@Scheduled(cron = "0 0 1 1 * ?")
public void generateMonthlyReport() {
log.info("开始生成月报");
LocalDate endDate = LocalDate.now().minusDays(1);
LocalDate startDate = endDate.withDayOfMonth(1);
Report report = reportService.generateMonthlyReport(startDate, endDate);
emailService.sendEmail("[email protected]", "月报",
"本月报表已生成,请查看附件");
log.info("月报生成完成");
}
}
异步数据处理
@Service
public class DataProcessService {
@Autowired
private DataProcessor dataProcessor;
@Async
public CompletableFuture<ProcessResult> processLargeFile(String filePath) {
ProcessResult result = dataProcessor.process(filePath);
return CompletableFuture.completedFuture(result);
}
public void batchProcess(List<String> filePaths) {
List<CompletableFuture<ProcessResult>> futures = filePaths.stream()
.map(this::processLargeFile)
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
List<ProcessResult> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
saveResults(results);
});
}
private void saveResults(List<ProcessResult> results) {
}
}
小结
本章介绍了 Spring 任务调度:
- 启用任务调度:@EnableScheduling、@EnableAsync
- 定时任务:@Scheduled 注解,支持 fixedDelay、fixedRate、cron
- Cron 表达式:灵活的时间配置
- 异步任务:@Async 注解,返回 CompletableFuture
- 自定义线程池:为不同任务配置不同线程池
- 异常处理:AsyncUncaughtExceptionHandler
- 实际应用:数据清理、报表生成、异步处理
Spring 的任务调度功能简化了定时任务和异步处理的开发,是构建企业级应用的重要工具。