Как работает интерфейс 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.