쿼츠(Quartz)-구현 예제

개미는뚠뚠·2025년 1월 12일
0

Quartz

목록 보기
2/2
post-thumbnail

🍞설정 및 코드 작성

  • Main(Scheduler, JobDeatil, Trigger)
@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();
    }
}
  • kmaFcJob(JOB)
@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;
    }

0개의 댓글