Trigger cancellation on context close for non-managed objects only

Specifically for prototype/scoped beans and FactoryBean-exposed objects.

Closes gh-33009
This commit is contained in:
Juergen Hoeller
2024-06-12 13:31:00 +02:00
parent 261dac87cc
commit 0ff200b2f1
3 changed files with 195 additions and 32 deletions

View File

@@ -44,6 +44,7 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
@@ -155,6 +156,8 @@ public class ScheduledAnnotationBeanPostProcessor
private final Map<Object, List<Runnable>> reactiveSubscriptions = new IdentityHashMap<>(16);
private final Set<Object> manualCancellationOnContextClose = Collections.newSetFromMap(new IdentityHashMap<>(16));
/**
* Create a default {@code ScheduledAnnotationBeanPostProcessor}.
@@ -305,6 +308,12 @@ public class ScheduledAnnotationBeanPostProcessor
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
if ((this.beanFactory != null && !this.beanFactory.isSingleton(beanName)) ||
(this.beanFactory instanceof SingletonBeanRegistry sbr && sbr.containsSingleton(beanName))) {
// Either a prototype/scoped bean or a FactoryBean with a pre-existing managed singleton
// -> trigger manual cancellation when ContextClosedEvent comes in
this.manualCancellationOnContextClose.add(bean);
}
}
}
return bean;
@@ -595,6 +604,18 @@ public class ScheduledAnnotationBeanPostProcessor
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) {
cancelScheduledTasks(bean);
this.manualCancellationOnContextClose.remove(bean);
}
@Override
public boolean requiresDestruction(Object bean) {
synchronized (this.scheduledTasks) {
return (this.scheduledTasks.containsKey(bean) || this.reactiveSubscriptions.containsKey(bean));
}
}
private void cancelScheduledTasks(Object bean) {
Set<ScheduledTask> tasks;
List<Runnable> liveSubscriptions;
synchronized (this.scheduledTasks) {
@@ -613,13 +634,6 @@ public class ScheduledAnnotationBeanPostProcessor
}
}
@Override
public boolean requiresDestruction(Object bean) {
synchronized (this.scheduledTasks) {
return (this.scheduledTasks.containsKey(bean) || this.reactiveSubscriptions.containsKey(bean));
}
}
@Override
public void destroy() {
synchronized (this.scheduledTasks) {
@@ -636,7 +650,10 @@ public class ScheduledAnnotationBeanPostProcessor
liveSubscription.run(); // equivalent to cancelling the subscription
}
}
this.reactiveSubscriptions.clear();
this.manualCancellationOnContextClose.clear();
}
this.registrar.destroy();
if (this.localScheduler != null) {
this.localScheduler.destroy();
@@ -659,15 +676,10 @@ public class ScheduledAnnotationBeanPostProcessor
finishRegistration();
}
else if (event instanceof ContextClosedEvent) {
synchronized (this.scheduledTasks) {
Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
for (Set<ScheduledTask> tasks : allTasks) {
for (ScheduledTask task : tasks) {
// At this early point, let in-progress tasks complete still
task.cancel(false);
}
}
for (Object bean : this.manualCancellationOnContextClose) {
cancelScheduledTasks(bean);
}
this.manualCancellationOnContextClose.clear();
}
}
}