Порядок уничтожения бинов в контекстной иерархии Spring
Правильно ли говорить, что когда иерархия контекста Spring закрыта, не существует гарантированного порядка, в котором компоненты будут уничтожены? Например, бины в дочернем контексте будут уничтожены до родительского контекста. Из минимального примера разрушение контекстов кажется совершенно не скоординированным между контекстами (как ни странно). В обоих контекстах регистрируется хук отключения, который в дальнейшем будет выполняться в разных потоках.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
@ContextConfiguration(classes = {ATest.Root.class}),
@ContextConfiguration(classes = {ATest.Child.class})
})
public class ATest {
@Test
public void contextTest() {
}
public static class Root {
@Bean
Foo foo() {
return new Foo();
}
}
public static class Child {
@Bean
Bar bar() {
return new Bar();
}
}
static class Foo {
Logger logger = LoggerFactory.getLogger(Foo.class);
volatile boolean destroyed;
@PostConstruct
void setup() {
logger.info("foo setup");
}
@PreDestroy
void destroy() {
destroyed = true;
logger.info("foo destroy");
}
}
static class Bar {
@Autowired
Foo foo;
Logger logger = LoggerFactory.getLogger(Bar.class);
@PostConstruct
void setup() {
logger.info("bar setup with foo = {}", foo);
}
@PreDestroy
void destroy() {
logger.info("bar destroy, foo is destroyed={}", foo.destroyed);
}
}
}
Дает вывод:
21:38:53.287 [Test worker] INFO ATest$Foo - foo setup
21:38:53.327 [Test worker] INFO ATest$Bar - bar setup with foo = com.tango.citrine.spring.ATest$Foo@2458117b
21:38:53.363 [Thread-4] INFO ATest$Foo - foo destroy
21:38:53.364 [Thread-5] INFO ATest$Bar - bar destroy, foo is destroyed=true
Есть ли способ заставить контексты быть закрыты в "правильном" порядке?
1 ответ
Я только что выкопал в той же самой проблеме сам, и все больше не выглядит странным. Хотя я все еще хотел, чтобы все было иначе.
Когда у вас есть родительский и дочерний контексты Spring, родитель ничего не знает о дочернем элементе. Вот как спроектирован Spring, и это верно для всех настроек.
Теперь могут быть некоторые различия
Веб-приложение в контейнере сервлетов
Наиболее распространенной настройкой для этого случая (не считая настройки одного контекста) является объявление корневого контекста с помощью ContextLoaderListener
и дочерний контекст с помощью DispatcherServlet
,
Когда веб-приложение (или контейнер) закрывается как ContextLoaderListener
а также DispatcherServlet
получать уведомления через ServletContextListener.contextDestroyed(...)
а также Servlet.destroy()
соответственно.
Согласно Javadoc, сначала сервлеты и фильтры уничтожаются и только после того, как они сделаны ServletContextListener
это.
Таким образом, в веб-приложениях, запущенных в контейнере сервлетов DispatcherServlet
контекст (который является дочерним) сначала уничтожается, и только затем корневой контекст уничтожается.
Автономное веб-приложение
Следующее в равной степени верно не только для автономных приложений, но и для любых автономных приложений Spring, использующих иерархический контекст.
Поскольку контейнера нет, приложению stanadlone необходимо связаться с самой JVM, чтобы получить сигнал отключения и обработать его. Это делается с помощью механизма отключения крючков.
Spring не пытается определить среду, в которой он работает, за исключением возможностей \ версии JVM (но Spring Boot может отлично справиться с автоматическим определением env).
Поэтому, чтобы Spring зарегистрировал перехватчик завершения работы, вы должны сказать это, когда создаете контекст ( javadoc). Если вы этого не сделаете, у вас не будет @PreDestroy
/DisposableBean
обратные вызовы, вызываемые на всех.
Как только вы зарегистрируете ловушку отключения контекста в JVM, он будет уведомлен и будет правильно обрабатывать завершение работы для этого контекста.
Если у вас есть родительско-дочерние контексты, вы можете захотеть .registerShutdownHook()
для каждого из них. Это будет работать в некоторых случаях. Но JVM вызывает перехватчики отключения в недетерминированном порядке, так что, к сожалению, это не решает проблему.
Теперь, что мы могли об этом
Вероятно, самым простым (хотя и не самым элегантным) решением было бы иметь ApplicationListener<ContextClosedEvent>
или же DisposableBean
сидя в родительском контексте и
- имея ссылку [s] на дочерний контекст [s]
- хранение bean-компонентов из родительского контекста, которые являются критическими для дочернего контекста (например, db-соединения), так что они сохраняются до тех пор, пока дочерний контекст не активен
@Autowire
или же@DependsOn
или с xml аналогами) - закрытие дочернего контекста [s] до закрытия родительского контекста
JUnit тесты
Оригинальный вопрос представлен тестом JUnit. Я на самом деле не так много копал, но подозреваю, что с этим снова все по-другому. Так как это SpringJUnit4ClassRunner
который управляет ими всеми и прежде всего контекстными иерархиями. С другой стороны, тесты JUnit имеют четко определенный и управляемый жизненный цикл (в значительной степени контейнер сервлетов со списком).
В любом случае, поняв внутреннюю работу, я полагаю, вам будет легко решить эту проблему:)