-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## 커스텀 스케줄러 구현 - 런타임중에 동적으로 스케줄 변경이 가능한 커스텀 스케줄러 구현 - 하나의 작업에 크론 여러개를 등록하고 싶을 시 "|" 를 구분자로 등록 가능 - DynamicScheduled(name = "", cron = "") 어노테이션으로 스케줄링 등록 - /admin/scheduler 페이지에서 관리 가능 --------- Co-authored-by: Photogrammer <[email protected]>
- Loading branch information
1 parent
d69c0a9
commit d74480e
Showing
11 changed files
with
316 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
server/src/main/java/com/talkka/server/admin/controller/SchedulerController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.talkka.server.admin.controller; | ||
|
||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.annotation.Lazy; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import com.talkka.server.admin.dto.SchedulerReqDto; | ||
import com.talkka.server.admin.exception.SchedulerNotFoundException; | ||
import com.talkka.server.admin.scheduler.DynamicSchedulingConfig; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/admin/api/scheduler") | ||
@Slf4j | ||
public class SchedulerController { | ||
@Lazy | ||
@Autowired | ||
private DynamicSchedulingConfig dynamicSchedulingConfig; | ||
|
||
// cron 유효성 검사 로직 필요 | ||
@PostMapping("") | ||
public ResponseEntity<?> updateScheduler(@RequestBody SchedulerReqDto dto) { | ||
try { | ||
dynamicSchedulingConfig.updateCronExpression(dto); | ||
} catch (SchedulerNotFoundException exception) { | ||
log.error(exception.getMessage()); | ||
return ResponseEntity.badRequest().body(exception.getMessage()); | ||
} | ||
return ResponseEntity.ok().build(); | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
server/src/main/java/com/talkka/server/admin/dto/SchedulerReqDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.talkka.server.admin.dto; | ||
|
||
public record SchedulerReqDto( | ||
String name, | ||
String cronString | ||
) { | ||
} |
12 changes: 12 additions & 0 deletions
12
server/src/main/java/com/talkka/server/admin/dto/SchedulerRespDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.talkka.server.admin.dto; | ||
|
||
import java.util.List; | ||
|
||
public record SchedulerRespDto( | ||
String name, | ||
String cronString | ||
) { | ||
public SchedulerRespDto(String name, List<String> cronList) { | ||
this(name, String.join(" | ", cronList)); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
server/src/main/java/com/talkka/server/admin/exception/SchedulerNotFoundException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.talkka.server.admin.exception; | ||
|
||
public class SchedulerNotFoundException extends RuntimeException { | ||
static final String message = "Scheduler not found"; | ||
|
||
public SchedulerNotFoundException() { | ||
super(message); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
server/src/main/java/com/talkka/server/admin/scheduler/DynamicScheduled.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.talkka.server.admin.scheduler; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.METHOD) | ||
public @interface DynamicScheduled { | ||
String cron() default "0 0/1 * * * *"; // 기본 cron 표현식 | ||
|
||
String name(); // 작업 이름 | ||
} |
98 changes: 98 additions & 0 deletions
98
server/src/main/java/com/talkka/server/admin/scheduler/DynamicSchedulingConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package com.talkka.server.admin.scheduler; | ||
|
||
import java.lang.reflect.Method; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.ScheduledFuture; | ||
|
||
import org.springframework.context.ApplicationContext; | ||
import org.springframework.scheduling.annotation.EnableScheduling; | ||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; | ||
import org.springframework.scheduling.config.ScheduledTaskRegistrar; | ||
import org.springframework.scheduling.support.CronTrigger; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.util.LinkedMultiValueMap; | ||
import org.springframework.util.MultiValueMap; | ||
import org.springframework.util.ReflectionUtils; | ||
|
||
import com.talkka.server.admin.dto.SchedulerReqDto; | ||
import com.talkka.server.admin.dto.SchedulerRespDto; | ||
import com.talkka.server.admin.exception.SchedulerNotFoundException; | ||
|
||
import jakarta.annotation.PostConstruct; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@EnableScheduling | ||
@Component | ||
@RequiredArgsConstructor | ||
// 나중에 Quartz Scheduler 로 리팩토링 | ||
public class DynamicSchedulingConfig { | ||
|
||
private ScheduledTaskRegistrar registrar; | ||
private final ApplicationContext applicationContext; | ||
private final Map<String, Runnable> taskMap = new HashMap<>(); | ||
private final MultiValueMap<String, ScheduledFuture<?>> scheduledTasks = new LinkedMultiValueMap<>(); | ||
private final MultiValueMap<String, String> cronMap = new LinkedMultiValueMap<>(); | ||
|
||
@PostConstruct | ||
public void init() { | ||
registrar = new ScheduledTaskRegistrar(); | ||
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); | ||
taskScheduler.setPoolSize(20); | ||
taskScheduler.initialize(); | ||
registrar.setTaskScheduler(taskScheduler); | ||
|
||
String[] beanNames = applicationContext.getBeanDefinitionNames(); | ||
for (String beanName : beanNames) { | ||
if (beanName.equals("dynamicSchedulingConfig")) { | ||
continue; | ||
} | ||
Object bean = applicationContext.getBean(beanName); | ||
Method[] methods = bean.getClass().getDeclaredMethods(); | ||
|
||
for (Method method : methods) { | ||
DynamicScheduled annotation = method.getAnnotation(DynamicScheduled.class); | ||
if (annotation != null) { | ||
String[] crons = annotation.cron().split("\\|"); | ||
String name = annotation.name(); | ||
Runnable task = () -> ReflectionUtils.invokeMethod(method, bean); | ||
taskMap.put(name, task); | ||
|
||
for (String cron : crons) { | ||
cron.trim(); | ||
ScheduledFuture<?> future = registrar.getScheduler() | ||
.schedule(task, triggerContext -> new CronTrigger(cron).nextExecution(triggerContext)); | ||
scheduledTasks.add(name, future); | ||
cronMap.add(name, cron); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
public List<SchedulerRespDto> getSchedulers() { | ||
List<SchedulerRespDto> schedulers = new ArrayList<>(); | ||
for (String name : taskMap.keySet()) { | ||
schedulers.add(new SchedulerRespDto(name, cronMap.get(name))); | ||
} | ||
return schedulers; | ||
} | ||
|
||
public void updateCronExpression(SchedulerReqDto dto) throws SchedulerNotFoundException { | ||
// 현재 해당 메소드의 스케줄링 작업 전부 멈추고 맵에서 삭제 | ||
scheduledTasks.get(dto.name()).forEach(future -> future.cancel(false)); | ||
scheduledTasks.remove(dto.name()); | ||
// 해당 메소드 가져옴 | ||
Runnable task = taskMap.get(dto.name()); | ||
// 크론식마다 스케줄 작업 추가 | ||
for (String cron : dto.cronString().split("\\|")) { | ||
cron.trim(); | ||
ScheduledFuture<?> future = registrar.getScheduler() | ||
.schedule(task, triggerContext -> new CronTrigger(cron).nextExecution(triggerContext)); | ||
scheduledTasks.add(dto.name(), future); | ||
cronMap.add(dto.name(), cron); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.