前言
在日常开发中,我们经常需要处理定时任务:每天凌晨的数据同步、每小时的统计报表、每5分钟的状态检查等。Spring框架提供了一个简单而强大的定时任务框架——Spring Scheduler,让我们能够以声明的方式轻松实现各种定时任务需求。
本文将通过一个真实案例,带你从入门到掌握Spring Scheduler的使用。
一、Spring Scheduler简介
Spring Scheduler是Spring框架提供的定时任务调度器,它基于注解和配置的方式,让任务调度变得简单直观。主要特点包括:
- 支持cron表达式、固定延迟、固定频率等多种调度方式
- 与Spring容器无缝集成,可直接使用Spring管理的Bean
- 支持异步执行和线程池配置
- 无需额外依赖,Spring Boot中开箱即用
二、项目场景:电商订单超时处理
假设我们有一个电商系统,需要处理订单超时自动关闭的功能:订单创建后30分钟内未支付,系统自动将其标记为已关闭。
环境准备
在Spring Boot项目中,首先确保添加了基础依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Spring Scheduler已经在Spring Boot的web starter中包含,无需额外添加依赖。
启用定时任务
在Spring Boot主类或配置类上添加@EnableScheduling
注解:
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
三、实现订单超时检查任务
1. 创建订单服务
首先创建一个订单服务类,包含基本的订单处理方法:
@Service
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
// 模拟订单存储
private Map<Long, Order> orderMap = new ConcurrentHashMap<>();
private AtomicLong idGenerator = new AtomicLong(0);
/**
* 创建新订单
*/
public Order createOrder(Order order) {
Long orderId = idGenerator.incrementAndGet();
order.setId(orderId);
order.setCreateTime(new Date());
order.setStatus(OrderStatus.CREATED);
orderMap.put(orderId, order);
logger.info("创建订单成功,订单ID: {}", orderId);
return order;
}
/**
* 检查并处理超时订单
*/
public void checkAndCloseTimeoutOrders() {
Date now = new Date();
int timeoutMinutes = 30;
for (Order order : orderMap.values()) {
if (OrderStatus.CREATED.equals(order.getStatus())) {
long diffInMillis = now.getTime() - order.getCreateTime().getTime();
long diffInMinutes = diffInMillis / (1000 * 60);
if (diffInMinutes >= timeoutMinutes) {
order.setStatus(OrderStatus.CLOSED);
order.setCloseTime(now);
logger.info("订单超时已关闭,订单ID: {}", order.getId());
}
}
}
}
/**
* 获取订单状态
*/
public OrderStatus getOrderStatus(Long orderId) {
Order order = orderMap.get(orderId);
return order != null ? order.getStatus() : null;
}
}
/**
* 订单状态枚举
*/
public enum OrderStatus {
CREATED, // 已创建
PAID, // 已支付
CLOSED // 已关闭
}
/**
* 订单实体类
*/
public class Order {
private Long id;
private String productName;
private BigDecimal amount;
private Date createTime;
private Date closeTime;
private OrderStatus status;
// 省略getter和setter方法
}
2. 实现定时任务
现在创建定时任务类,定期检查超时订单:
@Component
public class OrderTimeoutScheduler {
private static final Logger logger = LoggerFactory.getLogger(OrderTimeoutScheduler.class);
@Autowired
private OrderService orderService;
/**
* 每5分钟检查一次超时订单
* 使用cron表达式:每5分钟执行一次
*/
@Scheduled(cron = "0 */5 * * * ?")
public void checkOrderTimeout() {
logger.info("开始执行订单超时检查任务...");
long startTime = System.currentTimeMillis();
try {
orderService.checkAndCloseTimeoutOrders();
} catch (Exception e) {
logger.error("订单超时检查任务执行失败", e);
}
long endTime = System.currentTimeMillis();
logger.info("订单超时检查任务执行完成,耗时:{}ms", (endTime - startTime));
}
/**
* 另一种方式:固定延迟执行
* 上一次任务执行完成后,延迟5分钟再执行
*/
// @Scheduled(fixedDelay = 5 * 60 * 1000)
// public void checkOrderTimeoutWithFixedDelay() {
// // 实现逻辑
// }
/**
* 固定频率执行
* 每5分钟执行一次,无论上一次任务是否完成
*/
// @Scheduled(fixedRate = 5 * 60 * 1000)
// public void checkOrderTimeoutWithFixedRate() {
// // 实现逻辑
// }
}
3. 测试定时任务
创建一个测试控制器来验证我们的定时任务:
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
Order createdOrder = orderService.createOrder(order);
return ResponseEntity.ok(createdOrder);
}
@GetMapping("/{orderId}/status")
public ResponseEntity<OrderStatus> getOrderStatus(@PathVariable Long orderId) {
OrderStatus status = orderService.getOrderStatus(orderId);
return status != null ?
ResponseEntity.ok(status) :
ResponseEntity.notFound().build();
}
}
启动应用后,你可以:
- 通过POST
/orders
创建订单 - 等待5分钟,查看日志中定时任务的执行情况
- 30分钟后,通过GET
/orders/{id}/status
检查订单状态是否变为CLOSED
四、cron表达式详解
Spring Scheduler支持标准的cron表达式,由6个字段组成(Spring支持7个字段,包含秒):
秒 分 时 日 月 周 年(可选)
常用cron表达式示例:
0 * * * * ?
:每分钟执行一次0 */5 * * * ?
:每5分钟执行一次0 0 * * * ?
:每小时执行一次0 0 0 * * ?
:每天凌晨执行0 0 12 * * ?
:每天中午12点执行0 0 10,14,16 * * ?
:每天10点、14点、16点执行
五、高级配置:线程池与异步执行
默认情况下,Spring Scheduler使用单线程执行所有定时任务。如果任务较多或执行时间较长,可能需要配置线程池:
@Configuration
public class SchedulerConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 线程池大小
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.setAwaitTerminationSeconds(60);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
return scheduler;
}
}
对于需要异步执行的任务,可以结合@Async
注解使用:
@Async
@Scheduled(fixedRate = 5000)
public void asyncScheduledTask() {
// 这个任务会在单独的线程中异步执行
}
确保在配置类上添加@EnableAsync
注解。
六、最佳实践与注意事项
- 任务幂等性:确保定时任务可以多次执行而不会产生副作用
- 异常处理:在任务内部妥善处理异常,避免影响其他任务执行
- 分布式环境:在集群部署时,需要考虑使用分布式锁或只在一台实例上执行
- 避免长时间执行:长时间运行的任务会影响其他定时任务的执行
- 配置化:将cron表达式放在配置文件中,便于不同环境调整
# application.properties
order.timeout.cron=0 */5 * * * ?
@Scheduled(cron = "${order.timeout.cron}")
public void checkOrderTimeout() {
// ...
}
七、总结
Spring Scheduler提供了简单而强大的定时任务功能,通过本文的电商订单超时处理案例,我们可以看到:
- 使用
@EnableScheduling
启用定时任务支持 - 通过
@Scheduled
注解声明定时方法,支持cron表达式、固定延迟和固定频率 - 定时任务方法可以是任何Spring管理的Bean的方法
- 可以通过配置线程池来优化任务执行性能
- 结合
@Async
可以实现异步定时任务
Spring Scheduler虽然功能强大,但在分布式环境中需要注意任务重复执行的问题。对于复杂的分布式调度需求,可以考虑使用Quartz或XXL-Job等专业调度框架。
希望本文能帮助你理解和掌握Spring Scheduler的使用,为你的项目开发提供便利。