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.