001/*
002 * Copyright 2002-2018 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.concurrent;
018
019import java.util.concurrent.ExecutorService;
020import java.util.concurrent.Executors;
021import java.util.concurrent.RejectedExecutionHandler;
022import java.util.concurrent.ScheduledExecutorService;
023import java.util.concurrent.ScheduledThreadPoolExecutor;
024import java.util.concurrent.ThreadFactory;
025
026import org.springframework.beans.factory.FactoryBean;
027import org.springframework.lang.Nullable;
028import org.springframework.scheduling.support.DelegatingErrorHandlingRunnable;
029import org.springframework.scheduling.support.TaskUtils;
030import org.springframework.util.Assert;
031import org.springframework.util.ObjectUtils;
032
033/**
034 * {@link org.springframework.beans.factory.FactoryBean} that sets up
035 * a {@link java.util.concurrent.ScheduledExecutorService}
036 * (by default: a {@link java.util.concurrent.ScheduledThreadPoolExecutor})
037 * and exposes it for bean references.
038 *
039 * <p>Allows for registration of {@link ScheduledExecutorTask ScheduledExecutorTasks},
040 * automatically starting the {@link ScheduledExecutorService} on initialization and
041 * cancelling it on destruction of the context. In scenarios that only require static
042 * registration of tasks at startup, there is no need to access the
043 * {@link ScheduledExecutorService} instance itself in application code at all;
044 * {@code ScheduledExecutorFactoryBean} is then just being used for lifecycle integration.
045 *
046 * <p>For an alternative, you may set up a {@link ScheduledThreadPoolExecutor} instance
047 * directly using constructor injection, or use a factory method definition that points
048 * to the {@link java.util.concurrent.Executors} class.
049 * <b>This is strongly recommended in particular for common {@code @Bean} methods in
050 * configuration classes, where this {@code FactoryBean} variant would force you to
051 * return the {@code FactoryBean} type instead of {@code ScheduledExecutorService}.</b>
052 *
053 * <p>Note that {@link java.util.concurrent.ScheduledExecutorService}
054 * uses a {@link Runnable} instance that is shared between repeated executions,
055 * in contrast to Quartz which instantiates a new Job for each execution.
056 *
057 * <p><b>WARNING:</b> {@link Runnable Runnables} submitted via a native
058 * {@link java.util.concurrent.ScheduledExecutorService} are removed from
059 * the execution schedule once they throw an exception. If you would prefer
060 * to continue execution after such an exception, switch this FactoryBean's
061 * {@link #setContinueScheduledExecutionAfterException "continueScheduledExecutionAfterException"}
062 * property to "true".
063 *
064 * @author Juergen Hoeller
065 * @since 2.0
066 * @see #setPoolSize
067 * @see #setRemoveOnCancelPolicy
068 * @see #setThreadFactory
069 * @see ScheduledExecutorTask
070 * @see java.util.concurrent.ScheduledExecutorService
071 * @see java.util.concurrent.ScheduledThreadPoolExecutor
072 */
073@SuppressWarnings("serial")
074public class ScheduledExecutorFactoryBean extends ExecutorConfigurationSupport
075                implements FactoryBean<ScheduledExecutorService> {
076
077        private int poolSize = 1;
078
079        @Nullable
080        private ScheduledExecutorTask[] scheduledExecutorTasks;
081
082        private boolean removeOnCancelPolicy = false;
083
084        private boolean continueScheduledExecutionAfterException = false;
085
086        private boolean exposeUnconfigurableExecutor = false;
087
088        @Nullable
089        private ScheduledExecutorService exposedExecutor;
090
091
092        /**
093         * Set the ScheduledExecutorService's pool size.
094         * Default is 1.
095         */
096        public void setPoolSize(int poolSize) {
097                Assert.isTrue(poolSize > 0, "'poolSize' must be 1 or higher");
098                this.poolSize = poolSize;
099        }
100
101        /**
102         * Register a list of ScheduledExecutorTask objects with the ScheduledExecutorService
103         * that this FactoryBean creates. Depending on each ScheduledExecutorTask's settings,
104         * it will be registered via one of ScheduledExecutorService's schedule methods.
105         * @see java.util.concurrent.ScheduledExecutorService#schedule(java.lang.Runnable, long, java.util.concurrent.TimeUnit)
106         * @see java.util.concurrent.ScheduledExecutorService#scheduleWithFixedDelay(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit)
107         * @see java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit)
108         */
109        public void setScheduledExecutorTasks(ScheduledExecutorTask... scheduledExecutorTasks) {
110                this.scheduledExecutorTasks = scheduledExecutorTasks;
111        }
112
113        /**
114         * Set the remove-on-cancel mode on {@link ScheduledThreadPoolExecutor}.
115         * <p>Default is {@code false}. If set to {@code true}, the target executor will be
116         * switched into remove-on-cancel mode (if possible, with a soft fallback otherwise).
117         */
118        public void setRemoveOnCancelPolicy(boolean removeOnCancelPolicy) {
119                this.removeOnCancelPolicy = removeOnCancelPolicy;
120        }
121
122        /**
123         * Specify whether to continue the execution of a scheduled task
124         * after it threw an exception.
125         * <p>Default is "false", matching the native behavior of a
126         * {@link java.util.concurrent.ScheduledExecutorService}.
127         * Switch this flag to "true" for exception-proof execution of each task,
128         * continuing scheduled execution as in the case of successful execution.
129         * @see java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate
130         */
131        public void setContinueScheduledExecutionAfterException(boolean continueScheduledExecutionAfterException) {
132                this.continueScheduledExecutionAfterException = continueScheduledExecutionAfterException;
133        }
134
135        /**
136         * Specify whether this FactoryBean should expose an unconfigurable
137         * decorator for the created executor.
138         * <p>Default is "false", exposing the raw executor as bean reference.
139         * Switch this flag to "true" to strictly prevent clients from
140         * modifying the executor's configuration.
141         * @see java.util.concurrent.Executors#unconfigurableScheduledExecutorService
142         */
143        public void setExposeUnconfigurableExecutor(boolean exposeUnconfigurableExecutor) {
144                this.exposeUnconfigurableExecutor = exposeUnconfigurableExecutor;
145        }
146
147
148        @Override
149        protected ExecutorService initializeExecutor(
150                        ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
151
152                ScheduledExecutorService executor =
153                                createExecutor(this.poolSize, threadFactory, rejectedExecutionHandler);
154
155                if (this.removeOnCancelPolicy) {
156                        if (executor instanceof ScheduledThreadPoolExecutor) {
157                                ((ScheduledThreadPoolExecutor) executor).setRemoveOnCancelPolicy(true);
158                        }
159                        else {
160                                logger.debug("Could not apply remove-on-cancel policy - not a ScheduledThreadPoolExecutor");
161                        }
162                }
163
164                // Register specified ScheduledExecutorTasks, if necessary.
165                if (!ObjectUtils.isEmpty(this.scheduledExecutorTasks)) {
166                        registerTasks(this.scheduledExecutorTasks, executor);
167                }
168
169                // Wrap executor with an unconfigurable decorator.
170                this.exposedExecutor = (this.exposeUnconfigurableExecutor ?
171                                Executors.unconfigurableScheduledExecutorService(executor) : executor);
172
173                return executor;
174        }
175
176        /**
177         * Create a new {@link ScheduledExecutorService} instance.
178         * <p>The default implementation creates a {@link ScheduledThreadPoolExecutor}.
179         * Can be overridden in subclasses to provide custom {@link ScheduledExecutorService} instances.
180         * @param poolSize the specified pool size
181         * @param threadFactory the ThreadFactory to use
182         * @param rejectedExecutionHandler the RejectedExecutionHandler to use
183         * @return a new ScheduledExecutorService instance
184         * @see #afterPropertiesSet()
185         * @see java.util.concurrent.ScheduledThreadPoolExecutor
186         */
187        protected ScheduledExecutorService createExecutor(
188                        int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
189
190                return new ScheduledThreadPoolExecutor(poolSize, threadFactory, rejectedExecutionHandler);
191        }
192
193        /**
194         * Register the specified {@link ScheduledExecutorTask ScheduledExecutorTasks}
195         * on the given {@link ScheduledExecutorService}.
196         * @param tasks the specified ScheduledExecutorTasks (never empty)
197         * @param executor the ScheduledExecutorService to register the tasks on.
198         */
199        protected void registerTasks(ScheduledExecutorTask[] tasks, ScheduledExecutorService executor) {
200                for (ScheduledExecutorTask task : tasks) {
201                        Runnable runnable = getRunnableToSchedule(task);
202                        if (task.isOneTimeTask()) {
203                                executor.schedule(runnable, task.getDelay(), task.getTimeUnit());
204                        }
205                        else {
206                                if (task.isFixedRate()) {
207                                        executor.scheduleAtFixedRate(runnable, task.getDelay(), task.getPeriod(), task.getTimeUnit());
208                                }
209                                else {
210                                        executor.scheduleWithFixedDelay(runnable, task.getDelay(), task.getPeriod(), task.getTimeUnit());
211                                }
212                        }
213                }
214        }
215
216        /**
217         * Determine the actual Runnable to schedule for the given task.
218         * <p>Wraps the task's Runnable in a
219         * {@link org.springframework.scheduling.support.DelegatingErrorHandlingRunnable}
220         * that will catch and log the Exception. If necessary, it will suppress the
221         * Exception according to the
222         * {@link #setContinueScheduledExecutionAfterException "continueScheduledExecutionAfterException"}
223         * flag.
224         * @param task the ScheduledExecutorTask to schedule
225         * @return the actual Runnable to schedule (may be a decorator)
226         */
227        protected Runnable getRunnableToSchedule(ScheduledExecutorTask task) {
228                return (this.continueScheduledExecutionAfterException ?
229                                new DelegatingErrorHandlingRunnable(task.getRunnable(), TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER) :
230                                new DelegatingErrorHandlingRunnable(task.getRunnable(), TaskUtils.LOG_AND_PROPAGATE_ERROR_HANDLER));
231        }
232
233
234        @Override
235        @Nullable
236        public ScheduledExecutorService getObject() {
237                return this.exposedExecutor;
238        }
239
240        @Override
241        public Class<? extends ScheduledExecutorService> getObjectType() {
242                return (this.exposedExecutor != null ? this.exposedExecutor.getClass() : ScheduledExecutorService.class);
243        }
244
245        @Override
246        public boolean isSingleton() {
247                return true;
248        }
249
250}