[Spring Boot] @Scheduled를 이용한 스케쥴러 구현하기

김희정·2023년 11월 15일
6

Spring

목록 보기
11/18
post-custom-banner

💎 들어가며

이번 포스팅에서는 Spring Boot에서 @Scheduled 어노테이션을 이용하여 스케쥴러를 구현하는 방법에 대해 작성하고자 합니다.



1. 스케쥴러 구현하기

1.1 Application 설정

먼저, @Scheduled를 사용하기 위해서는 Application 클래스에서 @EnableScheduling을 설정해주어야합니다.

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

1.2 Scheduler 구현

이제 스케쥴러를 구현할 클래스를 생성합니다.

이때 유의할 점은 해당 스케쥴러가 스프링 빈에 등록되어야 합니다. 저는 @Component 애노테이션을 이용해서 빈에 등록했습니다.

아래 코드는 예시로, 60초마다 장비의 상태를 동기화하는 코드를 작성했습니다.

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
@Slf4j
public class SchedulerConfiguration {
    private final DeviceService deviceService;

    @Scheduled(fixedDelay = 60000)
    public void run() {
        deviceService.synchronize();
    }
}

2. @Scheduled 속성

@Scheduled 속성을 이용하여 스케쥴 옵션을 다양하게 설정할 수 있습니다. 속성에는 크게 fixedDelay, fixedRate, initDelay, cron 등이 있습니다.

  • fixedRate: 작업 수행시간과 상관없이 일정 주기마다 메소드를 호출하는 것
  • fixedDelay는 (작업 수행 시간을 포함하여) 작업을 마친 후부터 주기 타이머가 돌아 메소드를 호출
  • initialDelay: 스케줄러에서 메소드가 등록되자마자 수행하는 것이 아닌 초기 지연시간을 설정
  • cron: Cron 표현식을 사용하여 작업을 예약

fixedDelay

  • fixedDelay: milliseconds 단위로, 이전 Task의 종료 시점으로부터 정의된 시간만큼 지난 후 Task를 실행합니다.
@Scheduled(fixedDelay = 1000)
public void run() {
	log.info("Scheduler 실행");
}
  • fixedDelayString: fixedDelay와 같은데 문자열로 값을 표현하겠다는 의미입니다.
@Scheduled(fixedDelayString = "1000")
public void run() {
	log.info("Scheduler 실행");
}

fixedRate

  • fixedRate: milliseconds 단위로, 이전 Task의 시작 시점으로부터 정의된 시간만큼 지난 후 Task를 실행합니다.
@Scheduled(fixedRate = 1000)
public void run() {
	log.info("Scheduler 실행");
}
  • fixedRateString: fixedRate와 같은데 문자열로 값을 표현하겠다는 의미입니다.
@Scheduled(fixedRateString = "1000")
public void run() {
	log.info("Scheduler 실행");
}

initialDelay

  • initialDelay: 스케줄러에서 메소드가 등록되자마자 수행하는 것이 아닌 초기 지연시간을 설정하는 것입니다.
@Scheduled(fixedRate = 5000, initialDelay = 3000)
public void run() {
	log.info("Scheduler 실행");
}

위와 같이 사용하면 3초의 대기시간(initialDelay) 후에 5초(fixedRate)마다 로그를 출력하는 작업을 스케줄러가 수행해줍니다.

  • initialDelayString: 위와 마찬가지로 문자열로 값을 표현하겠다는 의미입니다.

cron

cron 표현식은 Scheduling의 정규 표현식을 의미합니다. 크론(cron) 설정은 리눅스의 크론탭(crontab) 기능과 유사하게 설정할 수 있습니다.

리눅스의 크론탭(crontab)

리눅스의 크론탭은 특정 시간에 프로그램을 실행시키기 위해 사용하며, 윈도우에서는 스케줄러와 비슷합니다.

cron의 구성은 아래와 같이 총 6개의 필드로 구성됩니다.
cron 구성

크론 필드

필드 명값의 허용 범위허용된 특수문자
초 (Seconds)0 ~ 59, - * /
분 (Minutes)0 ~ 59, - * /
시 (Hours)0 ~ 23, - * /
일 (Day)1 ~ 31, - * ? / L W
월 (Month)1 ~ 12 or JAN ~ DEC, - * /
요일(Week)0 ~ 6 or SUN ~ SAT, - * ? / L #

특수 문자

특수 문자설명예제
*모든 값 의미
?특정한 값이 없음을 의미
-범위를 나타낼 때 사용월요일부터 수요일까지 -> MON-WED
,특정 값을 여러 개 나열할 때 사용월,수,금 -> MON,WED,FRI
/시작 시간 / 단위0분부터 매 5분 -> 0/5
L일에서 사용하면 마지막 일,
요일에서 사용하면 마지막 요일(토요일)
W가장 가까운 평일15W -> 15일에서 가장 가까운 평일
#몇째 주의 무슨 요일을 표현3#2 -> 2번째주 수요일

사용 예시

  • 0 0/5 * * * ? : 매 5분마다 실행
  • 0 0 0/1 * * ? : 매 1시간마다 실행
  • 0 0 12 * * ? : 매일 낮 12시에
  • 0 15 10 ? * * : 매일 오전 10:15분에 실행
  • 0 15 10 * * ? : 매일 오전 10:15분에 실행
  • 0 * 14 * * ? : 매일 오후 2:00에 시작해서 매분마다 실행하고 2:59분에 종료
  • 0 0/5 14,18 * * ? : 매일 오후 2:00에 시작해서 5분마다 실행되어 2:55에 끝나고, 6:00에 시작하여 5분마다 실행되어 6:55에 종료
  • 0 0-5 14 * * ? : 매일 오후 2:00에 시작하여 매분마다 실행하고 오후 2:05분에 종료
  • 0 10,44 14 ? 3 WED : 3월 동안 오후 2:10과 2:44 실행
  • 0 15 10 ? * MON-FRI : 주중 오전 10:15분에
  • 0 15 10 15 * ? : 매달 15일 오전 10:15에
  • 0 15 10 L * ? : 매월 말일 오전 10:15에
  • 0 15 10 ? * 6L : 매월 마지막 금요일 오전 10:15에
  • 0 15 10 ? * 6#3 : 매월 3째주 금요일 오전 10:15에

3. 스케쥴러 설정 분리

애플리케이션을 개발하다 보면 하드 코딩(소스 내에서 관리) 보다는 유연한 설정(환경 설정 파일에서 관리)을 지원하는 것이 필요합니다. application.yml 파일에 아래와 같이 스케쥴러 설정 정보를 추가해줍니다.

schedule:
  cron: 0 0 0 * * *
  use: true

이제 아래와 같이 코드를 작성하면, 옵션에 따라 스케쥴러를 실행합니다.

@Component
@Slf4j
@RequiredArgsConstructor
public class CronTable {
    private final JobService jobService;
    
    @Value("${schedule.use}")
    private boolean useSchedule;
    
    @Scheduled(cron = "${schedule.cron}")
    public void mainJob() {
        try {
            if (useSchedule) {
                jobService.run();
            }
        } catch (InterruptedException e) {
            log.info("* Thread가 강제 종료되었습니다. Message: {}", e.getMessage());
        } catch (Exception e) {
            log.info("* Batch 시스템이 예기치 않게 종료되었습니다. Message: {}", e.getMessage());
        }
    }
}

💎 References

profile
Java, Spring 기반 풀스택 개발자의 개발 블로그입니다.
post-custom-banner

2개의 댓글

comment-user-thumbnail
2024년 7월 30일

안녕하세요. 좋은 글 감사합니다.

질문이 생겨서 댓글 남깁니다.

매일 실행 하는 거라면 ?를 안쓰고 *로만 3개를 채워도 될꺼라고 생각 하는데 ? 를 써줘야 하는 이유가 있나요?

0 0 12 * ? : 매일 낮 12시에
0 15 10 ?
: 매일 오전 10:15분에 실행
0 15 10
* ? : 매일 오전 10:15분에 실행

1개의 답글