Как работает интерфейс Lifecycle в Spring? Что такое "синглет верхнего уровня beans"?
В Spring javadoc сказано: "Обратите внимание, что интерфейс Lifecycle поддерживается только на одноэлементном уровне beans. Здесь URL
My LifecycleBeanTest.xml
описывает bean следующим образом:
<beans ...>
<bean id="lifecycle" class="tests.LifecycleBean"/>
</beans>
поэтому он выглядит "топическим" и "однотонным".
Что это значит? Как сделать Spring знать о моем bean реализации Lifecycle
и что-то сделать с ним?
Предположим, что мой основной метод выглядит следующим образом в Spring
public static void main(String[] args) {
new ClassPathXmlApplicationContext("/tests/LifecycleBeanTest.xml").close();
}
поэтому он создает контекст и немедленно закрывает его.
Можно ли создать в моей конфигурации несколько bean, что задерживает выполнение close()
до тех пор, пока приложение не выполнит все действия? Так что основной поток метода ждет завершения приложения?
Например, следующий bean не работает так, как я думал. Не вызывается start()
not stop()
.
package tests;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.Lifecycle;
public class LifecycleBean implements Lifecycle {
private static final Logger log = LoggerFactory.getLogger(LifecycleBean.class);
private final Thread thread = new Thread("Lifecycle") {
{
setDaemon(false);
setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("Abnormal thread termination", e);
}
});
}
public void run() {
for(int i=0; i<10 && !isInterrupted(); ++i) {
log.info("Hearbeat {}", i);
try {
sleep(1000);
} catch (InterruptedException e) {
return;
}
}
};
};
@Override
public void start() {
log.info("Starting bean");
thread.start();
}
@Override
public void stop() {
log.info("Stopping bean");
thread.interrupt();
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
@Override
public boolean isRunning() {
return thread.isAlive();
}
}
ОБНОВЛЕНИЕ 1
Я знаю, что могу ждать bean в коде. Интересно подключиться к самому Spring.
Ответы
Ответ 1
Вы должны использовать SmartLifecycle
вместо Lifecycle
. Только первый работает так, как вы ожидали Lifecycle
. Убедитесь, что вы вернули true в своей реализации isRunning()
.
Я использовал SmartLifecycle
для асинхронных заданий, для которых он звучит как предназначенный для. Я полагаю, это сработает для вас, но в то же время вы можете взглянуть на ApplicationListener
и такие события, как ContextStoppedEvent
.
Ответ 2
Вы можете изучить метод AbstractApplicationContext.doClose()
и увидеть, что не было прекращено закрытие контекста приложения разработчиками Spring
protected void doClose() {
boolean actuallyClose;
synchronized (this.activeMonitor) {
actuallyClose = this.active && !this.closed;
this.closed = true;
}
if (actuallyClose) {
if (logger.isInfoEnabled()) {
logger.info("Closing " + this);
}
try {
// Publish shutdown event.
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// Stop all Lifecycle beans, to avoid delays during individual destruction.
try {
getLifecycleProcessor().onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
// Destroy all cached singletons in the context BeanFactory.
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// Let subclasses do some final clean-up if they wish...
onClose();
synchronized (this.activeMonitor) {
this.active = false;
}
}
}
Таким образом, вы не можете запретить закрытие контекста приложения.
Тестирование службы с помощью платформы TestContext
Если вы используете Spring контекст тест рамки с JUnit, я думаю, что вы можете использовать его для тестирования услуг, которые реализуют жизненный цикл, я использовал технику из один внутренних тестов Spring
Немного измененный LifecycleBean (я добавил метод waitForTermination()
):
public class LifecycleBean implements Lifecycle {
private static final Logger log = LoggerFactory
.getLogger(LifecycleBean.class);
private final Thread thread = new Thread("Lifecycle") {
{
setDaemon(false);
setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("Abnormal thread termination", e);
}
});
}
public void run() {
for (int i = 0; i < 10 && !isInterrupted(); ++i) {
log.info("Hearbeat {}", i);
try {
sleep(1000);
} catch (InterruptedException e) {
return;
}
}
};
};
@Override
public void start() {
log.info("Starting bean");
thread.start();
}
@Override
public void stop() {
log.info("Stopping bean");
thread.interrupt();
waitForTermination();
}
@Override
public boolean isRunning() {
return thread.isAlive();
}
public void waitForTermination() {
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
Класс тестирования:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:Test-context.xml")
public class LifecycleBeanTest {
@Autowired
LifecycleBean bean;
Lifecycle appContextLifeCycle;
@Autowired
public void setLifeCycle(ApplicationContext context){
this.appContextLifeCycle = (Lifecycle)context;
}
@Test
public void testLifeCycle(){
//"start" application context
appContextLifeCycle.start();
bean.waitForTermination();
}
}
Содержимое Test-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="LifecycleBean"/>
</beans>
P.S. запуск и остановка контекста - это не то, что вы можете делать много раз в одном и том же контексте приложения, поэтому вам может потребоваться добавить аннотацию @DirtiesContext
для ваших тестовых методов для получения наилучших результатов.
Ответьте на новую версию вопроса
DefaultLifecycleProcessor использует beanFactory.getBeanNamesForType(Lifecycle.class, false, false);
для извлечения списка beans реализации Lifecycle
Из getBeanNamesForType javadoc:
ПРИМЕЧАНИЕ. Этот метод рассматривает только верхний уровень beans. Он делает not проверить вложенный beans, который может соответствовать указанному типу также.
Таким образом, этот метод не отображает внутренний beans (они назывались вложенными, когда была доступна только конфигурация xml - они объявлены как вложенные элементы bean xml).
Рассмотрим следующий пример из документации
<bean id="outer" class="...">
<!-- Instead of using a reference to target, just use an inner bean -->
<property name="target">
<bean class="com.mycompany.PersonImpl">
<property name="name"><value>Tony</value></property>
<property name="age"><value>51</value></property>
</bean>
</property>
</bean>
Start() и Stop() - это просто события, которые распространяются в контексте приложения, они не связаны со временем жизни контекста приложения, например, вы можете реализовать диспетчер загрузки с некоторой службой beans - когда пользователь нажимает кнопку "пауза", вы будете транслировать событие "stop", а затем, когда пользователь нажимает кнопку "начать", вы можете возобновить обработку, передавая событие "start". Spring можно использовать здесь, поскольку он отправляет события в правильном порядке.
Ответ 3
Я никогда не использовал Lifecycle
интерфейс, и я не уверен, как он должен работать. Но похоже, что просто вызов start()
в контекст вызывает эти обратные вызовы:
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("...");
ctx.start();
Как правило, я использую аннотации @PostConstruct
/@PreDestroy
или реализую InitializingBean
или DisposableBean
:
public class LifecycleBean implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() {
//...
}
@Override
public void destroy() {
//...
}
}
Примечание. Я не вызываю close()
в контексте приложения. Поскольку вы создаете поток non-daemon в LifecycleBean
, JVM остается включенным, даже если main
завершает работу.
Когда вы останавливаете этот поток JVM, но не закрываете контекст приложения должным образом. В основном последний поток не-демона останавливается, что приводит к завершению работы всего JVM. Вот несколько хакерских решений - когда ваш фоновый поток не-daemon близок к завершению, явное закрытие контекста приложения:
public class LifecycleBean implements ApplicationContextAware /* ... */ {
private AbstractApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = (AbstractApplicationContext)applicationContext;
}
public void run() {
for(int i=0; i<10 && !isInterrupted(); ++i) {
log.info("Hearbeat {}", i);
try {
sleep(1000);
} catch (InterruptedException e) {
}
}
applicationContext.close();
}
}
Ответ 4
Итак, наконец, я нашел, что если I:
1) Определите мой bean как implements Lifecycle
2) Внесите задержку в метод stop()
, подобный этому
@Override
public void stop() {
log.info("Stopping bean");
//thread.interrupt();
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
3) И создание контекста кода следующим образом:
new ClassPathXmlApplicationContext("/tests/LifecycleBeanTest.xml").stop();
Затем я получаю то, что хочу:
код создания контекста не завершается до тех пор, пока не будут выполнены все остановки всего Lifecycle beans. Таким образом, этот код работает в тестах JUnit
Ответ 5
Как насчет использования SmartLifecycle? Похоже, что он обеспечивает все необходимые функции.
Существует метод public void stop (Runnable contextStopping) {}.
И вы можете продолжить закрытие контекста приложения, выполнив переданный в contextStopping по времени, который вы хотите.
В моей среде все работает нормально даже на J-UNIT, конечно, запустив их с помощью SpringJUnit4ClassRunner.