Spring Frameworkにおける@Scheduledアノテーション詳細ガイド

Spring Frameworkにおける@Scheduledアノテーション詳細ガイド

目次

  • 概要
  • 基本的な設定
  • @Scheduledパラメータ詳解
  • 高度な使い方
  • 例外処理
  • 実践的な応用例
  • 注意点
  • まとめ

概要

@ScheduledはSpring Frameworkでタスクスケジューリングに使用されるアノテーションで、定時タスク機能を簡単に実装できます。Springのタスクスケジューリング抽象化に基づいており、多様なスケジューリング方式をサポートしています。

基本的な設定

定時タスクの有効化

設定クラス方式:

@Configuration
@EnableScheduling
public class SchedulingConfig {
    // 設定クラス
}

Spring Bootメインクラス方式:

@SpringBootApplication
@EnableScheduling
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@Scheduledパラメータ詳解

1. cron式

@Component
public class ScheduledTasks {
    
    /**
     * cron式フォーマット: [秒] [分] [時] [日] [月] [週] [年] (年は任意)
     */
    @Scheduled(cron = "0 * * * * ?")  // 1分ごとに実行
    public void taskWithCron() {
        System.out.println("定時タスク実行: " + new Date());
    }
    
    // 毎日午前10:15に実行
    @Scheduled(cron = "0 15 10 * * ?")
    public void dailyTask() {
        // 業務ロジック
    }
    
    // 毎週月曜午前9時に実行
    @Scheduled(cron = "0 0 9 ? * MON")
    public void weeklyTask() {
        // 業務ロジック
    }
}

2. fixedRate

@Component
public class FixedRateTasks {
    
    /**
     * fixedRate: 固定レートで実行
     * 前回の開始時間から次回の開始時間を計算
     */
    @Scheduled(fixedRate = 5000)  // 5秒ごとに実行
    public void taskWithFixedRate() {
        System.out.println("FixedRateタスク実行: " + new Date());
    }
    
    // 時間単位の組み合わせ
    @Scheduled(fixedRate = 2, timeUnit = TimeUnit.HOURS)
    public void taskWithTimeUnit() {
        // 2時間ごとに実行
    }
}

3. fixedDelay

@Component
public class FixedDelayTasks {
    
    /**
     * fixedDelay: 固定遅延で実行
     * 前回の完了時間から次回の開始時間を計算
     */
    @Scheduled(fixedDelay = 3000)  // 前回実行完了後3秒後に実行
    public void taskWithFixedDelay() {
        try {
            Thread.sleep(1000);  // タスク実行時間をシミュレート
            System.out.println("FixedDelayタスク実行: " + new Date());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

4. initialDelay

@Component
public class InitialDelayTasks {
    
    /**
     * initialDelay: 初期遅延
     * アプリケーション起動後に指定時間遅らせて初回タスクを実行
     */
    @Scheduled(initialDelay = 10000, fixedRate = 5000)
    public void taskWithInitialDelay() {
        System.out.println("初期遅延付きタスク実行: " + new Date());
    }
}

高度な使い方

1. 設定ファイルからパラメータを読み込む

@Component
public class ConfigurableScheduledTasks {
    
    @Scheduled(cron = "${task.cron.expression:0 0/5 * * * ?}")
    public void configurableTask() {
        System.out.println("設定可能な定時タスク: " + new Date());
    }
    
    @Scheduled(fixedRateString = "${task.fixed.rate:5000}")
    public void configurableFixedRateTask() {
        System.out.println("設定可能な固定レートタスク: " + new Date());
    }
}

設定ファイル application.properties:

task.cron.expression=0 */10 * * * ?
task.fixed.rate=10000

設定ファイル application.yml:

task:
  cron:
    expression: "0 */10 * * * ?"
  fixed:
    rate: 10000

2. 条件判断を使用する

@Component
@ConditionalOnProperty(name = "scheduling.enabled", havingValue = "true")
public class ConditionalScheduledTask {
    
    @Scheduled(fixedRate = 5000)
    public void conditionalTask() {
        System.out.println("条件付き定時タスク実行: " + new Date());
    }
}

3. 非同期実行

@Component
@EnableAsync
public class AsyncScheduledTasks {
    
    @Async
    @Scheduled(fixedRate = 5000)
    public void asyncTask() {
        System.out.println("非同期定時タスク開始: " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);  // 時間のかかる操作をシミュレート
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("非同期定時タスク終了: " + Thread.currentThread().getName());
    }
}

例外処理

1. 基本的な例外処理

@Component
public class ExceptionHandlingTasks {
    
    @Scheduled(fixedRate = 5000)
    public void taskWithExceptionHandling() {
        try {
            // 業務ロジック
            System.out.println("タスク実行: " + new Date());
            // 発生する可能性のある例外をシミュレート
            if (new Random().nextBoolean()) {
                throw new RuntimeException("例外をシミュレート");
            }
        } catch (Exception e) {
            System.err.println("タスク実行例外: " + e.getMessage());
            // ログ記録、アラート送信など
        }
    }
}

2. カスタムタスケジューラを使用する

@Configuration
public class SchedulerConfig {
    
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("scheduled-task-");
        scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.setAwaitTerminationSeconds(60);
        return scheduler;
    }
}

実践的な応用例

1. データクリーンタスク

@Component
public class DataCleanupTask {
    
    private final DataCleanupService dataCleanupService;
    
    public DataCleanupTask(DataCleanupService dataCleanupService) {
        this.dataCleanupService = dataCleanupService;
    }
    
    /**
     * 毎日午前2時に30日前のデータをクリーンアップ
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void cleanupOldData() {
        try {
            System.out.println("データクリーンタスク開始: " + new Date());
            dataCleanupService.cleanupDataOlderThan(30);
            System.out.println("データクリーンタスク完了: " + new Date());
        } catch (Exception e) {
            System.err.println("データクリーンタスク失敗: " + e.getMessage());
            // アラート通知を送信
        }
    }
}

2. キャッシュリフレッシュタスク

@Component
public class CacheRefreshTask {
    
    private final CacheService cacheService;
    
    public CacheRefreshTask(CacheService cacheService) {
        this.cacheService = cacheService;
    }
    
    /**
     * 5分ごとにキャッシュをリフレッシュ
     */
    @Scheduled(fixedRate = 5 * 60 * 1000)
    public void refreshCache() {
        System.out.println("キャッシュをリフレッシュ: " + new Date());
        cacheService.refreshAllCaches();
    }
}

3. ヘルスチェックタスク

@Component
public class HealthCheckTask {
    
    private final HealthCheckService healthCheckService;
    private final NotificationService notificationService;
    
    public HealthCheckTask(HealthCheckService healthCheckService, 
                          NotificationService notificationService) {
        this.healthCheckService = healthCheckService;
        this.notificationService = notificationService;
    }
    
    /**
     * 30秒ごとにヘルスチェックを実行
     */
    @Scheduled(fixedRate = 30000)
    public void healthCheck() {
        HealthStatus status = healthCheckService.checkSystemHealth();
        if (!status.isHealthy()) {
            notificationService.sendAlert("システムヘルスチェック失敗: " + status.getMessage());
        }
    }
}

注意点

1. スレッド単一問題

デフォルトでは、Springの@Scheduledは単一スレッドで全ての定時タスクを実行します。タスクの実行時間が長い場合、他のタスクの実行に影響を与える可能性があります。

解決策:

@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {
    
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }
    
    @Bean(destroyMethod = "shutdown")
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(10);
    }
}

2. クラスタ環境での重複実行

クラスタ環境では、定時タスクが複数のインスタンスで重複して実行されないようにする必要があります。

解決策:

  • 分散ロック(Redis、Zookeeperなど)を使用
  • データベースの悲観的ロックを使用
  • Quartzクラスタモードを使用

3. タスク実行時間の監視

@Component
public class MonitoredScheduledTask {
    
    @Scheduled(fixedRate = 5000)
    public void monitoredTask() {
        long startTime = System.currentTimeMillis();
        try {
            // 業務ロジック
            System.out.println("監視タスク実行: " + new Date());
        } finally {
            long executionTime = System.currentTimeMillis() - startTime;
            if (executionTime > 3000) {  // 実行時間が3秒を超えた場合
                System.err.println("タスク実行時間が長すぎます: " + executionTime + "ms");
            }
        }
    }
}

まとめ

@Scheduledアノテーションは強力で柔軟な定時タスク機能を提供し、適切な設定でほとんどの定時タスク要件を満たすことができます。使用する際には以下の点に注意してください:

  1. スケジューリング戦略の適切な選択(cron、fixedRate、fixedDelay)
  2. タスク実行時間とスレッドプール設定の考慮
  3. クラスタ環境でのタスク重複実行問題の処理
  4. 適切な例外処理と監視メカニズムの追加

@Scheduledアノテーションを適切に使用することで、安定した信頼性の高い定時タスクシステムを構築できます。

タグ: Spring Framework タスケジューリング @Scheduledアノテーション Cron式 Java

8月12日 10:30 投稿