/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.scheduling.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.support.DelegatingErrorHandlingRunnable;
import org.springframework.scheduling.support.TaskUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

FactoryBean that sets up a ScheduledExecutorService (by default: a ScheduledThreadPoolExecutor) and exposes it for bean references.

Allows for registration of ScheduledExecutorTasks, automatically starting the ScheduledExecutorService on initialization and cancelling it on destruction of the context. In scenarios that only require static registration of tasks at startup, there is no need to access the ScheduledExecutorService instance itself in application code at all; ScheduledExecutorFactoryBean is then just being used for lifecycle integration.

For an alternative, you may set up a ScheduledThreadPoolExecutor instance directly using constructor injection, or use a factory method definition that points to the Executors class. This is strongly recommended in particular for common @Bean methods in configuration classes, where this FactoryBean variant would force you to return the FactoryBean type instead of ScheduledExecutorService.

Note that ScheduledExecutorService uses a Runnable instance that is shared between repeated executions, in contrast to Quartz which instantiates a new Job for each execution.

WARNING: Runnables submitted via a native ScheduledExecutorService are removed from the execution schedule once they throw an exception. If you would prefer to continue execution after such an exception, switch this FactoryBean's "continueScheduledExecutionAfterException" property to "true".

Author:Juergen Hoeller
See Also:
Since:2.0
/** * {@link org.springframework.beans.factory.FactoryBean} that sets up * a {@link java.util.concurrent.ScheduledExecutorService} * (by default: a {@link java.util.concurrent.ScheduledThreadPoolExecutor}) * and exposes it for bean references. * * <p>Allows for registration of {@link ScheduledExecutorTask ScheduledExecutorTasks}, * automatically starting the {@link ScheduledExecutorService} on initialization and * cancelling it on destruction of the context. In scenarios that only require static * registration of tasks at startup, there is no need to access the * {@link ScheduledExecutorService} instance itself in application code at all; * {@code ScheduledExecutorFactoryBean} is then just being used for lifecycle integration. * * <p>For an alternative, you may set up a {@link ScheduledThreadPoolExecutor} instance * directly using constructor injection, or use a factory method definition that points * to the {@link java.util.concurrent.Executors} class. * <b>This is strongly recommended in particular for common {@code @Bean} methods in * configuration classes, where this {@code FactoryBean} variant would force you to * return the {@code FactoryBean} type instead of {@code ScheduledExecutorService}.</b> * * <p>Note that {@link java.util.concurrent.ScheduledExecutorService} * uses a {@link Runnable} instance that is shared between repeated executions, * in contrast to Quartz which instantiates a new Job for each execution. * * <p><b>WARNING:</b> {@link Runnable Runnables} submitted via a native * {@link java.util.concurrent.ScheduledExecutorService} are removed from * the execution schedule once they throw an exception. If you would prefer * to continue execution after such an exception, switch this FactoryBean's * {@link #setContinueScheduledExecutionAfterException "continueScheduledExecutionAfterException"} * property to "true". * * @author Juergen Hoeller * @since 2.0 * @see #setPoolSize * @see #setRemoveOnCancelPolicy * @see #setThreadFactory * @see ScheduledExecutorTask * @see java.util.concurrent.ScheduledExecutorService * @see java.util.concurrent.ScheduledThreadPoolExecutor */
@SuppressWarnings("serial") public class ScheduledExecutorFactoryBean extends ExecutorConfigurationSupport implements FactoryBean<ScheduledExecutorService> { private int poolSize = 1; @Nullable private ScheduledExecutorTask[] scheduledExecutorTasks; private boolean removeOnCancelPolicy = false; private boolean continueScheduledExecutionAfterException = false; private boolean exposeUnconfigurableExecutor = false; @Nullable private ScheduledExecutorService exposedExecutor;
Set the ScheduledExecutorService's pool size. Default is 1.
/** * Set the ScheduledExecutorService's pool size. * Default is 1. */
public void setPoolSize(int poolSize) { Assert.isTrue(poolSize > 0, "'poolSize' must be 1 or higher"); this.poolSize = poolSize; }
Register a list of ScheduledExecutorTask objects with the ScheduledExecutorService that this FactoryBean creates. Depending on each ScheduledExecutorTask's settings, it will be registered via one of ScheduledExecutorService's schedule methods.
See Also:
/** * Register a list of ScheduledExecutorTask objects with the ScheduledExecutorService * that this FactoryBean creates. Depending on each ScheduledExecutorTask's settings, * it will be registered via one of ScheduledExecutorService's schedule methods. * @see java.util.concurrent.ScheduledExecutorService#schedule(java.lang.Runnable, long, java.util.concurrent.TimeUnit) * @see java.util.concurrent.ScheduledExecutorService#scheduleWithFixedDelay(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit) * @see java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit) */
public void setScheduledExecutorTasks(ScheduledExecutorTask... scheduledExecutorTasks) { this.scheduledExecutorTasks = scheduledExecutorTasks; }
Set the remove-on-cancel mode on ScheduledThreadPoolExecutor.

Default is false. If set to true, the target executor will be switched into remove-on-cancel mode (if possible, with a soft fallback otherwise).

/** * Set the remove-on-cancel mode on {@link ScheduledThreadPoolExecutor}. * <p>Default is {@code false}. If set to {@code true}, the target executor will be * switched into remove-on-cancel mode (if possible, with a soft fallback otherwise). */
public void setRemoveOnCancelPolicy(boolean removeOnCancelPolicy) { this.removeOnCancelPolicy = removeOnCancelPolicy; }
Specify whether to continue the execution of a scheduled task after it threw an exception.

Default is "false", matching the native behavior of a ScheduledExecutorService. Switch this flag to "true" for exception-proof execution of each task, continuing scheduled execution as in the case of successful execution.

See Also:
/** * Specify whether to continue the execution of a scheduled task * after it threw an exception. * <p>Default is "false", matching the native behavior of a * {@link java.util.concurrent.ScheduledExecutorService}. * Switch this flag to "true" for exception-proof execution of each task, * continuing scheduled execution as in the case of successful execution. * @see java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate */
public void setContinueScheduledExecutionAfterException(boolean continueScheduledExecutionAfterException) { this.continueScheduledExecutionAfterException = continueScheduledExecutionAfterException; }
Specify whether this FactoryBean should expose an unconfigurable decorator for the created executor.

Default is "false", exposing the raw executor as bean reference. Switch this flag to "true" to strictly prevent clients from modifying the executor's configuration.

See Also:
  • unconfigurableScheduledExecutorService.unconfigurableScheduledExecutorService
/** * Specify whether this FactoryBean should expose an unconfigurable * decorator for the created executor. * <p>Default is "false", exposing the raw executor as bean reference. * Switch this flag to "true" to strictly prevent clients from * modifying the executor's configuration. * @see java.util.concurrent.Executors#unconfigurableScheduledExecutorService */
public void setExposeUnconfigurableExecutor(boolean exposeUnconfigurableExecutor) { this.exposeUnconfigurableExecutor = exposeUnconfigurableExecutor; } @Override protected ExecutorService initializeExecutor( ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) { ScheduledExecutorService executor = createExecutor(this.poolSize, threadFactory, rejectedExecutionHandler); if (this.removeOnCancelPolicy) { if (executor instanceof ScheduledThreadPoolExecutor) { ((ScheduledThreadPoolExecutor) executor).setRemoveOnCancelPolicy(true); } else { logger.debug("Could not apply remove-on-cancel policy - not a ScheduledThreadPoolExecutor"); } } // Register specified ScheduledExecutorTasks, if necessary. if (!ObjectUtils.isEmpty(this.scheduledExecutorTasks)) { registerTasks(this.scheduledExecutorTasks, executor); } // Wrap executor with an unconfigurable decorator. this.exposedExecutor = (this.exposeUnconfigurableExecutor ? Executors.unconfigurableScheduledExecutorService(executor) : executor); return executor; }
Create a new ScheduledExecutorService instance.

The default implementation creates a ScheduledThreadPoolExecutor. Can be overridden in subclasses to provide custom ScheduledExecutorService instances.

Params:
  • poolSize – the specified pool size
  • threadFactory – the ThreadFactory to use
  • rejectedExecutionHandler – the RejectedExecutionHandler to use
See Also:
Returns:a new ScheduledExecutorService instance
/** * Create a new {@link ScheduledExecutorService} instance. * <p>The default implementation creates a {@link ScheduledThreadPoolExecutor}. * Can be overridden in subclasses to provide custom {@link ScheduledExecutorService} instances. * @param poolSize the specified pool size * @param threadFactory the ThreadFactory to use * @param rejectedExecutionHandler the RejectedExecutionHandler to use * @return a new ScheduledExecutorService instance * @see #afterPropertiesSet() * @see java.util.concurrent.ScheduledThreadPoolExecutor */
protected ScheduledExecutorService createExecutor( int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) { return new ScheduledThreadPoolExecutor(poolSize, threadFactory, rejectedExecutionHandler); }
Register the specified ScheduledExecutorTasks on the given ScheduledExecutorService.
Params:
  • tasks – the specified ScheduledExecutorTasks (never empty)
  • executor – the ScheduledExecutorService to register the tasks on.
/** * Register the specified {@link ScheduledExecutorTask ScheduledExecutorTasks} * on the given {@link ScheduledExecutorService}. * @param tasks the specified ScheduledExecutorTasks (never empty) * @param executor the ScheduledExecutorService to register the tasks on. */
protected void registerTasks(ScheduledExecutorTask[] tasks, ScheduledExecutorService executor) { for (ScheduledExecutorTask task : tasks) { Runnable runnable = getRunnableToSchedule(task); if (task.isOneTimeTask()) { executor.schedule(runnable, task.getDelay(), task.getTimeUnit()); } else { if (task.isFixedRate()) { executor.scheduleAtFixedRate(runnable, task.getDelay(), task.getPeriod(), task.getTimeUnit()); } else { executor.scheduleWithFixedDelay(runnable, task.getDelay(), task.getPeriod(), task.getTimeUnit()); } } } }
Determine the actual Runnable to schedule for the given task.

Wraps the task's Runnable in a DelegatingErrorHandlingRunnable that will catch and log the Exception. If necessary, it will suppress the Exception according to the "continueScheduledExecutionAfterException" flag.

Params:
  • task – the ScheduledExecutorTask to schedule
Returns:the actual Runnable to schedule (may be a decorator)
/** * Determine the actual Runnable to schedule for the given task. * <p>Wraps the task's Runnable in a * {@link org.springframework.scheduling.support.DelegatingErrorHandlingRunnable} * that will catch and log the Exception. If necessary, it will suppress the * Exception according to the * {@link #setContinueScheduledExecutionAfterException "continueScheduledExecutionAfterException"} * flag. * @param task the ScheduledExecutorTask to schedule * @return the actual Runnable to schedule (may be a decorator) */
protected Runnable getRunnableToSchedule(ScheduledExecutorTask task) { return (this.continueScheduledExecutionAfterException ? new DelegatingErrorHandlingRunnable(task.getRunnable(), TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER) : new DelegatingErrorHandlingRunnable(task.getRunnable(), TaskUtils.LOG_AND_PROPAGATE_ERROR_HANDLER)); } @Override @Nullable public ScheduledExecutorService getObject() { return this.exposedExecutor; } @Override public Class<? extends ScheduledExecutorService> getObjectType() { return (this.exposedExecutor != null ? this.exposedExecutor.getClass() : ScheduledExecutorService.class); } @Override public boolean isSingleton() { return true; } }