Skip to content

Commit ab478d1

Browse files
committed
ScheduledAnnotationBeanPostProcessor tracks individual bean instances of any scope
Issue: SPR-12216 Issue: SPR-12872
1 parent 162aedc commit ab478d1

File tree

3 files changed

+221
-37
lines changed

3 files changed

+221
-37
lines changed

spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package org.springframework.scheduling.annotation;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.Collection;
2021
import java.util.Collections;
22+
import java.util.LinkedHashSet;
2123
import java.util.Map;
2224
import java.util.Set;
2325
import java.util.TimeZone;
@@ -35,7 +37,7 @@
3537
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3638
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
3739
import org.springframework.beans.factory.SmartInitializingSingleton;
38-
import org.springframework.beans.factory.config.BeanPostProcessor;
40+
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
3941
import org.springframework.context.ApplicationContext;
4042
import org.springframework.context.ApplicationContextAware;
4143
import org.springframework.context.ApplicationListener;
@@ -48,6 +50,7 @@
4850
import org.springframework.scheduling.Trigger;
4951
import org.springframework.scheduling.config.CronTask;
5052
import org.springframework.scheduling.config.IntervalTask;
53+
import org.springframework.scheduling.config.ScheduledTask;
5154
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
5255
import org.springframework.scheduling.support.CronTrigger;
5356
import org.springframework.scheduling.support.ScheduledMethodRunnable;
@@ -81,8 +84,8 @@
8184
* @see org.springframework.scheduling.config.ScheduledTaskRegistrar
8285
* @see AsyncAnnotationBeanPostProcessor
8386
*/
84-
public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered,
85-
EmbeddedValueResolverAware, BeanFactoryAware, ApplicationContextAware,
87+
public class ScheduledAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor,
88+
Ordered, EmbeddedValueResolverAware, BeanFactoryAware, ApplicationContextAware,
8689
SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
8790

8891
/**
@@ -109,6 +112,9 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
109112
private final Set<Class<?>> nonAnnotatedClasses =
110113
Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));
111114

115+
private final Map<Object, Set<ScheduledTask>> scheduledTasks =
116+
new ConcurrentHashMap<Object, Set<ScheduledTask>>(16);
117+
112118

113119
@Override
114120
public int getOrder() {
@@ -296,6 +302,9 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
296302
String errorMessage =
297303
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
298304

305+
Set<ScheduledTask> tasks =
306+
new LinkedHashSet<ScheduledTask>(4);
307+
299308
// Determine initial delay
300309
long initialDelay = scheduled.initialDelay();
301310
String initialDelayString = scheduled.initialDelayString();
@@ -330,7 +339,7 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
330339
else {
331340
timeZone = TimeZone.getDefault();
332341
}
333-
this.registrar.addCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone)));
342+
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
334343
}
335344

336345
// At this point we don't need to differentiate between initial delay set or not anymore
@@ -343,7 +352,7 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
343352
if (fixedDelay >= 0) {
344353
Assert.isTrue(!processedSchedule, errorMessage);
345354
processedSchedule = true;
346-
this.registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
355+
tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
347356
}
348357
String fixedDelayString = scheduled.fixedDelayString();
349358
if (StringUtils.hasText(fixedDelayString)) {
@@ -359,15 +368,15 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
359368
throw new IllegalArgumentException(
360369
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");
361370
}
362-
this.registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
371+
tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
363372
}
364373

365374
// Check fixed rate
366375
long fixedRate = scheduled.fixedRate();
367376
if (fixedRate >= 0) {
368377
Assert.isTrue(!processedSchedule, errorMessage);
369378
processedSchedule = true;
370-
this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
379+
tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
371380
}
372381
String fixedRateString = scheduled.fixedRateString();
373382
if (StringUtils.hasText(fixedRateString)) {
@@ -383,11 +392,12 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
383392
throw new IllegalArgumentException(
384393
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");
385394
}
386-
this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
395+
tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
387396
}
388397

389398
// Check whether we had any attribute set
390399
Assert.isTrue(processedSchedule, errorMessage);
400+
this.scheduledTasks.put(bean, tasks);
391401
}
392402
catch (IllegalArgumentException ex) {
393403
throw new IllegalStateException(
@@ -396,8 +406,30 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
396406
}
397407

398408

409+
@Override
410+
public void postProcessBeforeDestruction(Object bean, String beanName) {
411+
Set<ScheduledTask> tasks = this.scheduledTasks.remove(bean);
412+
if (tasks != null) {
413+
for (ScheduledTask task : tasks) {
414+
task.cancel();
415+
}
416+
}
417+
}
418+
419+
@Override
420+
public boolean requiresDestruction(Object bean) {
421+
return this.scheduledTasks.containsKey(bean);
422+
}
423+
399424
@Override
400425
public void destroy() {
426+
Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
427+
for (Set<ScheduledTask> tasks : allTasks) {
428+
for (ScheduledTask task : tasks) {
429+
task.cancel();
430+
}
431+
}
432+
this.scheduledTasks.clear();
401433
this.registrar.destroy();
402434
}
403435

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.scheduling.config;
18+
19+
import java.util.concurrent.ScheduledFuture;
20+
21+
/**
22+
* A representation of a scheduled task,
23+
* used as a return value for scheduling methods.
24+
*
25+
* @author Juergen Hoeller
26+
* @since 4.3
27+
* @see ScheduledTaskRegistrar#scheduleTriggerTask
28+
* @see ScheduledTaskRegistrar#scheduleFixedRateTask
29+
*/
30+
public final class ScheduledTask {
31+
32+
volatile ScheduledFuture<?> future;
33+
34+
35+
ScheduledTask() {
36+
}
37+
38+
39+
/**
40+
* Trigger cancellation of this scheduled task.
41+
*/
42+
public void cancel() {
43+
ScheduledFuture<?> future = this.future;
44+
if (future != null) {
45+
future.cancel(true);
46+
}
47+
}
48+
49+
}

0 commit comments

Comments
 (0)