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

  1. We have an AliveThing extending from SmartLifecycle
  2. The configuration defines Queue<String> and (prototype-scoped) AliveThing beans
  3. AliveThing is created using context.getBean(AliveThing.class, logger, "horse")
  4. Created AliveThing is neither started, nor stopped after ApplicationContextRunner#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.