- [트러블 슈팅] 재배포 없이 스케줄링 날짜 변경하기2026년 01월 31일 17시 10분 37초에 업로드 된 글입니다.작성자: do_hyuk728x90반응형
기존 방식
app: cron: saveDailyA: "0 0 1 * * *" saveDailyB: "0 1 1 * * *" saveMonthlyA: "0 0 1 1 * *" saveMonthlyB: "0 1 1 1 * *" createExcel: "0 2 1 1 * *"@Scheduled(cron = "${app.cron.saveDailyA}") public void saveDailyA() { LocalDate today = LocalDate.now(); service.saveHistory(today); }@Scheduled(cron = “..") 이 방식은 서버 실행시 한번만 스캔되기 때문에 cron값을 동적으로 변경할 수 없고, 설정 파일 변경 후 재배포를 진행해야 적용된다.
1. SchedulingConfigurer + 외부 설정 방식 사용
더보기
설정 파일 위치 cron: cms: saveDailyA: "0 0 1 * * *" saveMonthlyA: "0 0 1 1 * *" stream-detect: saveDailyB: "0 1 1 * * *" saveMonthlyB: "0 1 1 1 * *" excel: createExcel: "0 2 1 1 * *"spring: profiles: active: prod backend: config: import: "file:./config/${spring.profiles.active}.yml"@Slf4j @RequiredArgsConstructor @Configuration public class Properties { private final Environment environment; private final Map<String, Object> properties = new LinkedHashMap<>(); private final String CONFIG_FILE = "backend.config.import"; private String ymlPath = ""; private long lastModified = 0; @PostConstruct public void init() throws Exception { ymlPath = environment.getProperty(CONFIG_FILE); reloadYaml(System.currentTimeMillis()); } ... private void reloadYaml(long currentModified) throws Exception { Resource resource = new PathMatchingResourcePatternResolver().getResource(ymlPath); ... CommonResource.DailyAScheduleTime = get("cron.stream-detect.saveDailyA", String.class, null); CommonResource.MonthlyAScheduleTime= get("cron.stream-detect.saveMonthlyA", String.class, null); CommonResource.DailyBScheduleTime = get("cron.cms.saveDailyB", String.class, null); CommonResource.MonthlyBScheduleTime= get("cron.cms.saveMonthlyB", String.class, null); CommonResource.generateExcelFileScheduleTime = get("cron.excel.createExcel", String.class, null); }@Slf4j @RequiredArgsConstructor @Component public class CronTriggerFactory { public Trigger fromCron(String cron) { return triggerContext -> { if (cron == null || cron.isBlank()) { log.warn("cron property is empty {}", cron); return null; } try { return new CronTrigger(cron) .nextExecution(triggerContext); } catch (IllegalArgumentException e) { log.error("Invalid cron expression [{}]", cron, e); return null; } }; } }@RequiredArgsConstructor @Configuration public class ExcelSchedulerConfig implements SchedulingConfigurer { private final CronTriggerFactory cronFactory; private final ExcelExtractorJob job; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addTriggerTask( job::execute, cronFactory.fromCron(CommonResource.generateExcelFileScheduleTime) ); } }public interface ScheduleJob { void execute(); }@Slf4j @RequiredArgsConstructor @Service public class ExcelExtractorJob implements ScheduleJob { private final ExcelService service; @Override public void execute() { LocalDate target = LocalDate.now(); log.info("=== ExcelExtractorJob start. target={} ===", target); service.generateExcel(target); log.info("service.generateExcel(target)"); log.info("=== ExcelExtractorJob end. target={} ===", target); } }@Slf4j @EnableScheduling @SpringBootApplication public class MiniApplication { public static void main(String[] args) { SpringApplication.run(MiniApplication.class, args); } }외부 설정 파일 수정이 감지되었을 경우 cron 값을 수정하고 스케줄러에 예약은 하지만 기존에 예약된 내용이 사라지진 않기 때문에 다음 예약부터 적용이 됨
해당 방식으로 진행하려면 기존 예약을 없애기 위해 서버를 재시작해서 cron을 재등록 시켜야함
2. TaskScheduler + ScheduledFuture 직접 관리
더보기cron: enabled: true cms: enabled: true saveDailyA: "0 0 1 * * *" saveMonthlyA: "0 0 1 1 * *" stream-detect: enabled: true saveDailyB: "0 1 1 * * *" saveMonthlyB: "0 1 1 1 * *" excel: enabled: true createExcel: "0 2 1 1 * *"@Slf4j @RequiredArgsConstructor @Component public class ScheduleFactory { private final ThreadPoolTaskScheduler taskScheduler; private final Map<CronKey, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>(); public void register(CronKey key, String cron, Runnable task) { if (cron == null || cron.isBlank()) { log.info("{} scheduler disabled", key); return; } ScheduledFuture<?> future = taskScheduler.schedule(task, new CronTrigger(cron)); scheduledTasks.put(key, future); log.info("{} scheduler registered. cron={}", key, cron); } protected void cancel(CronKey key) { ScheduledFuture<?> old = scheduledTasks.remove(key); if (old != null) old.cancel(false); } }@Slf4j @RequiredArgsConstructor @Configuration public class ExcelSchedulerConfig { private final ExcelExtractorJob job; private final ScheduleFactory scheduleFactory; public synchronized void resetSchedules() { // 기존 스케줄 전부 취소 scheduleFactory.cancel(CronKey.EXCEL_KEY); // cron별 등록 scheduleFactory.register( CronKey.EXCEL_KEY, CommonResource.generateExcelFileScheduleTime, job::execute ); } }@Slf4j @RequiredArgsConstructor @Configuration public class Properties { private final Environment environment; private final Map<String, Object> properties = new LinkedHashMap<>(); private final String CONFIG_FILE = "backend.config.import"; private String ymlPath = ""; private long lastModified = 0; @PostConstruct public void init() throws Exception { ymlPath = environment.getProperty(CONFIG_FILE); reloadYaml(System.currentTimeMillis()); } ... private void reloadYaml(long currentModified) throws Exception { Resource resource = new PathMatchingResourcePatternResolver().getResource(ymlPath); ... boolean allEnable = get("cron.enabled", Boolean.class, null); boolean cmsEnable = get("cron.cms.enabled", Boolean.class, null); boolean streamDetectEnable = get("cron.stream-detect.enabled", Boolean.class, null); boolean excelEnable = get("cron.excel.enabled", Boolean.class, null); String newExcelCron = get("cron.excel.createExcel", String.class, null); String newStreamDetectDailyCron = get("cron.stream-detect.saveDailyB", String.class, null); String newStreamDetectMonthlyCron = get("cron.stream-detect.saveMonthlyB", String.class, null); String newCmsDailyCron = get("cron.cms.saveDailyA", String.class, null); String newCmsMonthlyCron = get("cron.cms.saveMonthlyA", String.class, null); if (!allEnable) { CommonResource.reset(); excelSchedulerConfig.resetSchedules(); wowzaSchedulerConfig.resetDailySchedules(); wowzaSchedulerConfig.resetMonthlySchedules(); goldEyesSchedulerConfig.resetDailySchedules(); goldEyesSchedulerConfig.resetMonthlySchedules(); return; } if (excelEnable) { if (isChanged(CommonResource.generateExcelFileScheduleTime, newExcelCron)) { CommonResource.generateExcelFileScheduleTime = newExcelCron; excelSchedulerConfig.resetSchedules(); } } else { CommonResource.generateExcelFileScheduleTime = null; excelSchedulerConfig.resetSchedules(); } ... } private boolean isChanged(String currentValue, String newValue) { return !Objects.equals(currentValue, newValue); }재시작 없이 설정 파일 변경을 감지하게 되면 기존 스케줄 제거 후 새로운 cron 으로 재등록
728x90반응형'Spring' 카테고리의 다른 글
Spring Cache 사용기록 (0) 2026.02.22 [트러블 슈팅] JPA UPDATE 벌크 연산 후 조회 안됨 (0) 2026.02.01 [트러블 슈팅] JWT 유효기간보다 빨리 만료되는 문제 해결 (0) 2026.01.11 Chained Transaction Manager deprecated (0) 2026.01.04 JPA Fetch Join과 페이징을 함께 사용할 때 주의점 (0) 2025.04.28 댓글