@SpringBootApplication(scanBasePackages = {"org.Scheduler.Service"}) public class SchedulerApp { public static void main(String[] args) { SpringApplication.run(SchedulerApp.class, args); } @Bean public JobDetail jobDetails() { JobDetail setJobDetail = JobBuilder.newJob(KmaFcJob.class) .withIdentity("kmaFcJob", "KmaJobgroup") .storeDurably() .build(); return setJobDetail; } @Bean public Trigger jobTrigger() { Trigger setTrigger = TriggerBuilder.newTrigger() .forJob(jobDetails()) .withIdentity("kmaFcTrigger", "KmaTriggergroup") .withSchedule(CronScheduleBuilder.cronSchedule("0 */1 * * * ?")) // 매 1분마다 실행 .build(); return setTrigger; } @Bean public SchedulerFactoryBean schedulerFactoryBean(ApplicationContext applicationContext) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setJobDetails(jobDetails()); factory.setTriggers(jobTrigger()); factory.setJobFactory(jobFactory(applicationContext)); // ApplicationContext를 인자로 전달 return factory; } @Bean public JobFactory jobFactory(ApplicationContext applicationContext) { AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(applicationContext); // ApplicationContext를 인자로 전달 return jobFactory; } @Bean public Scheduler scheduler(SchedulerFactoryBean factory) throws Exception { return factory.getScheduler(); } }
@Slf4j @Component public class KmaFcJob implements Job { @Autowired private KmaMapper kmaMapper; @Override public void execute(JobExecutionContext context) throws JobExecutionException { String fcUrl; String baseDate = KITUtil.currentTimeFormat("yyyyMMdd"); String baseTime = KITUtil.currentTimeFormat("HHmm"); int maxRetries = 3; // 최대 재시도 횟수 int attempt = 0; // 현재 시도 횟수 String[][] AREA_XY = {{"64","119"},{"62","120"},{"62","121"},{"98","125"}}; while (attempt < maxRetries) { try { String urlParams = "?serviceKey=비밀이야" + "&pageNo=" + "1" + "&numOfRows=" + "1000" + "&dataType=" + "JSON" + "&base_date=" + baseDate + "&base_time=" + baseTime; fcUrl = "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst" + urlParams; for (int i = 0; i < AREA_XY.length; i++) { String nx = AREA_XY[i][0]; String ny = AREA_XY[i][1]; Map<String, Object> rs = KITUtil.callApi("FC", fcUrl + "&nx=" + nx + "&ny=" + ny, null); if (KITUtil.isNull(rs.get("code"), "").equals("SUCCESS")) { // log.info(KITUtil.isNull(rs.get("apiNm"), ""), KITUtil.isNull(rs.get("response"), "")); log.info((String) rs.get("response")); } } // 성공적으로 처리되면 루프 종료 break; } catch (JsonProcessingException e) { log.error("JSON 처리 중 오류 발생: {}", e.getMessage()); } catch (IOException e) { log.error("HTTP 연결 중 오류 발생: {}", e.getMessage()); } catch (Exception e) { log.error("알 수 없는 오류 발생: {}", e.getMessage()); } attempt++; // 시도 횟수 증가 log.info("재시도 중... 시도 횟수: {}", attempt); // 대기 시간 (예: 2초) try { Thread.sleep(2000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); // 현재 스레드의 인터럽트 상태를 복원 } } log.info("GET_FC_FINISH => TIME :::" + LocalDateTime.now()); } }
Quartz에서는 JobStore라 칭하며 스케줄링 수행과 관련된 모든 데이터를 메모리 상에 저장하거나 JDBC를 이용하여 데이터베이스에 저장할 수 있다.
위와 같이 등록된 Trigger 및 Job 정보를 갖고 있는 DB를 JFrame을 활용하여 모니터링 시스템을 구현한다.
Job인터페이스에 코드 작성 시 DB 연결(Mybatis)이 안 되는 이슈사항이 있었음.
Caused by: java.lang.NullPointerException: Cannot invoke "org.Scheduler.Service.dao.KmaMapper.findAll()" because "this.kmaMapper" is null
원인
Dependency Injection: kmaMapper가 Spring의 의존성 주입을 통해 주입되어야 하는 경우, FirstJob 클래스가 Spring의 관리 하에 있어야 한다. Quartz Job은 기본적으로 Spring의 Bean으로 관리되지 않기 때문에, Spring의 @Component 또는 @Service 어노테이션을 사용하여 FirstJob을 Spring Bean으로 등록해야한다.
→ 처음에 이거는 실패함. 이유는 Quartz Scheduler가 Job을 생성할 때 Spring의 ApplicationContext를 사용하지 않으면, Spring의 의존성 주입이 작동하지 않습니다.
해결방법
스케쥴러가 Job인스턴스를 생성할 때 spring의 bean에 등록될 수 있게 설정을 잡아줌
public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private transient AutowireCapableBeanFactory beanFactory; public AutowiringSpringBeanJobFactory(ApplicationContext applicationContext) { setApplicationContext(applicationContext); } @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; } }
@Bean public SchedulerFactoryBean schedulerFactoryBean(ApplicationContext applicationContext) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setJobDetails(jobDetails()); factory.setTriggers(jobTrigger()); factory.setJobFactory(jobFactory(applicationContext)); // ApplicationContext를 인자로 전달 return factory; } @Bean public JobFactory jobFactory(ApplicationContext applicationContext) { AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(applicationContext); // ApplicationContext를 인자로 전달 return jobFactory; }