001/*
002 * Copyright 2002-2020 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.annotation;
018
019import java.lang.reflect.Method;
020import java.time.Duration;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.IdentityHashMap;
026import java.util.LinkedHashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030import java.util.TimeZone;
031import java.util.concurrent.ConcurrentHashMap;
032import java.util.concurrent.ScheduledExecutorService;
033
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036
037import org.springframework.aop.framework.AopInfrastructureBean;
038import org.springframework.aop.framework.AopProxyUtils;
039import org.springframework.aop.support.AopUtils;
040import org.springframework.beans.factory.BeanFactory;
041import org.springframework.beans.factory.BeanFactoryAware;
042import org.springframework.beans.factory.BeanNameAware;
043import org.springframework.beans.factory.DisposableBean;
044import org.springframework.beans.factory.ListableBeanFactory;
045import org.springframework.beans.factory.NoSuchBeanDefinitionException;
046import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
047import org.springframework.beans.factory.SmartInitializingSingleton;
048import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
049import org.springframework.beans.factory.config.ConfigurableBeanFactory;
050import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
051import org.springframework.beans.factory.config.NamedBeanHolder;
052import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
053import org.springframework.beans.factory.support.RootBeanDefinition;
054import org.springframework.context.ApplicationContext;
055import org.springframework.context.ApplicationContextAware;
056import org.springframework.context.ApplicationListener;
057import org.springframework.context.EmbeddedValueResolverAware;
058import org.springframework.context.event.ContextRefreshedEvent;
059import org.springframework.core.MethodIntrospector;
060import org.springframework.core.Ordered;
061import org.springframework.core.annotation.AnnotatedElementUtils;
062import org.springframework.core.annotation.AnnotationAwareOrderComparator;
063import org.springframework.core.annotation.AnnotationUtils;
064import org.springframework.lang.Nullable;
065import org.springframework.scheduling.TaskScheduler;
066import org.springframework.scheduling.Trigger;
067import org.springframework.scheduling.config.CronTask;
068import org.springframework.scheduling.config.FixedDelayTask;
069import org.springframework.scheduling.config.FixedRateTask;
070import org.springframework.scheduling.config.ScheduledTask;
071import org.springframework.scheduling.config.ScheduledTaskHolder;
072import org.springframework.scheduling.config.ScheduledTaskRegistrar;
073import org.springframework.scheduling.support.CronTrigger;
074import org.springframework.scheduling.support.ScheduledMethodRunnable;
075import org.springframework.util.Assert;
076import org.springframework.util.StringUtils;
077import org.springframework.util.StringValueResolver;
078
079/**
080 * Bean post-processor that registers methods annotated with @{@link Scheduled}
081 * to be invoked by a {@link org.springframework.scheduling.TaskScheduler} according
082 * to the "fixedRate", "fixedDelay", or "cron" expression provided via the annotation.
083 *
084 * <p>This post-processor is automatically registered by Spring's
085 * {@code <task:annotation-driven>} XML element, and also by the
086 * {@link EnableScheduling @EnableScheduling} annotation.
087 *
088 * <p>Autodetects any {@link SchedulingConfigurer} instances in the container,
089 * allowing for customization of the scheduler to be used or for fine-grained
090 * control over task registration (e.g. registration of {@link Trigger} tasks.
091 * See the @{@link EnableScheduling} javadocs for complete usage details.
092 *
093 * @author Mark Fisher
094 * @author Juergen Hoeller
095 * @author Chris Beams
096 * @author Elizabeth Chatman
097 * @since 3.0
098 * @see Scheduled
099 * @see EnableScheduling
100 * @see SchedulingConfigurer
101 * @see org.springframework.scheduling.TaskScheduler
102 * @see org.springframework.scheduling.config.ScheduledTaskRegistrar
103 * @see AsyncAnnotationBeanPostProcessor
104 */
105public class ScheduledAnnotationBeanPostProcessor
106                implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
107                Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
108                SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
109
110        /**
111         * The default name of the {@link TaskScheduler} bean to pick up: {@value}.
112         * <p>Note that the initial lookup happens by type; this is just the fallback
113         * in case of multiple scheduler beans found in the context.
114         * @since 4.2
115         */
116        public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
117
118
119        protected final Log logger = LogFactory.getLog(getClass());
120
121        private final ScheduledTaskRegistrar registrar;
122
123        @Nullable
124        private Object scheduler;
125
126        @Nullable
127        private StringValueResolver embeddedValueResolver;
128
129        @Nullable
130        private String beanName;
131
132        @Nullable
133        private BeanFactory beanFactory;
134
135        @Nullable
136        private ApplicationContext applicationContext;
137
138        private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));
139
140        private final Map<Object, Set<ScheduledTask>> scheduledTasks = new IdentityHashMap<>(16);
141
142
143        /**
144         * Create a default {@code ScheduledAnnotationBeanPostProcessor}.
145         */
146        public ScheduledAnnotationBeanPostProcessor() {
147                this.registrar = new ScheduledTaskRegistrar();
148        }
149
150        /**
151         * Create a {@code ScheduledAnnotationBeanPostProcessor} delegating to the
152         * specified {@link ScheduledTaskRegistrar}.
153         * @param registrar the ScheduledTaskRegistrar to register @Scheduled tasks on
154         * @since 5.1
155         */
156        public ScheduledAnnotationBeanPostProcessor(ScheduledTaskRegistrar registrar) {
157                Assert.notNull(registrar, "ScheduledTaskRegistrar is required");
158                this.registrar = registrar;
159        }
160
161
162        @Override
163        public int getOrder() {
164                return LOWEST_PRECEDENCE;
165        }
166
167        /**
168         * Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke
169         * the scheduled methods, or a {@link java.util.concurrent.ScheduledExecutorService}
170         * to be wrapped as a TaskScheduler.
171         * <p>If not specified, default scheduler resolution will apply: searching for a
172         * unique {@link TaskScheduler} bean in the context, or for a {@link TaskScheduler}
173         * bean named "taskScheduler" otherwise; the same lookup will also be performed for
174         * a {@link ScheduledExecutorService} bean. If neither of the two is resolvable,
175         * a local single-threaded default scheduler will be created within the registrar.
176         * @see #DEFAULT_TASK_SCHEDULER_BEAN_NAME
177         */
178        public void setScheduler(Object scheduler) {
179                this.scheduler = scheduler;
180        }
181
182        @Override
183        public void setEmbeddedValueResolver(StringValueResolver resolver) {
184                this.embeddedValueResolver = resolver;
185        }
186
187        @Override
188        public void setBeanName(String beanName) {
189                this.beanName = beanName;
190        }
191
192        /**
193         * Making a {@link BeanFactory} available is optional; if not set,
194         * {@link SchedulingConfigurer} beans won't get autodetected and
195         * a {@link #setScheduler scheduler} has to be explicitly configured.
196         */
197        @Override
198        public void setBeanFactory(BeanFactory beanFactory) {
199                this.beanFactory = beanFactory;
200        }
201
202        /**
203         * Setting an {@link ApplicationContext} is optional: If set, registered
204         * tasks will be activated in the {@link ContextRefreshedEvent} phase;
205         * if not set, it will happen at {@link #afterSingletonsInstantiated} time.
206         */
207        @Override
208        public void setApplicationContext(ApplicationContext applicationContext) {
209                this.applicationContext = applicationContext;
210                if (this.beanFactory == null) {
211                        this.beanFactory = applicationContext;
212                }
213        }
214
215
216        @Override
217        public void afterSingletonsInstantiated() {
218                // Remove resolved singleton classes from cache
219                this.nonAnnotatedClasses.clear();
220
221                if (this.applicationContext == null) {
222                        // Not running in an ApplicationContext -> register tasks early...
223                        finishRegistration();
224                }
225        }
226
227        @Override
228        public void onApplicationEvent(ContextRefreshedEvent event) {
229                if (event.getApplicationContext() == this.applicationContext) {
230                        // Running in an ApplicationContext -> register tasks this late...
231                        // giving other ContextRefreshedEvent listeners a chance to perform
232                        // their work at the same time (e.g. Spring Batch's job registration).
233                        finishRegistration();
234                }
235        }
236
237        private void finishRegistration() {
238                if (this.scheduler != null) {
239                        this.registrar.setScheduler(this.scheduler);
240                }
241
242                if (this.beanFactory instanceof ListableBeanFactory) {
243                        Map<String, SchedulingConfigurer> beans =
244                                        ((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
245                        List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());
246                        AnnotationAwareOrderComparator.sort(configurers);
247                        for (SchedulingConfigurer configurer : configurers) {
248                                configurer.configureTasks(this.registrar);
249                        }
250                }
251
252                if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
253                        Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
254                        try {
255                                // Search for TaskScheduler bean...
256                                this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
257                        }
258                        catch (NoUniqueBeanDefinitionException ex) {
259                                if (logger.isTraceEnabled()) {
260                                        logger.trace("Could not find unique TaskScheduler bean - attempting to resolve by name: " +
261                                                        ex.getMessage());
262                                }
263                                try {
264                                        this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
265                                }
266                                catch (NoSuchBeanDefinitionException ex2) {
267                                        if (logger.isInfoEnabled()) {
268                                                logger.info("More than one TaskScheduler bean exists within the context, and " +
269                                                                "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
270                                                                "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
271                                                                "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
272                                                                ex.getBeanNamesFound());
273                                        }
274                                }
275                        }
276                        catch (NoSuchBeanDefinitionException ex) {
277                                if (logger.isTraceEnabled()) {
278                                        logger.trace("Could not find default TaskScheduler bean - attempting to find ScheduledExecutorService: " +
279                                                        ex.getMessage());
280                                }
281                                // Search for ScheduledExecutorService bean next...
282                                try {
283                                        this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
284                                }
285                                catch (NoUniqueBeanDefinitionException ex2) {
286                                        if (logger.isTraceEnabled()) {
287                                                logger.trace("Could not find unique ScheduledExecutorService bean - attempting to resolve by name: " +
288                                                                ex2.getMessage());
289                                        }
290                                        try {
291                                                this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
292                                        }
293                                        catch (NoSuchBeanDefinitionException ex3) {
294                                                if (logger.isInfoEnabled()) {
295                                                        logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
296                                                                        "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
297                                                                        "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
298                                                                        "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
299                                                                        ex2.getBeanNamesFound());
300                                                }
301                                        }
302                                }
303                                catch (NoSuchBeanDefinitionException ex2) {
304                                        if (logger.isTraceEnabled()) {
305                                                logger.trace("Could not find default ScheduledExecutorService bean - falling back to default: " +
306                                                                ex2.getMessage());
307                                        }
308                                        // Giving up -> falling back to default scheduler within the registrar...
309                                        logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
310                                }
311                        }
312                }
313
314                this.registrar.afterPropertiesSet();
315        }
316
317        private <T> T resolveSchedulerBean(BeanFactory beanFactory, Class<T> schedulerType, boolean byName) {
318                if (byName) {
319                        T scheduler = beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, schedulerType);
320                        if (this.beanName != null && this.beanFactory instanceof ConfigurableBeanFactory) {
321                                ((ConfigurableBeanFactory) this.beanFactory).registerDependentBean(
322                                                DEFAULT_TASK_SCHEDULER_BEAN_NAME, this.beanName);
323                        }
324                        return scheduler;
325                }
326                else if (beanFactory instanceof AutowireCapableBeanFactory) {
327                        NamedBeanHolder<T> holder = ((AutowireCapableBeanFactory) beanFactory).resolveNamedBean(schedulerType);
328                        if (this.beanName != null && beanFactory instanceof ConfigurableBeanFactory) {
329                                ((ConfigurableBeanFactory) beanFactory).registerDependentBean(holder.getBeanName(), this.beanName);
330                        }
331                        return holder.getBeanInstance();
332                }
333                else {
334                        return beanFactory.getBean(schedulerType);
335                }
336        }
337
338
339        @Override
340        public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
341        }
342
343        @Override
344        public Object postProcessBeforeInitialization(Object bean, String beanName) {
345                return bean;
346        }
347
348        @Override
349        public Object postProcessAfterInitialization(Object bean, String beanName) {
350                if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
351                                bean instanceof ScheduledExecutorService) {
352                        // Ignore AOP infrastructure such as scoped proxies.
353                        return bean;
354                }
355
356                Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
357                if (!this.nonAnnotatedClasses.contains(targetClass) &&
358                                AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
359                        Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
360                                        (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
361                                                Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
362                                                                method, Scheduled.class, Schedules.class);
363                                                return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
364                                        });
365                        if (annotatedMethods.isEmpty()) {
366                                this.nonAnnotatedClasses.add(targetClass);
367                                if (logger.isTraceEnabled()) {
368                                        logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
369                                }
370                        }
371                        else {
372                                // Non-empty set of methods
373                                annotatedMethods.forEach((method, scheduledMethods) ->
374                                                scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
375                                if (logger.isTraceEnabled()) {
376                                        logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
377                                                        "': " + annotatedMethods);
378                                }
379                        }
380                }
381                return bean;
382        }
383
384        /**
385         * Process the given {@code @Scheduled} method declaration on the given bean.
386         * @param scheduled the @Scheduled annotation
387         * @param method the method that the annotation has been declared on
388         * @param bean the target bean instance
389         * @see #createRunnable(Object, Method)
390         */
391        protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
392                try {
393                        Runnable runnable = createRunnable(bean, method);
394                        boolean processedSchedule = false;
395                        String errorMessage =
396                                        "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
397
398                        Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
399
400                        // Determine initial delay
401                        long initialDelay = scheduled.initialDelay();
402                        String initialDelayString = scheduled.initialDelayString();
403                        if (StringUtils.hasText(initialDelayString)) {
404                                Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
405                                if (this.embeddedValueResolver != null) {
406                                        initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
407                                }
408                                if (StringUtils.hasLength(initialDelayString)) {
409                                        try {
410                                                initialDelay = parseDelayAsLong(initialDelayString);
411                                        }
412                                        catch (RuntimeException ex) {
413                                                throw new IllegalArgumentException(
414                                                                "Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
415                                        }
416                                }
417                        }
418
419                        // Check cron expression
420                        String cron = scheduled.cron();
421                        if (StringUtils.hasText(cron)) {
422                                String zone = scheduled.zone();
423                                if (this.embeddedValueResolver != null) {
424                                        cron = this.embeddedValueResolver.resolveStringValue(cron);
425                                        zone = this.embeddedValueResolver.resolveStringValue(zone);
426                                }
427                                if (StringUtils.hasLength(cron)) {
428                                        Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
429                                        processedSchedule = true;
430                                        if (!Scheduled.CRON_DISABLED.equals(cron)) {
431                                                TimeZone timeZone;
432                                                if (StringUtils.hasText(zone)) {
433                                                        timeZone = StringUtils.parseTimeZoneString(zone);
434                                                }
435                                                else {
436                                                        timeZone = TimeZone.getDefault();
437                                                }
438                                                tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
439                                        }
440                                }
441                        }
442
443                        // At this point we don't need to differentiate between initial delay set or not anymore
444                        if (initialDelay < 0) {
445                                initialDelay = 0;
446                        }
447
448                        // Check fixed delay
449                        long fixedDelay = scheduled.fixedDelay();
450                        if (fixedDelay >= 0) {
451                                Assert.isTrue(!processedSchedule, errorMessage);
452                                processedSchedule = true;
453                                tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
454                        }
455                        String fixedDelayString = scheduled.fixedDelayString();
456                        if (StringUtils.hasText(fixedDelayString)) {
457                                if (this.embeddedValueResolver != null) {
458                                        fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
459                                }
460                                if (StringUtils.hasLength(fixedDelayString)) {
461                                        Assert.isTrue(!processedSchedule, errorMessage);
462                                        processedSchedule = true;
463                                        try {
464                                                fixedDelay = parseDelayAsLong(fixedDelayString);
465                                        }
466                                        catch (RuntimeException ex) {
467                                                throw new IllegalArgumentException(
468                                                                "Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
469                                        }
470                                        tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
471                                }
472                        }
473
474                        // Check fixed rate
475                        long fixedRate = scheduled.fixedRate();
476                        if (fixedRate >= 0) {
477                                Assert.isTrue(!processedSchedule, errorMessage);
478                                processedSchedule = true;
479                                tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
480                        }
481                        String fixedRateString = scheduled.fixedRateString();
482                        if (StringUtils.hasText(fixedRateString)) {
483                                if (this.embeddedValueResolver != null) {
484                                        fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
485                                }
486                                if (StringUtils.hasLength(fixedRateString)) {
487                                        Assert.isTrue(!processedSchedule, errorMessage);
488                                        processedSchedule = true;
489                                        try {
490                                                fixedRate = parseDelayAsLong(fixedRateString);
491                                        }
492                                        catch (RuntimeException ex) {
493                                                throw new IllegalArgumentException(
494                                                                "Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
495                                        }
496                                        tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
497                                }
498                        }
499
500                        // Check whether we had any attribute set
501                        Assert.isTrue(processedSchedule, errorMessage);
502
503                        // Finally register the scheduled tasks
504                        synchronized (this.scheduledTasks) {
505                                Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
506                                regTasks.addAll(tasks);
507                        }
508                }
509                catch (IllegalArgumentException ex) {
510                        throw new IllegalStateException(
511                                        "Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
512                }
513        }
514
515        /**
516         * Create a {@link Runnable} for the given bean instance,
517         * calling the specified scheduled method.
518         * <p>The default implementation creates a {@link ScheduledMethodRunnable}.
519         * @param target the target bean instance
520         * @param method the scheduled method to call
521         * @since 5.1
522         * @see ScheduledMethodRunnable#ScheduledMethodRunnable(Object, Method)
523         */
524        protected Runnable createRunnable(Object target, Method method) {
525                Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated with @Scheduled");
526                Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
527                return new ScheduledMethodRunnable(target, invocableMethod);
528        }
529
530        private static long parseDelayAsLong(String value) throws RuntimeException {
531                if (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))) {
532                        return Duration.parse(value).toMillis();
533                }
534                return Long.parseLong(value);
535        }
536
537        private static boolean isP(char ch) {
538                return (ch == 'P' || ch == 'p');
539        }
540
541
542        /**
543         * Return all currently scheduled tasks, from {@link Scheduled} methods
544         * as well as from programmatic {@link SchedulingConfigurer} interaction.
545         * @since 5.0.2
546         */
547        @Override
548        public Set<ScheduledTask> getScheduledTasks() {
549                Set<ScheduledTask> result = new LinkedHashSet<>();
550                synchronized (this.scheduledTasks) {
551                        Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
552                        for (Set<ScheduledTask> tasks : allTasks) {
553                                result.addAll(tasks);
554                        }
555                }
556                result.addAll(this.registrar.getScheduledTasks());
557                return result;
558        }
559
560        @Override
561        public void postProcessBeforeDestruction(Object bean, String beanName) {
562                Set<ScheduledTask> tasks;
563                synchronized (this.scheduledTasks) {
564                        tasks = this.scheduledTasks.remove(bean);
565                }
566                if (tasks != null) {
567                        for (ScheduledTask task : tasks) {
568                                task.cancel();
569                        }
570                }
571        }
572
573        @Override
574        public boolean requiresDestruction(Object bean) {
575                synchronized (this.scheduledTasks) {
576                        return this.scheduledTasks.containsKey(bean);
577                }
578        }
579
580        @Override
581        public void destroy() {
582                synchronized (this.scheduledTasks) {
583                        Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
584                        for (Set<ScheduledTask> tasks : allTasks) {
585                                for (ScheduledTask task : tasks) {
586                                        task.cancel();
587                                }
588                        }
589                        this.scheduledTasks.clear();
590                }
591                this.registrar.destroy();
592        }
593
594}