Spring 3.0 中的任務排程簡化

工程 | Mark Fisher | 2010年1月5日 | ...

繼續 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" 元素指向包含我們“bean”的包。它們有兩個: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)。最後一個“開始工作”訊息和第一個“完成工作”訊息之間應該有大約 5 秒的暫停。如果所有工作都在單個執行緒中執行,我們將看到順序的開始/完成對,整個過程大約需要 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 表示式比這強大得多(我只是不想建立一個只在特定日期或時間執行的示例)。值得指出的是,Spring 3.0 本身就直接包含了對基於 cron 的排程的支援。

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

我希望這篇文章對這些新特性提供了一個有用的概述。請繼續關注 SpringSource 團隊部落格,獲取更多與 Spring 3.0 相關的內容。

訂閱 Spring 新聞通訊

訂閱 Spring 新聞通訊,保持聯絡

訂閱

搶佔先機

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

瞭解更多

獲取支援

Tanzu Spring 透過一個簡單的訂閱提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將舉辦的活動

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

檢視全部