001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.scheduling.config; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.LinkedHashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027import java.util.concurrent.Executors; 028import java.util.concurrent.ScheduledExecutorService; 029 030import org.springframework.beans.factory.DisposableBean; 031import org.springframework.beans.factory.InitializingBean; 032import org.springframework.lang.Nullable; 033import org.springframework.scheduling.TaskScheduler; 034import org.springframework.scheduling.Trigger; 035import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; 036import org.springframework.scheduling.support.CronTrigger; 037import org.springframework.util.Assert; 038import org.springframework.util.CollectionUtils; 039 040/** 041 * Helper bean for registering tasks with a {@link TaskScheduler}, typically using cron 042 * expressions. 043 * 044 * <p>As of Spring 3.1, {@code ScheduledTaskRegistrar} has a more prominent user-facing 045 * role when used in conjunction with the {@link 046 * org.springframework.scheduling.annotation.EnableAsync @EnableAsync} annotation and its 047 * {@link org.springframework.scheduling.annotation.SchedulingConfigurer 048 * SchedulingConfigurer} callback interface. 049 * 050 * @author Juergen Hoeller 051 * @author Chris Beams 052 * @author Tobias Montagna-Hay 053 * @author Sam Brannen 054 * @since 3.0 055 * @see org.springframework.scheduling.annotation.EnableAsync 056 * @see org.springframework.scheduling.annotation.SchedulingConfigurer 057 */ 058public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean { 059 060 /** 061 * A special cron expression value that indicates a disabled trigger: {@value}. 062 * <p>This is primarily meant for use with {@link #addCronTask(Runnable, String)} 063 * when the value for the supplied {@code expression} is retrieved from an 064 * external source — for example, from a property in the 065 * {@link org.springframework.core.env.Environment Environment}. 066 * @since 5.2 067 * @see org.springframework.scheduling.annotation.Scheduled#CRON_DISABLED 068 */ 069 public static final String CRON_DISABLED = "-"; 070 071 072 @Nullable 073 private TaskScheduler taskScheduler; 074 075 @Nullable 076 private ScheduledExecutorService localExecutor; 077 078 @Nullable 079 private List<TriggerTask> triggerTasks; 080 081 @Nullable 082 private List<CronTask> cronTasks; 083 084 @Nullable 085 private List<IntervalTask> fixedRateTasks; 086 087 @Nullable 088 private List<IntervalTask> fixedDelayTasks; 089 090 private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<>(16); 091 092 private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<>(16); 093 094 095 /** 096 * Set the {@link TaskScheduler} to register scheduled tasks with. 097 */ 098 public void setTaskScheduler(TaskScheduler taskScheduler) { 099 Assert.notNull(taskScheduler, "TaskScheduler must not be null"); 100 this.taskScheduler = taskScheduler; 101 } 102 103 /** 104 * Set the {@link TaskScheduler} to register scheduled tasks with, or a 105 * {@link java.util.concurrent.ScheduledExecutorService} to be wrapped as a 106 * {@code TaskScheduler}. 107 */ 108 public void setScheduler(@Nullable Object scheduler) { 109 if (scheduler == null) { 110 this.taskScheduler = null; 111 } 112 else if (scheduler instanceof TaskScheduler) { 113 this.taskScheduler = (TaskScheduler) scheduler; 114 } 115 else if (scheduler instanceof ScheduledExecutorService) { 116 this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler)); 117 } 118 else { 119 throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass()); 120 } 121 } 122 123 /** 124 * Return the {@link TaskScheduler} instance for this registrar (may be {@code null}). 125 */ 126 @Nullable 127 public TaskScheduler getScheduler() { 128 return this.taskScheduler; 129 } 130 131 132 /** 133 * Specify triggered tasks as a Map of Runnables (the tasks) and Trigger objects 134 * (typically custom implementations of the {@link Trigger} interface). 135 */ 136 public void setTriggerTasks(Map<Runnable, Trigger> triggerTasks) { 137 this.triggerTasks = new ArrayList<>(); 138 triggerTasks.forEach((task, trigger) -> addTriggerTask(new TriggerTask(task, trigger))); 139 } 140 141 /** 142 * Specify triggered tasks as a list of {@link TriggerTask} objects. Primarily used 143 * by {@code <task:*>} namespace parsing. 144 * @since 3.2 145 * @see ScheduledTasksBeanDefinitionParser 146 */ 147 public void setTriggerTasksList(List<TriggerTask> triggerTasks) { 148 this.triggerTasks = triggerTasks; 149 } 150 151 /** 152 * Get the trigger tasks as an unmodifiable list of {@link TriggerTask} objects. 153 * @return the list of tasks (never {@code null}) 154 * @since 4.2 155 */ 156 public List<TriggerTask> getTriggerTaskList() { 157 return (this.triggerTasks != null? Collections.unmodifiableList(this.triggerTasks) : 158 Collections.emptyList()); 159 } 160 161 /** 162 * Specify triggered tasks as a Map of Runnables (the tasks) and cron expressions. 163 * @see CronTrigger 164 */ 165 public void setCronTasks(Map<Runnable, String> cronTasks) { 166 this.cronTasks = new ArrayList<>(); 167 cronTasks.forEach(this::addCronTask); 168 } 169 170 /** 171 * Specify triggered tasks as a list of {@link CronTask} objects. Primarily used by 172 * {@code <task:*>} namespace parsing. 173 * @since 3.2 174 * @see ScheduledTasksBeanDefinitionParser 175 */ 176 public void setCronTasksList(List<CronTask> cronTasks) { 177 this.cronTasks = cronTasks; 178 } 179 180 /** 181 * Get the cron tasks as an unmodifiable list of {@link CronTask} objects. 182 * @return the list of tasks (never {@code null}) 183 * @since 4.2 184 */ 185 public List<CronTask> getCronTaskList() { 186 return (this.cronTasks != null ? Collections.unmodifiableList(this.cronTasks) : 187 Collections.emptyList()); 188 } 189 190 /** 191 * Specify triggered tasks as a Map of Runnables (the tasks) and fixed-rate values. 192 * @see TaskScheduler#scheduleAtFixedRate(Runnable, long) 193 */ 194 public void setFixedRateTasks(Map<Runnable, Long> fixedRateTasks) { 195 this.fixedRateTasks = new ArrayList<>(); 196 fixedRateTasks.forEach(this::addFixedRateTask); 197 } 198 199 /** 200 * Specify fixed-rate tasks as a list of {@link IntervalTask} objects. Primarily used 201 * by {@code <task:*>} namespace parsing. 202 * @since 3.2 203 * @see ScheduledTasksBeanDefinitionParser 204 */ 205 public void setFixedRateTasksList(List<IntervalTask> fixedRateTasks) { 206 this.fixedRateTasks = fixedRateTasks; 207 } 208 209 /** 210 * Get the fixed-rate tasks as an unmodifiable list of {@link IntervalTask} objects. 211 * @return the list of tasks (never {@code null}) 212 * @since 4.2 213 */ 214 public List<IntervalTask> getFixedRateTaskList() { 215 return (this.fixedRateTasks != null ? Collections.unmodifiableList(this.fixedRateTasks) : 216 Collections.emptyList()); 217 } 218 219 /** 220 * Specify triggered tasks as a Map of Runnables (the tasks) and fixed-delay values. 221 * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long) 222 */ 223 public void setFixedDelayTasks(Map<Runnable, Long> fixedDelayTasks) { 224 this.fixedDelayTasks = new ArrayList<>(); 225 fixedDelayTasks.forEach(this::addFixedDelayTask); 226 } 227 228 /** 229 * Specify fixed-delay tasks as a list of {@link IntervalTask} objects. Primarily used 230 * by {@code <task:*>} namespace parsing. 231 * @since 3.2 232 * @see ScheduledTasksBeanDefinitionParser 233 */ 234 public void setFixedDelayTasksList(List<IntervalTask> fixedDelayTasks) { 235 this.fixedDelayTasks = fixedDelayTasks; 236 } 237 238 /** 239 * Get the fixed-delay tasks as an unmodifiable list of {@link IntervalTask} objects. 240 * @return the list of tasks (never {@code null}) 241 * @since 4.2 242 */ 243 public List<IntervalTask> getFixedDelayTaskList() { 244 return (this.fixedDelayTasks != null ? Collections.unmodifiableList(this.fixedDelayTasks) : 245 Collections.emptyList()); 246 } 247 248 249 /** 250 * Add a Runnable task to be triggered per the given {@link Trigger}. 251 * @see TaskScheduler#scheduleAtFixedRate(Runnable, long) 252 */ 253 public void addTriggerTask(Runnable task, Trigger trigger) { 254 addTriggerTask(new TriggerTask(task, trigger)); 255 } 256 257 /** 258 * Add a {@code TriggerTask}. 259 * @since 3.2 260 * @see TaskScheduler#scheduleAtFixedRate(Runnable, long) 261 */ 262 public void addTriggerTask(TriggerTask task) { 263 if (this.triggerTasks == null) { 264 this.triggerTasks = new ArrayList<>(); 265 } 266 this.triggerTasks.add(task); 267 } 268 269 /** 270 * Add a {@link Runnable} task to be triggered per the given cron {@code expression}. 271 * <p>As of Spring Framework 5.2, this method will not register the task if the 272 * {@code expression} is equal to {@link #CRON_DISABLED}. 273 */ 274 public void addCronTask(Runnable task, String expression) { 275 if (!CRON_DISABLED.equals(expression)) { 276 addCronTask(new CronTask(task, expression)); 277 } 278 } 279 280 /** 281 * Add a {@link CronTask}. 282 * @since 3.2 283 */ 284 public void addCronTask(CronTask task) { 285 if (this.cronTasks == null) { 286 this.cronTasks = new ArrayList<>(); 287 } 288 this.cronTasks.add(task); 289 } 290 291 /** 292 * Add a {@code Runnable} task to be triggered at the given fixed-rate interval. 293 * @see TaskScheduler#scheduleAtFixedRate(Runnable, long) 294 */ 295 public void addFixedRateTask(Runnable task, long interval) { 296 addFixedRateTask(new IntervalTask(task, interval, 0)); 297 } 298 299 /** 300 * Add a fixed-rate {@link IntervalTask}. 301 * @since 3.2 302 * @see TaskScheduler#scheduleAtFixedRate(Runnable, long) 303 */ 304 public void addFixedRateTask(IntervalTask task) { 305 if (this.fixedRateTasks == null) { 306 this.fixedRateTasks = new ArrayList<>(); 307 } 308 this.fixedRateTasks.add(task); 309 } 310 311 /** 312 * Add a Runnable task to be triggered with the given fixed delay. 313 * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long) 314 */ 315 public void addFixedDelayTask(Runnable task, long delay) { 316 addFixedDelayTask(new IntervalTask(task, delay, 0)); 317 } 318 319 /** 320 * Add a fixed-delay {@link IntervalTask}. 321 * @since 3.2 322 * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long) 323 */ 324 public void addFixedDelayTask(IntervalTask task) { 325 if (this.fixedDelayTasks == null) { 326 this.fixedDelayTasks = new ArrayList<>(); 327 } 328 this.fixedDelayTasks.add(task); 329 } 330 331 332 /** 333 * Return whether this {@code ScheduledTaskRegistrar} has any tasks registered. 334 * @since 3.2 335 */ 336 public boolean hasTasks() { 337 return (!CollectionUtils.isEmpty(this.triggerTasks) || 338 !CollectionUtils.isEmpty(this.cronTasks) || 339 !CollectionUtils.isEmpty(this.fixedRateTasks) || 340 !CollectionUtils.isEmpty(this.fixedDelayTasks)); 341 } 342 343 344 /** 345 * Calls {@link #scheduleTasks()} at bean construction time. 346 */ 347 @Override 348 public void afterPropertiesSet() { 349 scheduleTasks(); 350 } 351 352 /** 353 * Schedule all registered tasks against the underlying 354 * {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}. 355 */ 356 @SuppressWarnings("deprecation") 357 protected void scheduleTasks() { 358 if (this.taskScheduler == null) { 359 this.localExecutor = Executors.newSingleThreadScheduledExecutor(); 360 this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor); 361 } 362 if (this.triggerTasks != null) { 363 for (TriggerTask task : this.triggerTasks) { 364 addScheduledTask(scheduleTriggerTask(task)); 365 } 366 } 367 if (this.cronTasks != null) { 368 for (CronTask task : this.cronTasks) { 369 addScheduledTask(scheduleCronTask(task)); 370 } 371 } 372 if (this.fixedRateTasks != null) { 373 for (IntervalTask task : this.fixedRateTasks) { 374 addScheduledTask(scheduleFixedRateTask(task)); 375 } 376 } 377 if (this.fixedDelayTasks != null) { 378 for (IntervalTask task : this.fixedDelayTasks) { 379 addScheduledTask(scheduleFixedDelayTask(task)); 380 } 381 } 382 } 383 384 private void addScheduledTask(@Nullable ScheduledTask task) { 385 if (task != null) { 386 this.scheduledTasks.add(task); 387 } 388 } 389 390 391 /** 392 * Schedule the specified trigger task, either right away if possible 393 * or on initialization of the scheduler. 394 * @return a handle to the scheduled task, allowing to cancel it 395 * @since 4.3 396 */ 397 @Nullable 398 public ScheduledTask scheduleTriggerTask(TriggerTask task) { 399 ScheduledTask scheduledTask = this.unresolvedTasks.remove(task); 400 boolean newTask = false; 401 if (scheduledTask == null) { 402 scheduledTask = new ScheduledTask(task); 403 newTask = true; 404 } 405 if (this.taskScheduler != null) { 406 scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger()); 407 } 408 else { 409 addTriggerTask(task); 410 this.unresolvedTasks.put(task, scheduledTask); 411 } 412 return (newTask ? scheduledTask : null); 413 } 414 415 /** 416 * Schedule the specified cron task, either right away if possible 417 * or on initialization of the scheduler. 418 * @return a handle to the scheduled task, allowing to cancel it 419 * (or {@code null} if processing a previously registered task) 420 * @since 4.3 421 */ 422 @Nullable 423 public ScheduledTask scheduleCronTask(CronTask task) { 424 ScheduledTask scheduledTask = this.unresolvedTasks.remove(task); 425 boolean newTask = false; 426 if (scheduledTask == null) { 427 scheduledTask = new ScheduledTask(task); 428 newTask = true; 429 } 430 if (this.taskScheduler != null) { 431 scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger()); 432 } 433 else { 434 addCronTask(task); 435 this.unresolvedTasks.put(task, scheduledTask); 436 } 437 return (newTask ? scheduledTask : null); 438 } 439 440 /** 441 * Schedule the specified fixed-rate task, either right away if possible 442 * or on initialization of the scheduler. 443 * @return a handle to the scheduled task, allowing to cancel it 444 * (or {@code null} if processing a previously registered task) 445 * @since 4.3 446 * @deprecated as of 5.0.2, in favor of {@link #scheduleFixedRateTask(FixedRateTask)} 447 */ 448 @Deprecated 449 @Nullable 450 public ScheduledTask scheduleFixedRateTask(IntervalTask task) { 451 FixedRateTask taskToUse = (task instanceof FixedRateTask ? (FixedRateTask) task : 452 new FixedRateTask(task.getRunnable(), task.getInterval(), task.getInitialDelay())); 453 return scheduleFixedRateTask(taskToUse); 454 } 455 456 /** 457 * Schedule the specified fixed-rate task, either right away if possible 458 * or on initialization of the scheduler. 459 * @return a handle to the scheduled task, allowing to cancel it 460 * (or {@code null} if processing a previously registered task) 461 * @since 5.0.2 462 */ 463 @Nullable 464 public ScheduledTask scheduleFixedRateTask(FixedRateTask task) { 465 ScheduledTask scheduledTask = this.unresolvedTasks.remove(task); 466 boolean newTask = false; 467 if (scheduledTask == null) { 468 scheduledTask = new ScheduledTask(task); 469 newTask = true; 470 } 471 if (this.taskScheduler != null) { 472 if (task.getInitialDelay() > 0) { 473 Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay()); 474 scheduledTask.future = 475 this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval()); 476 } 477 else { 478 scheduledTask.future = 479 this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval()); 480 } 481 } 482 else { 483 addFixedRateTask(task); 484 this.unresolvedTasks.put(task, scheduledTask); 485 } 486 return (newTask ? scheduledTask : null); 487 } 488 489 /** 490 * Schedule the specified fixed-delay task, either right away if possible 491 * or on initialization of the scheduler. 492 * @return a handle to the scheduled task, allowing to cancel it 493 * (or {@code null} if processing a previously registered task) 494 * @since 4.3 495 * @deprecated as of 5.0.2, in favor of {@link #scheduleFixedDelayTask(FixedDelayTask)} 496 */ 497 @Deprecated 498 @Nullable 499 public ScheduledTask scheduleFixedDelayTask(IntervalTask task) { 500 FixedDelayTask taskToUse = (task instanceof FixedDelayTask ? (FixedDelayTask) task : 501 new FixedDelayTask(task.getRunnable(), task.getInterval(), task.getInitialDelay())); 502 return scheduleFixedDelayTask(taskToUse); 503 } 504 505 /** 506 * Schedule the specified fixed-delay task, either right away if possible 507 * or on initialization of the scheduler. 508 * @return a handle to the scheduled task, allowing to cancel it 509 * (or {@code null} if processing a previously registered task) 510 * @since 5.0.2 511 */ 512 @Nullable 513 public ScheduledTask scheduleFixedDelayTask(FixedDelayTask task) { 514 ScheduledTask scheduledTask = this.unresolvedTasks.remove(task); 515 boolean newTask = false; 516 if (scheduledTask == null) { 517 scheduledTask = new ScheduledTask(task); 518 newTask = true; 519 } 520 if (this.taskScheduler != null) { 521 if (task.getInitialDelay() > 0) { 522 Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay()); 523 scheduledTask.future = 524 this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), startTime, task.getInterval()); 525 } 526 else { 527 scheduledTask.future = 528 this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), task.getInterval()); 529 } 530 } 531 else { 532 addFixedDelayTask(task); 533 this.unresolvedTasks.put(task, scheduledTask); 534 } 535 return (newTask ? scheduledTask : null); 536 } 537 538 539 /** 540 * Return all locally registered tasks that have been scheduled by this registrar. 541 * @since 5.0.2 542 * @see #addTriggerTask 543 * @see #addCronTask 544 * @see #addFixedRateTask 545 * @see #addFixedDelayTask 546 */ 547 @Override 548 public Set<ScheduledTask> getScheduledTasks() { 549 return Collections.unmodifiableSet(this.scheduledTasks); 550 } 551 552 @Override 553 public void destroy() { 554 for (ScheduledTask task : this.scheduledTasks) { 555 task.cancel(); 556 } 557 if (this.localExecutor != null) { 558 this.localExecutor.shutdownNow(); 559 } 560 } 561 562}