Spring Task 详细介绍

Spring Framework 提供了强大的任务调度和执行功能,主要包括定时任务(@Scheduled)、异步任务(@Async)和任务执行器(TaskExecutor)。下面我将详细介绍这些功能。

一、定时任务(Scheduled Tasks)

1. 基础配置

首先需要在配置类上添加@EnableScheduling注解启用定时任务功能:

@Configuration
@EnableScheduling
public class SchedulingConfig {
}

2. @Scheduled 注解详解

@Scheduled注解支持多种参数配置:

@Service
public class ScheduledService {
    
    // 固定频率执行,单位毫秒
    @Scheduled(fixedRate = 5000)
    public void fixedRateTask() {
        System.out.println("固定频率任务,每5秒执行一次");
    }
    
    // 固定延迟执行,上一次执行完成后延迟指定时间再执行
    @Scheduled(fixedDelay = 3000)
    public void fixedDelayTask() {
        System.out.println("固定延迟任务,执行完成后3秒再执行");
    }
    
    // 初始延迟,第一次执行前延迟时间
    @Scheduled(initialDelay = 1000, fixedRate = 5000)
    public void initialDelayTask() {
        System.out.println("初始延迟1秒,之后每5秒执行一次");
    }
    
    // Cron表达式
    @Scheduled(cron = "0 0/5 * * * ?")
    public void cronTask() {
        System.out.println("每5分钟执行一次");
    }
    
    // 使用占位符从配置文件中读取
    @Scheduled(cron = "${task.cron}")
    public void configurableTask() {
        System.out.println("可配置的定时任务");
    }
}

3 Cron表达式语法详解及常用示例

Cron表达式是用于配置定时任务执行时间的强大工具,Spring Task完全支持标准Cron表达式语法。下面详细介绍语法规则和常用示例。

3.1 Cron表达式语法结构

标准Cron表达式由6或7个字段组成(年字段可选),字段之间用空格分隔:

秒 分 时 日 月 周 年(可选)

各字段含义及取值范围:

字段 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
0-23 , - * /
1-31 , - * ? / L W C
1-12或JAN-DEC , - * /
0-6或SUN-SAT , - * ? / L C #
1970-2099(可选) , - * /
3.2 特殊字符含义
  1. * - 表示所有值(每…)
  2. ? - 不指定值(用于日和周字段互斥)
  3. - - 指定范围(如1-5)
  4. , - 指定多个值(如1,3,5)
  5. / - 指定增量(如0/15表示从0开始每15单位)
  6. L - 最后(Last)
  7. W - 最近工作日(Weekday)
  8. # - 第几个周几(如6#3表示第3个周五)
3.3 常用Cron表达式示例
3.3.1. 基础定时
表达式 说明
0 0/5 * * * ? 每5分钟执行一次
0 0 * * * ? 每小时执行一次
0 0 12 * * ? 每天中午12点执行
0 15 10 ? * * 每天10:15执行
0 15 10 * * ? 同上,每天10:15执行
0 15 10 * * ? * 同上,每天10:15执行
3.3.2. 工作日/周末
表达式 说明
0 0 9 ? * MON-FRI 工作日每天9点执行
0 0 12 ? * SAT,SUN 周末每天中午12点执行
3.3.3. 每月/每周特定时间
表达式 说明
0 0 10 15 * ? 每月15号10点执行
0 0 12 1 * ? 每月1号中午12点执行
0 0 10 ? * 6L 每月最后一个周五10点执行
0 0 10 ? * 6#3 每月第3个周五10点执行
3.3.4. 复杂时间组合
表达式 说明
0 0 8-18 ? * MON-FRI 工作日8点到18点每小时执行
0 0/30 9-17 ? * MON-FRI 工作日9点到17点每半小时执行
0 0 12 ? * WED 每周三中午12点执行
0 0 12 1/5 * ? 每月从1号开始每5天中午12点执行
3.3.5. 特殊时间点
表达式 说明
0 0 0 1 1 ? 每年1月1日0点执行(元旦)
0 0 0 24 12 ? 每年12月24日0点执行(平安夜)
0 0 0 * * ? 每天0点执行
0 0 0 1 * ? 每月1日0点执行
3.4、Spring中的特殊用法
  1. 占位符支持

    @Scheduled(cron = "${jobs.cron.expression}")
    public void job() {
        // 任务逻辑
    }
    
  2. 宏表达式(Spring特有):

    • @yearly@annually - 每年一次 (0 0 0 1 1 *)
    • @monthly - 每月一次 (0 0 0 1 * *)
    • @weekly - 每周一次 (0 0 0 ? * SUN)
    • @daily@midnight - 每天一次 (0 0 0 * * ?)
    • @hourly - 每小时一次 (0 0 * * * ?)

    使用示例:

    @Scheduled(cron = "@hourly")
    public void hourlyJob() {
        // 每小时执行的任务
    }
    
3.5、注意事项
  1. 日和周字段互斥:这两个字段中必须有一个是?,否则会产生冲突

    • 正确:0 0 0 ? * MON (每周一)
    • 正确:0 0 0 1 * ? (每月1号)
    • 错误:0 0 0 1 * MON (冲突)
  2. Spring与标准Cron的区别

    • Spring中周字段:1=SUN, 2=MON,…,7=SAT
    • 标准Unix Cron中:0=SUN, 1=MON,…,6=SAT
  3. 闰秒:Cron表达式不支持闰秒(60)

  4. 夏令时:在夏令时转换时可能会出现意外行为

3.6、在线工具推荐
  1. Cron表达式生成器
  2. Spring Cron表达式验证器

通过合理使用Cron表达式,您可以精确控制Spring Task的执行时间,满足各种复杂的调度需求。

4. 动态定时任务

实现SchedulingConfigurer接口可以实现动态定时任务:

@Configuration
public class DynamicSchedulingConfig implements SchedulingConfigurer {
    
    @Value("${dynamic.task.interval}")
    private long interval;
    
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addTriggerTask(
            () -> System.out.println("动态定时任务执行"),
            triggerContext -> {
                // 可以动态计算下次执行时间
                return new Date(System.currentTimeMillis() + interval);
            }
        );
    }
}

二、异步任务(Async Tasks)

1. 基础配置

启用异步任务支持:

@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}

2. @Async 注解使用

@Service
public class AsyncService {
    
    @Async
    public void asyncTaskWithoutReturn() {
        System.out.println("执行无返回值的异步任务");
    }
    
    @Async
    public Future<String> asyncTaskWithReturn() {
        return new AsyncResult<>("异步任务结果");
    }
    
    @Async("taskExecutor")  // 指定执行器
    public void asyncTaskWithExecutor() {
        System.out.println("使用特定线程池执行的异步任务");
    }
}

3. 异常处理

@Configuration
public class AsyncExceptionConfig implements AsyncConfigurer {
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            System.err.println("异步任务异常: " + method.getName());
            ex.printStackTrace();
        };
    }
}

三、任务执行器(TaskExecutor)

Spring提供了多种TaskExecutor实现:

1. ThreadPoolTaskExecutor

@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(10);
    executor.setQueueCapacity(25);
    executor.setThreadNamePrefix("MyExecutor-");
    executor.initialize();
    return executor;
}

2. 其他执行器

// 简单异步执行器
@Bean
public SimpleAsyncTaskExecutor simpleAsyncTaskExecutor() {
    return new SimpleAsyncTaskExecutor();
}

// 同步执行器
@Bean
public SyncTaskExecutor syncTaskExecutor() {
    return new SyncTaskExecutor();
}

// 定时任务执行器
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
    scheduler.setPoolSize(5);
    scheduler.setThreadNamePrefix("Scheduler-");
    return scheduler;
}

四、高级特性

1. 条件化任务执行

@Scheduled(fixedRate = 5000)
@ConditionalOnProperty(name = "task.enabled", havingValue = "true")
public void conditionalTask() {
    System.out.println("条件满足时执行的任务");
}

2. 任务生命周期管理

@Service
public class TaskLifecycleService implements SchedulingConfigurer {
    
    private ScheduledFuture<?> future;
    
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(1);
        scheduler.initialize();
        
        future = scheduler.scheduleAtFixedRate(
            () -> System.out.println("可管理的任务"),
            5000
        );
    }
    
    public void pauseTask() {
        if (future != null) {
            future.cancel(false);
        }
    }
    
    public void resumeTask() {
        // 重新启动任务
    }
}

3. 分布式任务调度

结合ShedLock实现分布式锁:

@Scheduled(cron = "0 0/5 * * * ?")
@SchedulerLock(name = "distributedTask", lockAtLeastFor = "4m", lockAtMostFor = "5m")
public void distributedTask() {
    System.out.println("分布式环境下确保单实例执行的任务");
}

五、最佳实践

  1. 线程池配置:根据任务特性合理配置线程池大小
  2. 异常处理:确保异步任务有完善的异常处理机制
  3. 任务幂等性:确保任务可以安全地重复执行
  4. 资源清理:长时间运行的任务要妥善管理资源
  5. 监控:添加任务执行日志和监控指标

六、常见问题

  1. 任务不执行

    • 检查是否添加了@EnableScheduling@EnableAsync
    • 检查组件是否被Spring管理(@Component等)
  2. 任务执行时间不准确

    • 检查任务执行时间是否超过间隔时间
    • 考虑使用fixedDelay代替fixedRate
  3. 线程池耗尽

    • 增加线程池大小
    • 优化任务执行时间

Spring的任务调度功能非常强大且灵活,可以根据实际需求选择合适的实现方式。对于复杂场景,可以考虑集成Quartz等专业调度框架。

七 案例

订单状态定时处理

  @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨一点触发
    public void processCompletedOrder() {
        log.info("处理已完成订单:{}", LocalDateTime.now());
        List<Orders> ordersList = orderMapper.completedTimeoutOrder();
        if (ordersList != null && !ordersList.isEmpty()) {
            for (Orders order : ordersList) {
                order.setStatus(Orders.COMPLETED);
            }
        }
        if (!ordersList.isEmpty()) {
            orderMapper.updateBatch(ordersList);
        }
    }
Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐