J'ai créé une tâche planifiée simple à l'aide de @Scheduled
annotation.
@Scheduled(fixedRate = 2000)
public void doSomething() {}
Maintenant, je veux arrêter cette tâche quand elle n’est plus nécessaire.
Je sais qu'il pourrait exister une option pour vérifier un indicateur conditionnel au début de cette méthode, mais cela n'arrêtera pas l'exécution de cette méthode.
Y at-il quelque chose que le printemps fournit pour arrêter @Scheduled
tâche ?
Fournissez ScheduledAnnotationBeanPostProcessor
et appelez explicitement postProcessBeforeDestruction(Object bean, String beanName)
, pour le bean dont la planification doit être arrêtée.
private final Map<Object, ScheduledFuture<?>> scheduledTasks =
new IdentityHashMap<>();
@Scheduled(fixedRate = 2000)
public void fixedRateJob() {
System.out.println("Something to be done every 2 secs");
}
@Bean
public TaskScheduler poolScheduler() {
return new CustomTaskScheduler();
}
class CustomTaskScheduler extends ThreadPoolTaskScheduler {
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
ScheduledFuture<?> future = super.scheduleAtFixedRate(task, period);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task;
scheduledTasks.put(runnable.getTarget(), future);
return future;
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
ScheduledFuture<?> future = super.scheduleAtFixedRate(task, startTime, period);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task;
scheduledTasks.put(runnable.getTarget(), future);
return future;
}
}
Lorsque la planification d'un bean doit être arrêtée, vous pouvez rechercher la carte pour obtenir le Future
correspondant et l'annuler explicitement.
Il y a un peu d'ambiguïté dans cette question
Ma meilleure hypothèse est que vous souhaitez arrêter une tâche en utilisant une condition susceptible de survenir dans la même application, de manière récupérable. Je vais essayer de répondre sur la base de cette hypothèse.
C’est la solution la plus simple possible à laquelle je peux penser. Cependant, je vais apporter des améliorations telles que le retour anticipé plutôt que imbriqué si s
@Component
public class SomeScheduledJob implements Job {
private static final Logger LOGGER = LoggerFactory.getLogger(SomeScheduledJob.class);
@Value("${jobs.mediafiles.imagesPurgeJob.enable}")
private boolean imagesPurgeJobEnable;
@Override
@Scheduled(cron = "${jobs.mediafiles.imagesPurgeJob.schedule}")
public void execute() {
if(!imagesPurgeJobEnable){
return;
}
Do your conditional job here...
}
Propriétés pour le code ci-dessus
jobs.mediafiles.imagesPurgeJob.enable=true or false
jobs.mediafiles.imagesPurgeJob.schedule=0 0 0/12 * * ?
Il y a quelque temps, mon projet exigeait que tout composant puisse créer une nouvelle tâche planifiée ou arrêter le planificateur (toutes les tâches). Alors j'ai fait quelque chose comme ça
@Configuration
@EnableScheduling
@ComponentScan
@Component
public class CentralScheduler {
private static AnnotationConfigApplicationContext CONTEXT = null;
@Autowired
private ThreadPoolTaskScheduler scheduler;
public static CentralScheduler getInstance() {
if (!isValidBean()) {
CONTEXT = new AnnotationConfigApplicationContext(CentralScheduler.class);
}
return CONTEXT.getBean(CentralScheduler.class);
}
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
public void start(Runnable task, String scheduleExpression) throws Exception {
scheduler.schedule(task, new CronTrigger(scheduleExpression));
}
public void start(Runnable task, Long delay) throws Exception {
scheduler.scheduleWithFixedDelay(task, delay);
}
public void stopAll() {
scheduler.shutdown();
CONTEXT.close();
}
private static boolean isValidBean() {
if (CONTEXT == null || !CONTEXT.isActive()) {
return false;
}
try {
CONTEXT.getBean(CentralScheduler.class);
} catch (NoSuchBeanDefinitionException ex) {
return false;
}
return true;
}
}
Donc je peux faire des choses comme
Runnable task = new MyTask();
CentralScheduler.getInstance().start(task, 30_000L);
CentralScheduler.getInstance().stopAll();
N'oubliez pas que, pour certaines raisons, je l'ai fait sans avoir à me soucier de la concurrence. Sinon, il devrait y avoir une synchronisation.
Voici un exemple où nous pouvons arrêter, démarrer et répertorier également toutes les tâches en cours d'exécution planifiées:
@RestController
@RequestMapping("/test")
public class TestController {
private static final String SCHEDULED_TASKS = "scheduledTasks";
@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;
@Autowired
private ScheduledTasks scheduledTasks;
@Autowired
private ObjectMapper objectMapper;
@GetMapping(value = "/stopScheduler")
public String stopSchedule(){
postProcessor.postProcessBeforeDestruction(scheduledTasks, SCHEDULED_TASKS);
return "OK";
}
@GetMapping(value = "/startScheduler")
public String startSchedule(){
postProcessor.postProcessAfterInitialization(scheduledTasks, SCHEDULED_TASKS);
return "OK";
}
@GetMapping(value = "/listScheduler")
public String listSchedules() throws JsonProcessingException{
Set<ScheduledTask> setTasks = postProcessor.getScheduledTasks();
if(!setTasks.isEmpty()){
return objectMapper.writeValueAsString(setTasks);
}else{
return "No running tasks !";
}
}
}
Lorsque le processus printanier Scheduled
, il effectuera une itération de chaque méthode annotée de cette annotation et organisera les tâches par beans comme le montre la source suivante:
private final Map<Object, Set<ScheduledTask>> scheduledTasks =
new IdentityHashMap<Object, Set<ScheduledTask>>(16);
Si vous souhaitez simplement annuler une tâche planifiée répétée, vous pouvez simplement procéder comme suit (voici un démo exécutable dans mon référentiel):
@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;
@Autowired
private TestSchedule testSchedule;
public void later() {
postProcessor.postProcessBeforeDestruction(test, "testSchedule");
}
Il trouvera le ScheduledTask
de ce haricot et l'annulera un à un. Ce qu'il faut remarquer, c'est que la méthode en cours sera également arrêtée (comme le montre postProcessBeforeDestruction
source).
synchronized (this.scheduledTasks) {
tasks = this.scheduledTasks.remove(bean); // remove from future running
}
if (tasks != null) {
for (ScheduledTask task : tasks) {
task.cancel(); // cancel current running method
}
}