지난 포스팅에 이어 Quartz를 활용해봅시다.
Quartz는 스케쥴러, 즉 일정 시간마다 지정한 횟수만큼 반복해서 Job을 실행할 수 있는 라이브러리를 말합니다.
단순히 몇 번 반복하는 작업이라면 for문등의 반복문을 활용할 수도 있겠지만,
작업 실패 시 재시작 처리라던가, 재 시작 후에 작업 복귀 등의 복잡한 기능이 필요할 때
이를 단순히 구현하는 것보다 수준 높은 기능을 제공합니다.
또 설정된 스레드 풀 내에서 새로운 스레드를 생성해 작업을 진행하기 때문에 메인 스레드를 방해하지 않고
비동기적으로 작동할 수 있습니다.
그냥 줄글로 작성하기보다는, 코드를 보면서 설명하는 게 좋을 듯 싶어 코드를 첨부해가며 진행해보겠습니다.
Quartz도 Batch처럼 데이터베이스에 실행중인 작업이나 트리거에 대한 정보를 저장할 필요가 있습니다.
실제 제가 사용한 PostgreSQL에 해당하는 quartz의 메타 테이블 정보입니다.
아래 내용은 간단하게 쭉 읽어보시고, 코드로 설명 다시 드려볼게요.
@Component
public class QuartzBatchJob implements org.quartz.Job {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private BeanUtil beanUtil;
@SneakyThrows
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
Job job = (Job) beanUtil.getBean((String) jobDataMap.get(QuartzService.JOB_NAME));
JobParameters jobParameters = new JobParametersBuilder()
.addDate("curDate", new Date())
.toJobParameters();
//JobDataMap을 이용해 Job 실행횟수 +1 증가
int cnt = (int)jobDataMap.get("executeCount");
jobDataMap.put("executeCount", ++cnt);
jobLauncher.run(job,jobParameters);
}
}
해당 코드는 Quartz의 Job 인터페이스를 구현해서 Quartz에서 수행할 '작업'을 작성했습니다.이 execute 코드 안에서 실제로 수행할 내용의 코드가 들어가게 됩니다.하지만 개발자가 Job 인터페이스를 구현해서 정의한 작업은 그저 '동작'만 구현한 것입니다.실제로 이를 인스턴스화해서 작업으로 실행시키기 위해서는(Job 구현체의 자바 클래스), (JobDataMap), (식별자), (실행 설정) 등의 정보가 필요합니다.그리고 이러한 정보들을 포함시키기 위해 JobBuilder를 이용해서 JobDetail 인터페이스의 구현체를 생성해야 합니다.
@Configuration
@RequiredArgsConstructor
public class QuartzService {
private final Scheduler scheduler;
public static final String JOB_NAME = "JOB_NAME";
@PostConstruct
public void init() throws Exception{
scheduler.clear();
scheduler.getListenerManager().addJobListener(new QuartzJobListener());
scheduler.getListenerManager().addTriggerListener(new QuartzTriggerListener());
addJob(QuartzBatchJob.class, "apiJob", "api를 가져오는 Job입니다.", null, "0 0 3 * * ?"); // 매일 새벽 3시 실행 가정.
}
// Job 추가
public <T extends Job> void addJob(Class<? extends Job> job, String name, String desc, Map paramMap, String cron) throws SchedulerException {
JobDetail jobDetail = buildJobDetail(job,name,desc,paramMap);
Trigger trigger = BuildCronTrigger(cron);
if(scheduler.checkExists(jobDetail.getKey())){
scheduler.deleteJob(jobDetail.getKey());
}
scheduler.scheduleJob(jobDetail,trigger);
}
// JobDetail 생성
public <T extends Job> JobDetail buildJobDetail(Class<? extends Job> job, String name, String desc, Map paramMap){
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put(JOB_NAME, name);
jobDataMap.put("executeCount", 1);
return JobBuilder
.newJob(job)
.withIdentity(name)
.withDescription(desc)
.usingJobData(jobDataMap)
.build();
}
//Trigger 생성
private Trigger BuildCronTrigger(String cronExp){
return TriggerBuilder.newTrigger()
.withSchedule(CronScheduleBuilder.cronSchedule(cronExp))
.build();
}
}
@PostConstruct를 통해 사전에 먼저 동작들을 실행하고 있습니다.먼저 scheduler에 직접 만든 QuartzJobListener와 QuartzTriggerListener를 넣어주고 있습니다.그렇다면 이 Listener들은 무엇이냐?
기능 목적은 말 그대로 logging 목적이다. JobListener와 TriggerListener를 implements해서 사용한다.addJob함수를 주목해보자.아까 말했던 (Job 구현체의 자바 클래스), (JobDataMap), (식별자), (실행 설정) 이 4가지의 정보들을 인자로 넣어줬다.addJob함수에서 해당 내용들의 JobDetail을 만들었고 실행 설정(cron)정보를 통해 Trigger도 만들었다.그러고나서 Scheduler에 JobDetail과 Trigger를 넣어줬다.
그리고 여기서 드는 의문.지금 Scheduler 변수를 사용해서 스케쥴링을 하는 것은 이해가 되는데얘가 어떻게 생성되었지?스케쥴러에 대한 정보들은 어디에 있지? 그러한 설정이 전혀 없다.그렇기에 스케쥴러에게 필요한 정보들을 설정해줘야한다.대표적으로 dataSource라던가..
@RequiredArgsConstructor
@Configuration
public class QuartzConfig {
private final DataSource dataSource;
private final PlatformTransactionManager platformTransactionManager;
private final ApplicationContext applicationContext;
@Bean
public SchedulerFactoryBean schedulerFactoryBean(){
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
AutoWiringSpringBeanJobFactory autoWiringSpringBeanJobFactory = new AutoWiringSpringBeanJobFactory();
autoWiringSpringBeanJobFactory.setApplicationContext(applicationContext);
schedulerFactoryBean.setJobFactory(autoWiringSpringBeanJobFactory);
schedulerFactoryBean.setDataSource(dataSource);
schedulerFactoryBean.setOverwriteExistingJobs(true);
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setTransactionManager(platformTransactionManager);
schedulerFactoryBean.setQuartzProperties(quartzProperties());
return schedulerFactoryBean;
}
private Properties quartzProperties(){
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
Properties properties = null;
try{
propertiesFactoryBean.afterPropertiesSet();
properties = propertiesFactoryBean.getObject();
}catch (IOException e){
log.error("quartzProperties parse error : {}", e);
}
return properties;
}
}
스프링에선 SchedulerFactoryBean을 이용해서 간편하게 Bean 설정을 해줄 수 있다.
또 현재 이 Bean은 ApplicationContext가 없으므로 해당 내용도 넣어주기 위해 ApplicationContext를 가져오는 함수도 만들었다.
// SchedulerFactoryBean 에 스프링의 빈정보 ApplicationContext를 주입하기 위한 목적으로 생성
public class AutoWiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
'스프링 정리' 카테고리의 다른 글
테스트코드에서의 @Transactional (0) | 2023.10.30 |
---|---|
Spring Batch - @PersistJobDataAfterExecution (0) | 2023.04.04 |
Spring Security & JWT(Json Web Token) 활용 예제 (0) | 2023.03.28 |
Spring Batch 간단하게 활용해보기(스프링 배치 5.0) (2) | 2023.03.24 |
Spring Security - authentication /authorization (0) | 2023.03.05 |