Spring Scheduler定時(shí)任務(wù)實(shí)戰(zhàn)指南(零基礎(chǔ)入門任務(wù)調(diào)度)
前言
在日常開發(fā)中,我們經(jīng)常需要處理定時(shí)任務(wù):每天凌晨的數(shù)據(jù)同步、每小時(shí)的統(tǒng)計(jì)報(bào)表、每5分鐘的狀態(tài)檢查等。Spring框架提供了一個(gè)簡(jiǎn)單而強(qiáng)大的定時(shí)任務(wù)框架——Spring Scheduler,讓我們能夠以聲明的方式輕松實(shí)現(xiàn)各種定時(shí)任務(wù)需求。
本文將通過一個(gè)真實(shí)案例,帶你從入門到掌握Spring Scheduler的使用。
一、Spring Scheduler簡(jiǎn)介
Spring Scheduler是Spring框架提供的定時(shí)任務(wù)調(diào)度器,它基于注解和配置的方式,讓任務(wù)調(diào)度變得簡(jiǎn)單直觀。主要特點(diǎn)包括:
- 支持cron表達(dá)式、固定延遲、固定頻率等多種調(diào)度方式
- 與Spring容器無(wú)縫集成,可直接使用Spring管理的Bean
- 支持異步執(zhí)行和線程池配置
- 無(wú)需額外依賴,Spring Boot中開箱即用
二、項(xiàng)目場(chǎng)景:電商訂單超時(shí)處理
假設(shè)我們有一個(gè)電商系統(tǒng),需要處理訂單超時(shí)自動(dòng)關(guān)閉的功能:訂單創(chuàng)建后30分鐘內(nèi)未支付,系統(tǒng)自動(dòng)將其標(biāo)記為已關(guān)閉。
環(huán)境準(zhǔn)備
在Spring Boot項(xiàng)目中,首先確保添加了基礎(chǔ)依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>Spring Scheduler已經(jīng)在Spring Boot的web starter中包含,無(wú)需額外添加依賴。
啟用定時(shí)任務(wù)
在Spring Boot主類或配置類上添加@EnableScheduling注解:
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}三、實(shí)現(xiàn)訂單超時(shí)檢查任務(wù)
1. 創(chuàng)建訂單服務(wù)
首先創(chuàng)建一個(gè)訂單服務(wù)類,包含基本的訂單處理方法:
@Service
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
// 模擬訂單存儲(chǔ)
private Map<Long, Order> orderMap = new ConcurrentHashMap<>();
private AtomicLong idGenerator = new AtomicLong(0);
/**
* 創(chuàng)建新訂單
*/
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("創(chuàng)建訂單成功,訂單ID: {}", orderId);
return order;
}
/**
* 檢查并處理超時(shí)訂單
*/
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("訂單超時(shí)已關(guān)閉,訂單ID: {}", order.getId());
}
}
}
}
/**
* 獲取訂單狀態(tài)
*/
public OrderStatus getOrderStatus(Long orderId) {
Order order = orderMap.get(orderId);
return order != null ? order.getStatus() : null;
}
}
/**
* 訂單狀態(tài)枚舉
*/
public enum OrderStatus {
CREATED, // 已創(chuàng)建
PAID, // 已支付
CLOSED // 已關(guān)閉
}
/**
* 訂單實(shí)體類
*/
public class Order {
private Long id;
private String productName;
private BigDecimal amount;
private Date createTime;
private Date closeTime;
private OrderStatus status;
// 省略getter和setter方法
}2. 實(shí)現(xiàn)定時(shí)任務(wù)
現(xiàn)在創(chuàng)建定時(shí)任務(wù)類,定期檢查超時(shí)訂單:
@Component
public class OrderTimeoutScheduler {
private static final Logger logger = LoggerFactory.getLogger(OrderTimeoutScheduler.class);
@Autowired
private OrderService orderService;
/**
* 每5分鐘檢查一次超時(shí)訂單
* 使用cron表達(dá)式:每5分鐘執(zhí)行一次
*/
@Scheduled(cron = "0 */5 * * * ?")
public void checkOrderTimeout() {
logger.info("開始執(zhí)行訂單超時(shí)檢查任務(wù)...");
long startTime = System.currentTimeMillis();
try {
orderService.checkAndCloseTimeoutOrders();
} catch (Exception e) {
logger.error("訂單超時(shí)檢查任務(wù)執(zhí)行失敗", e);
}
long endTime = System.currentTimeMillis();
logger.info("訂單超時(shí)檢查任務(wù)執(zhí)行完成,耗時(shí):{}ms", (endTime - startTime));
}
/**
* 另一種方式:固定延遲執(zhí)行
* 上一次任務(wù)執(zhí)行完成后,延遲5分鐘再執(zhí)行
*/
// @Scheduled(fixedDelay = 5 * 60 * 1000)
// public void checkOrderTimeoutWithFixedDelay() {
// // 實(shí)現(xiàn)邏輯
// }
/**
* 固定頻率執(zhí)行
* 每5分鐘執(zhí)行一次,無(wú)論上一次任務(wù)是否完成
*/
// @Scheduled(fixedRate = 5 * 60 * 1000)
// public void checkOrderTimeoutWithFixedRate() {
// // 實(shí)現(xiàn)邏輯
// }
}3. 測(cè)試定時(shí)任務(wù)
創(chuàng)建一個(gè)測(cè)試控制器來(lái)驗(yàn)證我們的定時(shí)任務(wù):
@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();
}
}啟動(dòng)應(yīng)用后,你可以:
- 通過POST
/orders創(chuàng)建訂單 - 等待5分鐘,查看日志中定時(shí)任務(wù)的執(zhí)行情況
- 30分鐘后,通過GET
/orders/{id}/status檢查訂單狀態(tài)是否變?yōu)镃LOSED
四、cron表達(dá)式詳解
Spring Scheduler支持標(biāo)準(zhǔn)的cron表達(dá)式,由6個(gè)字段組成(Spring支持7個(gè)字段,包含秒):
秒 分 時(shí) 日 月 周 年(可選)
常用cron表達(dá)式示例:
0 * * * * ?:每分鐘執(zhí)行一次0 */5 * * * ?:每5分鐘執(zhí)行一次0 0 * * * ?:每小時(shí)執(zhí)行一次0 0 0 * * ?:每天凌晨執(zhí)行0 0 12 * * ?:每天中午12點(diǎn)執(zhí)行0 0 10,14,16 * * ?:每天10點(diǎn)、14點(diǎn)、16點(diǎn)執(zhí)行
五、高級(jí)配置:線程池與異步執(zhí)行
默認(rèn)情況下,Spring Scheduler使用單線程執(zhí)行所有定時(shí)任務(wù)。如果任務(wù)較多或執(zhí)行時(shí)間較長(zhǎng),可能需要配置線程池:
@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;
}
}對(duì)于需要異步執(zhí)行的任務(wù),可以結(jié)合@Async注解使用:
@Async
@Scheduled(fixedRate = 5000)
public void asyncScheduledTask() {
// 這個(gè)任務(wù)會(huì)在單獨(dú)的線程中異步執(zhí)行
}確保在配置類上添加@EnableAsync注解。
六、最佳實(shí)踐與注意事項(xiàng)
- ??任務(wù)冪等性??:確保定時(shí)任務(wù)可以多次執(zhí)行而不會(huì)產(chǎn)生副作用
- ??異常處理??:在任務(wù)內(nèi)部妥善處理異常,避免影響其他任務(wù)執(zhí)行
- ??分布式環(huán)境??:在集群部署時(shí),需要考慮使用分布式鎖或只在一臺(tái)實(shí)例上執(zhí)行
- ??避免長(zhǎng)時(shí)間執(zhí)行??:長(zhǎng)時(shí)間運(yùn)行的任務(wù)會(huì)影響其他定時(shí)任務(wù)的執(zhí)行
- ??配置化??:將cron表達(dá)式放在配置文件中,便于不同環(huán)境調(diào)整
# application.properties
order.timeout.cron=0 */5 * * * ?
@Scheduled(cron = "${order.timeout.cron}")
public void checkOrderTimeout() {
// ...
}七、總結(jié)
Spring Scheduler提供了簡(jiǎn)單而強(qiáng)大的定時(shí)任務(wù)功能,通過本文的電商訂單超時(shí)處理案例,我們可以看到:
- 使用
@EnableScheduling啟用定時(shí)任務(wù)支持 - 通過
@Scheduled注解聲明定時(shí)方法,支持cron表達(dá)式、固定延遲和固定頻率 - 定時(shí)任務(wù)方法可以是任何Spring管理的Bean的方法
- 可以通過配置線程池來(lái)優(yōu)化任務(wù)執(zhí)行性能
- 結(jié)合
@Async可以實(shí)現(xiàn)異步定時(shí)任務(wù)
Spring Scheduler雖然功能強(qiáng)大,但在分布式環(huán)境中需要注意任務(wù)重復(fù)執(zhí)行的問題。對(duì)于復(fù)雜的分布式調(diào)度需求,可以考慮使用Quartz或XXL-Job等專業(yè)調(diào)度框架。
希望本文能幫助你理解和掌握Spring Scheduler的使用,為你的項(xiàng)目開發(fā)提供便利。
到此這篇關(guān)于Spring Scheduler定時(shí)任務(wù)實(shí)戰(zhàn):從零掌握任務(wù)調(diào)度的文章就介紹到這了,更多相關(guān)Spring Scheduler定時(shí)任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Java模擬鼠標(biāo)和鍵盤的詳細(xì)操作步驟
這篇文章主要介紹了使用Java模擬鼠標(biāo)和鍵盤的詳細(xì)操作步驟,要運(yùn)行上面提供的Java程序,您需要遵循幾個(gè)步驟來(lái)設(shè)置Java環(huán)境、編寫程序代碼,并執(zhí)行該程序,文中有相關(guān)的代碼示例,需要的朋友可以參考下2024-05-05
springboot集成mqtt的實(shí)踐開發(fā)
本篇文章主要介紹了springboot集成mqtt的實(shí)踐開發(fā),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-08-08
maven install報(bào)錯(cuò)中程序包xxx不存在的問題解決
本文主要介紹了maven install報(bào)錯(cuò)中程序包xxx不存在的問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
Java實(shí)現(xiàn)對(duì)象排序的兩種方式詳解
這篇文章主要介紹了Java實(shí)現(xiàn)對(duì)象排序的兩種方式詳解,在Java中經(jīng)常會(huì)涉及到對(duì)象數(shù)組的排序問題,則就提到對(duì)象之間的比較問題,今天我們就來(lái)看一下兩種不同排序方式之間的區(qū)別,需要的朋友可以參考下2023-09-09
SpringBoot實(shí)現(xiàn)海量數(shù)據(jù)高效實(shí)時(shí)搜索功能
我們都知道隨著業(yè)務(wù)系統(tǒng)的發(fā)展和使用,數(shù)據(jù)庫(kù)存儲(chǔ)的業(yè)務(wù)數(shù)據(jù)量會(huì)越來(lái)越大,逐漸成為了業(yè)務(wù)系統(tǒng)的瓶頸,本文給大家介紹了Spring Boot業(yè)務(wù)系統(tǒng)如何實(shí)現(xiàn)海量數(shù)據(jù)高效實(shí)時(shí)搜索,文中有詳細(xì)的代碼示例,需要的朋友可以參考下2023-10-10
Spring Security實(shí)現(xiàn)身份認(rèn)證和授權(quán)的示例代碼
在 Spring Boot 應(yīng)用中使用 Spring Security 可以非常方便地實(shí)現(xiàn)用戶身份認(rèn)證和授權(quán),本文主要介紹了Spring Security實(shí)現(xiàn)身份認(rèn)證和授權(quán)的示例代碼,感興趣的可以了解一下2023-06-06
使用Java注解和反射實(shí)現(xiàn)JSON字段自動(dòng)重命名
這篇文章主要介紹了如何使用Java注解和反射實(shí)現(xiàn)JSON字段自動(dòng)重命名,文中通過代碼示例和圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-08-08

