Affects: Spring Framework 5.3.8


Having this configuration:

@Configuration
public class WindowConfiguration {

    @Bean
    Closeable closeableContext(ConfigurableApplicationContext context) {
        return context;
    }

    @Bean
    Window window(Closeable closeableContext) {
        return new Window(closeableContext);
    }

    static class Window implements DisposableBean, InitializingBean {

        private final Closeable closeableContext;

        Window(Closeable closeableContext) {
            this.closeableContext = closeableContext;
        }

        public void closeWindow() {
            try {
                closeableContext.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }

        @Override
        public void destroy() throws Exception {
            System.out.println("Window closed");
        }

        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("Window opened");
        }
    }
}

this throws:

var context = new AnnotationConfigApplicationContext(WindowConfiguration.class);
var window = context.getBean(WindowConfiguration.Window.class);
window.closeWindow();

Looks like the problem is in DefaultLifecycleProcessor#doStop when the bean in question is the ConfigurableApplicationContext (the AbstractApplicationContext implementation) https://github.com/spring-projects/spring-framework/blob/b595dc1dfad9db534ca7b9e8f46bb9926b88ab5a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java#L247

AbstractApplicationContext#stop() will try to stop DefaultLifecycleProcessor itself https://github.com/spring-projects/spring-framework/blob/b595dc1dfad9db534ca7b9e8f46bb9926b88ab5a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L1426-L1429

Problem is DefaultLifecycleProcessor doesn't set the flag running as false before stoping the beans, and thus creating an infinite loop (AbstractApplicationContext#isRunning() still returns true on next doStop pass in DefaultLifecycleProcessor) https://github.com/spring-projects/spring-framework/blob/b595dc1dfad9db534ca7b9e8f46bb9926b88ab5a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java#L116-L119

Comment From: quaff

I think it's reasonable, why would you do that?

Comment From: criske

Let's say Window implements a UI WindowListener. You want to close the window context child when a "closed" WindowEvent is received.

Comment From: xinkeng0

Do not create the ConfigurableApplicationContext bean repeatedly. I tried this successfully

    //@Bean
    //Closeable closeableContext(ConfigurableApplicationContext context) {
    //    return context;
    //}

    @Bean
    Window window(ConfigurableApplicationContext  closeableContext) {
        return new Window(closeableContext);
    }

Comment From: criske

Do not create the ConfigurableApplicationContext bean repeatedly.

@gengxiaoxiaoxin Thanks for the input, but care to explain this?

I tried this successfully ...

While your aproach is fairly obvious, this won't work if Window is registered via component scanning. Let's say Window component is decoupled from spring with jsr-330 and 250. This will be a valid component, but Closeable will fail to be autowired:

@Named
class Window {
   //..
  @Inject
  public Window(Closeable closeableContext) {
     this.closeableContext = closeableContext;
  }
  @PostConstruct
  //...
  @PreDestroy
  //...
}

The only working solution is to register the ConfigurableApplicationContext not as bean but as a resolvable dependency otherwise it will be picked by DefaultLifecycleProcessor

var context = new GenericApplicationContext();
var reader = new AnnotatedBeanDefinitionReader(context);
reader.register(WindowConfiguration.class);
context.getBeanFactory().registerResolvableDependency(Closeable.class, context);
context.refresh();

var window = context.getBean(Window.class);
window.closeWindow();

But anyway this ticket was opened to report a bug that appears in a certain condition and not to solve a particular problem (for that there is Stackoverflow).

Comment From: xinkeng0

While your aproach is fairly obvious, this won't work if is registered via component scanning. Let's say component is decoupled from spring with jsr-330 and 250. This will be a valid component, but will fail to be autowired:WindowWindowCloseable

@criske @Inject cannot to be autowired a interface by default,maybe you can try qualify the class name.

@Inject
Window(AnnotationConfigApplicationContext closeableContext) {
    this.closeableContext = closeableContext;
}

Still working

var context = new AnnotationConfigApplicationContext(WindowConfiguration.class);
var window = context.getBean(WindowConfiguration.Window.class);
window.closeWindow();

Comment From: snicoll

But anyway this ticket was opened to report a bug that appears in a certain condition

Exposing the application context as a closeable bean is indeed strange and you should rather call the usual close method to close it. I don't see anything actionable here but if you share a small sample that reproduce the condition you've shared and doesn't expose the context as a bean, we can reopen.