Spring

[Spring] Spring Quartz 도입하기

HD9504 2025. 3. 14. 01:03

.백엔드 개발을 하다보면 주기적으로 또는 특정 시간대에 동작해야하는 기능을 만들어야할 때가 있습니다. 이러한 경우 Spring에서는 기본적으로 Spring Schduler를 내장하고 있습니다. Spring Schduler 를 사용하면 원하는 기능을 간단하게 만들 수 있지만 서버가 분산환경이거나, 복잡한 로직일 경우 적합하지 않습니다. 오늘은 이러한 경우에 사용할 수 있는 Spring quartz에 대해 알아보겠습니다.

쿼츠 구성요소

[전체 동작 흐름]

1. SchedulerFactory가 Scheduler를 생성하고, SchdulerRepository에 등록

2. QuartzScheduler가 시작되면, ThreadExecutor를 통해 QuartzSchdulerThread를 생성

3. 생성된 QuartzSchdulerThread 는 주기적으로 JobStore를 조회하여, 다음에 실행해야할 Trigger를 확인

4. 실행된 시점에 Trigger가 있으면, 해당 Trigger와 연결된 JobDetail을 가져옴

5. ThreadPool에 작업을 배정하고, WorkerThread가 실제 job의 execute 메서드를 수행 

6. 실행 전후에 JobListener와, TriggerListener가 등록되어 있다면 해당 콜백로직이 수행

[Job]

Job인터페이스를 구현한 실제 실행할 로직을 포함

public class ExampleJob implements Job {
	
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
		... 
    }
}

[JobDetail]

실제로 실행할 Job에 대한 메타데이터를 담는 객체

@Bean
public JobDetail jobDetail() {
    return JobBuilder.newJob(ExampleJob.class)
            .withIdentity("exampleJob") //고유 식별자
            .storeDurably() //trigger와 연결되지 않더라도 jobstore에 유지, 동적으로 추가 제거할 수 있는 유연성  제공
            .usingJobData("key", "value") //정보를 담아서 실행할 수 있음.
            .build();
}

[Trigger]

Job을 실행시키는 조건을 정의하는 인터페이스

@Bean
public Trigger exampleJobTrigger(JobDetail exampleJobDetail) {
    return TriggerBuilder.newTrigger()
            .forJob(exampleJobDetail)
            .withIdentity("exampleJobTrigger")
            .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))// every 10 seconds
            .build();
}

[Scheduler]

주기적으로 잡스토어를 확인해서, trigger가 존재하는 경우 job을 수행하는 인터페이스      

주의사항

Quartz의 Job 인스턴스는 쿼츠가 직접 생성하기 때문에, 스프링 컨테이너의 관리 대상이 아니여서, 컴포넌트를 붙여도 의존성을 주입받을 수 없다.

@Component //붙여도 ExampleService를 주입받을 수 없음
public class ExampleJob implements Job {

    @Autowired
    ExampleService exampleService;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

    }

}

 

이에 대한 해결책으로 두 가지 방법이 있다. 

1. AutowiringSpringBeanJobFactory 구현

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}


@Configuration
public class QuartzAutowiringConfig {

    @Bean
    public SchedulerFactoryBeanCustomizer schedulerFactoryBeanCustomizer(AutowiringSpringBeanJobFactory autowiringSpringBeanJobFactory) {
        return schedulerFactoryBean -> schedulerFactoryBean.setJobFactory(autowiringSpringBeanJobFactory);
    }

    @Bean
    public AutowiringSpringBeanJobFactory autowiringSpringBeanJobFactory() {
        return new AutowiringSpringBeanJobFactory();
    }

}

 

2. Job 대신 QuartzJobBean을 상속받아 Job 구현하기

public class ExampleJob extends QuartzJobBean {

    @Autowired
    ExampleService exampleService;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        exampleService.exampleMethod();
    }
}


      

참고


https://docs.spring.io/spring-boot/reference/io/quartz.html

https://advenoh.tistory.com/51