From 9d9e621efe06eedfad3735d3669a2e8b11dd9b32 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 29 Jul 2024 10:47:08 +0200 Subject: [PATCH] Defensive singleton check for non-registered bean Closes gh-33286 --- .../ScheduledAnnotationBeanPostProcessor.java | 5 +++-- ...duledAnnotationBeanPostProcessorTests.java | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java index 8d3b9d735da0..78701f901f5e 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java @@ -308,8 +308,9 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { 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))) { + if ((this.beanFactory != null && + (!this.beanFactory.containsBean(beanName) || !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); diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java index 65626d56e1fb..179a2cbfa332 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.AfterEach; @@ -294,6 +295,24 @@ void oneTimeTask() { assertThat(task.getInitialDelayDuration()).isEqualTo(Duration.ofMillis(2_000L)); } + @Test + void oneTimeTaskOnNonRegisteredBean() { + BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); + context.registerBeanDefinition("postProcessor", processorDefinition); + context.refresh(); + + ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); + assertThat(postProcessor.getScheduledTasks()).hasSize(0); + + Object target = context.getAutowireCapableBeanFactory().createBean(OneTimeTaskBean.class); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); + @SuppressWarnings("unchecked") + Set manualTasks = (Set) + new DirectFieldAccessor(postProcessor).getPropertyValue("manualCancellationOnContextClose"); + assertThat(manualTasks).hasSize(1); + assertThat(manualTasks).contains(target); + } + @Test void cronTask() { BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);