Affects: 5.3.27
When dynamically creating a prototype-scoped bean extending from Lifecycle
using ApplicationContext#getBean(Class<T> requiredType, Object... args)
, its life cycle is not managed. That is, bean is neither started, nor stopped at the application context exit.
Demonstration
- We have an
AliveThing
extending fromSmartLifecycle
- The configuration defines
Queue<String>
and (prototype-scoped)AliveThing
beans AliveThing
is created usingcontext.getBean(AliveThing.class, logger, "horse")
- Created
AliveThing
is neither started, nor stopped afterApplicationContextRunner#run()
completion
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
class AliveThingTest {
@Test
void test() {
AtomicReference<Queue<String>> loggerRef = new AtomicReference<>();
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(AliveThingConfiguration.class))
.run(context -> {
// Get the logger
@SuppressWarnings("unchecked")
Queue<String> logger = (Queue<String>) context
.getBeanProvider(forClassWithGenerics(Queue.class, String.class))
.getObject();
loggerRef.set(logger);
// Create a horse
context.getBean(AliveThing.class, logger, "horse");
// Verify that the horse is started
assertIterableEquals(
logger,
Collections.singletonList("horse started")
);
});
// Verify that the horse is started and stopped
assertIterableEquals(
loggerRef.get(),
Arrays.asList("horse started", "horse stopped"));
}
static final class AliveThing implements SmartLifecycle {
private volatile boolean running = false;
private final Queue<String> logger;
private final String name;
private AliveThing(Queue<String> logger, String name) {
this.logger = logger;
this.name = name;
}
@Override
public synchronized void start() {
if (!running) {
logger.add(name + " started");
running = true;
}
}
@Override
public synchronized void stop() {
if (running) {
logger.add(name + " stopped");
running = false;
}
}
@Override
public boolean isRunning() {
return running;
}
}
@AutoConfiguration
static class AliveThingConfiguration {
@Bean
Queue<String> logger() {
return new LinkedList<>();
}
@Bean
@Scope("prototype")
AliveThing aliveThing(Queue<String> logger, String name) {
return new AliveThing(logger, name);
}
}
}
Comment From: quaff
https://github.com/spring-projects/spring-framework/blob/7d2047cdbd6f440cd2ce638947795c81c537db28/spring-context/src/main/java/org/springframework/context/Lifecycle.java#L37-L41
Comment From: vy
@quaff, thanks so much for clarifying the issue.