跳到主要内容

任务调度

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 任务调度:

  1. 启用任务调度:@EnableScheduling、@EnableAsync
  2. 定时任务:@Scheduled 注解,支持 fixedDelay、fixedRate、cron
  3. Cron 表达式:灵活的时间配置
  4. 异步任务:@Async 注解,返回 CompletableFuture
  5. 自定义线程池:为不同任务配置不同线程池
  6. 异常处理:AsyncUncaughtExceptionHandler
  7. 实际应用:数据清理、报表生成、异步处理

Spring 的任务调度功能简化了定时任务和异步处理的开发,是构建企业级应用的重要工具。