[Quartz] 메서드 실행 delay 및 스케쥴링 취소
업데이트:
제가 진행중인 프로젝트에서는 특정 메서드가 실행되었을 때, N초를 delay 시켜야 하는 요구사항이 있습니다.
그리고 특정 Job에 스케쥴링을 건 이후에 해당 Job의 스케쥴링을 제거할 수도 있어야 합니다.
이를 Quartz로 구현해 보도록 하겠습니다.
이 포스트에 사용된 모든 코드는 여기에서 확인하실 수 있습니다.
구성 요소
- Job 인터페이스 구현체 : 실제 실행할 로직을 구현합니다.
- JobDetail : Job 인터페이스 구현체의 인스턴스를 생성합니다.
- 같은 그룹에는 동일한 이름을 가진 Job을 생성할 수 없습니다.
- JobDataMap : 스케쥴러에서 Job이 실행될 때 사용할 값을 전달하는데에 사용합니다.
- key-value 형식으로 값을 전달하고, Job을 실행할 때 해당 값을 key를 통해 꺼내 쓸 수 있습니다.
- Trigger : Job을 어떤 방식과 주기로 실행시킬지 결정합니다.
- SimpleTrigger : 시작 시간, 종료 시간, 실행 간격, 반복 횟수 등 설정
- CronTrigger : Cron 형식으로 주기를 지정
- Job : Trigger = 1 : N
- Scheduler : 생성한 Job과 Trigger를 가지고 스케쥴링을 실행합니다.
- Listener : 작업의 시작, 중간, 끝, 에러를 처리합니다.
- ScheduleListener
- TriggerListener
Spring Boot 설정
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-quartz'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
저는 위와 같이 의존성들이 설정된 Spring Boot 애플리케이션을 생성했습니다.
기본 구성요소 생성
Quartz 테스트를 위한 요소들을 생성해 보겠습니다.
adapter 생성
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import taeheekim.quartzdelay.port.inbound.MyJobAccessor;
@Slf4j
@RestController
@RequiredArgsConstructor
public class MyJobController {
private final MyJobAccessor myJobAccessor;
@GetMapping("/start-myjob")
public String startMyJob() {
log.info("MyJobStartController - startMyJob");
myJobAccessor.startMyJob();
return "MyJobStarted!!";
}
@GetMapping("/stop-myjob")
public String stopMyJob() {
log.info("MyJobStartController - stopMyJob");
myJobAccessor.stopMyJob();
return "MyJobStopped!!";
}
}
inbound port 생성
public interface MyJobAccessor {
void startMyJob();
void stopMyJob();
}
Quartz 실행 요소들 생성
Job 인터페이스 구현체 생성
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.time.LocalDateTime;
/**
* Quartz의 Job 인터페이스를 구현한 클래스입니다.
*/
@Slf4j
public class MyJob implements Job {
/**
* Job이 실행되면 execute 메서드가 호출됩니다. 스케쥴러의 쓰레드 중 하나에 의해 호출됩니다.
*
* @param context JobExecutionContext 객체입니다.
* 이 Job을 실행하는 런타임 환경에 대한 정보를 담고 있습니다.
* Scheduler, Trigger, JobDetail 등을 포함하여 Job 인스턴스에 대한 정보를 제공하는 객체입니다.
* 여기에서는 JobDataMap에서 key 값을 통해 value값을 가져와 로그로 출력했습니다.
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.info("MyJob Start time={}", LocalDateTime.now());
String testValue = context.getJobDetail().getJobDataMap().get("ABCDE").toString();
log.info("testValue={}", testValue);
log.info("MyJob End time={}", LocalDateTime.now());
}
}
스케쥴러 설정
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.stereotype.Service;
import taeheekim.quartzdelay.port.inbound.MyJobAccessor;
import java.time.LocalDateTime;
import static org.quartz.TriggerBuilder.newTrigger;
@Slf4j
@Service
@RequiredArgsConstructor
public class MyJobProcessor implements MyJobAccessor {
private final Scheduler scheduler; // Quartz의 스케쥴러를 주입받습니다.
@Override
public void startMyJob() {
// JobDataMap을 통해 실행되는 Job에게 key-value 형식으로 데이터를 전달합니다.
// JobDataMap은 JobExecutionContext를 통해 전달됩니다.
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("ABCDE", "12345");
// 실행시킬 Job을 만들고, 위에서 생성한 JobDataMap을 전달합니다.
JobDetail jobDetail = JobBuilder.newJob(MyJob.class) // 앞서 생성한 MyJob 클래스를 지정합니다.
.usingJobData(jobDataMap) // JobDataMap를 전달합니다.
.build();
// Trigger를 생성합니다.
Trigger trigger = newTrigger()
.withIdentity(TriggerKey.triggerKey("myTestJob", "myGroup")) // TriggerKey를 지정합니다.
.startAt(DateBuilder.futureDate(5, DateBuilder.IntervalUnit.SECOND)) // 5초 후에 실행되도록 설정합니다.
.build();
try {
scheduler.scheduleJob(jobDetail, trigger); // JobDetail과 Trigger를 스케쥴러에 등록합니다.
scheduler.start(); // 스케쥴러를 시작합니다.
} catch (SchedulerException e) {
log.error("스케쥴러 실행 실패", e);
throw new RuntimeException(e);
}
}
@Override
public void stopMyJob() {
try {
scheduler.unscheduleJob(TriggerKey.triggerKey("myTestJob", "myGroup")); // 스케쥴러에서 TriggerKey로 Job을 스케쥴에서 제거합니다.
log.info("MyJob Stopped time={}", LocalDateTime.now());
} catch (SchedulerException e) {
log.error("스케쥴러 중지 실패", e);
throw new RuntimeException(e);
}
}
}
테스트
Spring Boot 애플리케이션을 시작합니다.
MyJob 실행
Controller에 등록해놓은 엔드포인트 http://localhost:8080/start-myjob
를 호출해 봅니다.
위와 같이 5초 뒤에 MyJob이 실행된 것을 확인할 수 있습니다.
JobData에서 key를 통해 꺼낸 value도 잘 출력된 것을 확인할 수 있습니다.
MyJob 실행 후 중지
MyJob을 실행한 후 5초 내에 http://localhost:8080/stop-myjob
를 호출시켜 중지가 잘 되는지 확인해보겠습니다.
위처럼 MyJob의 실행이 취소되어 해당 클래스의 로그는 찍히지 않는것을 확인할 수 있습니다.
TriggerKey의 name, group명이 일치해야 해당 Job을 찾아서 실행을 취소시킬 수 있습니다.
둘 중 하나라도 다르면 실행이 취소되지 않습니다.
마치며
프로젝트 요구사항에 맞추어 특정 메서드가 실행되었을 때 N초 뒤에 실행되도록 하고, 스케쥴링을 건 이후에 특정 Job의 스케쥴링을 제거하는 기능을 Quartz를 이용해 구현해 보았습니다.
감사합니다.
참고
- [JAVA] Quartz job Scheduler 기본 사용법 정리
- [Java] Java Quartz Scheduler 사용해보기(일정 주기로 실행하는 자바 스케쥴러)
- [JAVA] Quartz 스케줄러 만들기 (1) - 실행
- Quartz Job Scheduler란?
댓글남기기