Spring 3.0 中的任務排程簡化

工程 | Mark Fisher | 2010年01月05日 | ...

KeithChris 開啟的 Spring 3.0 “簡化系列”之後,我想簡要概述一下 Spring 3.0 在排程和任務執行方面所帶來的簡化。

我將透過一個簡單的 示例應用程式 進行講解,您可以從 spring-samples Subversion 倉庫檢出。該應用程式的設計力求簡潔,同時展示了 Spring 3.0 中基於註解和基於 XML 的任務排程方法。

讓我們從基於註解的方法開始。你可以直接透過 AnnotationDemo 中的 main() 方法來執行它。如果你仔細檢視,你會發現它不過是一個 Spring ApplicationContext 的載入程式。


public static void main(String[] args) {
    new ClassPathXmlApplicationContext("config.xml", AnnotationDemo.class);
}

之所以不需要其他東西,是因為 ApplicationContext 包含了一個“活動”元件,我們稍後就會看到。正是因為這個元件,main() 方法才不會退出。config.xml 也同樣精簡,只包含兩個元素。


<context:component-scan base-package="org/springframework/samples/task/basic/annotation"/>

<task:annotation-driven/>

“component-scan”元素指向包含我們“beans”的包。有兩個:ScheduledProcessor 和 AsyncWorker。我們稍後會看它們,但首先來看看“annotation-driven”元素。這是 Spring 3.0 中新引入的,它驅動了兩個註解:@Scheduled 和 @Async。你可以分別使用“scheduler”和“executor”屬性來引用 Spring TaskScheduler 和 TaskExecutor,但對於這個示例,我們將只使用預設值。

ScheduledProcessor 在一個方法上包含 @Scheduled 註解,因此它是上面提到的“活動”元件。由於配置中存在“annotation-driven”元素,此方法將被註冊到 Spring TaskScheduler 例項,該例項將以 30 秒的固定延遲週期性地執行該方法。


@Service
public class ScheduledProcessor implements Processor {

    private final AtomicInteger counter = new AtomicInteger();

    @Autowired
    private Worker worker;

    @Scheduled(fixedDelay = 30000)
    public void process() {
        System.out.println("processing next 10 at " + new Date());
        for (int i = 0; i < 10; i++) {
            worker.work(counter.incrementAndGet());
        }
    }
}

正如你在上一個程式碼片段中看到的,Worker 在迴圈中被 ScheduledProcessor 呼叫。然而,AsyncWorker 實現包含對其 work(..) 方法的 @Async 註解,並且由於配置中的“annotation-driven”元素,它將被包裝在一個代理中,以便該方法實際上由 TaskExecutor 例項呼叫。為了驗證這一點,當前執行緒名稱會在該方法中顯示。同樣,為了說明工作是併發進行的,會呼叫 sleep(..) 來模擬耗時的操作。


@Component
public class AsyncWorker implements Worker {

    @Async
    public void work(int i) {
        String threadName = Thread.currentThread().getName(); 
        System.out.println("   " + threadName + " beginning work on " + i);
        try {
            Thread.sleep(5000); // simulates work
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("   " + threadName + " completed work on " + i);
    }
}

如果你執行 AnnotationDemo 的 main() 方法,輸出應該類似於這樣: processing next 10 at Mon Jan 04 18:20:52 EST 2010 SimpleAsyncTaskExecutor-1 beginning work on 1 SimpleAsyncTaskExecutor-2 beginning work on 2 SimpleAsyncTaskExecutor-3 beginning work on 3 SimpleAsyncTaskExecutor-5 beginning work on 5 SimpleAsyncTaskExecutor-4 beginning work on 4 SimpleAsyncTaskExecutor-6 beginning work on 6 SimpleAsyncTaskExecutor-7 beginning work on 7 SimpleAsyncTaskExecutor-8 beginning work on 8 SimpleAsyncTaskExecutor-9 beginning work on 9 SimpleAsyncTaskExecutor-10 beginning work on 10 SimpleAsyncTaskExecutor-1 completed work on 1 SimpleAsyncTaskExecutor-2 completed work on 2 SimpleAsyncTaskExecutor-3 completed work on 3 SimpleAsyncTaskExecutor-5 completed work on 5 SimpleAsyncTaskExecutor-6 completed work on 6 SimpleAsyncTaskExecutor-7 completed work on 7 SimpleAsyncTaskExecutor-8 completed work on 8 SimpleAsyncTaskExecutor-4 completed work on 4 SimpleAsyncTaskExecutor-10 completed work on 10 SimpleAsyncTaskExecutor-9 completed work on 9

關於該輸出有幾點需要注意。首先,一次處理 10 行將每 30 秒重複一次(由於 @Scheduled)。其次,工作項由不同的執行緒併發處理(由於 @Async)。在最後一個“beginning work”訊息和第一個“completed work”訊息之間應該有大約 5 秒的延遲。如果所有工作程式都在單個執行緒中執行,我們將看到順序的 beginning/completed 對,整個過程將花費大約 50 秒。當然,時間方面無法在此博文中捕獲,因此你真的應該 下載 並自行執行示例以獲得完整體驗(該專案可以直接匯入到 SpringSource Tool Suite 或其他支援 Maven 的 Eclipse 類環境中)。

我想展示的最後一件事是 @Scheduled 註解的基於 XML 的替代方案。該示例包含另一個類 SimpleProcessor,它在其 process() 方法上沒有 @Scheduled 註解。


@Service
public class SimpleProcessor implements Processor {

    private final AtomicInteger counter = new AtomicInteger();

    public void process() {
        System.out.println("processing next 10 at " + new Date());
        for (int i = 0; i < 10; i++) {
            System.out.println("   processing " + counter.incrementAndGet());
        }
    }
}

XML 比基於註解的方法只稍微冗長一些,因為 Spring 3.0 現在提供了一個“task”名稱空間來保持配置的簡潔。


<context:component-scan base-package="org/springframework/samples/task/basic/xml"/>

<task:scheduled-tasks>
    <task:scheduled ref="simpleProcessor" method="process" cron="3/10 * * * * ?"/>
</task:scheduled-tasks>

如果你執行 XmlDemo 中的 main() 方法,你會發現 process 每 10 秒執行一次。為了多樣化,這個使用了 cron 表示式而不是簡單的固定延遲。因此,你會注意到時間是基於 3 秒的偏移量(:13, :23 等),當然 cron 表示式可以更強大(我只是不想建立一個僅在特定日期或時間執行的示例)。值得指出的是,對 cron 表示式排程的支援直接包含在 Spring 3.0 本身中。

一定要查閱 Spring 3.0 參考手冊任務執行和排程章節,以瞭解更多關於新的 TaskScheduler 抽象和 Trigger 策略的資訊,它們為你在這裡看到的內容提供了基礎。參考手冊還討論了“task”名稱空間提供的其他元素,用於配置具有特定執行緒池設定的 TaskScheduler 和 TaskExecutor 例項。

希望這篇博文對這些新功能進行了有用的概述。請繼續關注 SpringSource 團隊部落格,瞭解更多 Spring 3.0 相關內容。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有