簡體   English   中英

Lifecycle界面如何在Spring中運行?什么是“頂級單身豆”?

[英]How does Lifecycle interface work in Spring? What are “top-level singleton beans”?

在Spring javadoc中說,“請注意,Lifecycle接口僅支持頂級單例bean。” 這里的URL

我的LifecycleBeanTest.xml描述bean如下:

<beans ...>
    <bean id="lifecycle" class="tests.LifecycleBean"/>
</beans>

所以它看起來很“熱”和“單調”。

這是什么意思? 如何讓Spring了解我的bean實現Lifecycle並用它做點什么?

假設我的main方法在Spring中看起來如下

public static void main(String[] args) {
    new ClassPathXmlApplicationContext("/tests/LifecycleBeanTest.xml").close();
}

所以,它實例化上下文然后立即關閉它。

我可以在我的配置中創建一些bean,這會延遲close()執行,直到應用程序完成所有工作嗎? 那么主方法線程等待應用程序終止?

例如,以下bean不按我想象的方式工作。 調用start()而不是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本身很有意思。

您應該使用SmartLifecycle而不是Lifecycle 只有前者按預期Lifecycle才能正常工作。 確保在isRunning()實現中返回true。

我已經將SmartLifecycle用於異步作業,聽起來就是這樣。 我想它會對你有用,但同時你可能會看看ApplicationListenerContextStoppedEvent事件。

您可以檢查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's 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,我認為您可以使用它來測試實現Lifecycle的服務,我使用了一個內部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>

PS啟動和停止上下文不是您可能想在同一應用程序上下文中多次執行的操作,因此您可能需要在測試方法上放置@DirtiesContext注釋以獲得最佳結果。

回答問題的新版本

DefaultLifecycleProcessor使用beanFactory.getBeanNamesForType(Lifecycle.class, false, false); 從getBeanNamesForType javadoc檢索實現生命周期的bean列表:

注意:此方法僅對頂級bean進行內省。 check nested beans which might match the specified type as well. 檢查嵌套豆可能匹配指定類型為好。

因此,此方法不會列出內部bean(當只有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()只是由應用程序上下文傳播的事件,它們與應用程序上下文的生命周期無關,例如,您可以使用某些服務bean實現下載管理器 - 當用戶點擊“暫停”按鈕時,您將廣播“停止”事件,然后當用戶點擊“開始”按鈕時,您可以通過廣播“開始”事件來恢復處理。 Spring在這里是可用的,因為它以正確的順序調度事件。

我從未使用Lifecycle接口,我不確定它是如何工作的。 但看起來簡單地在上下文中調用start()調用這些回調:

AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("...");
ctx.start();

但通常我使用@PostConstruct / @PreDestroy注釋或實現InitializingBeanDisposableBean

public class LifecycleBean implements InitializingBean, DisposableBean {

    @Override
    public void afterPropertiesSet() {
        //...
    }

    @Override
    public void destroy() {
        //...
    }

}

注意我沒有在應用程序上下文上調用close() 由於您在LifecycleBean中創建非守護程序線程,因此即使main退出,JVM仍會繼續運行。

當您停止該線程JVM存在但未正確關閉應用程序上下文時。 基本上,最后一個非守護程序線程停止,導致整個JVM終止。 這里有一些hacky解決方法 - 當你的后台非守護程序線程即將完成時,顯式關閉應用程序上下文:

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();
    }

}

所以,最后我發現如果我:

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 bean的所有停止之前,上下文創建代碼不會退出。 因此,此代碼適用於JUnit測試

使用SmartLifecycle怎么樣? 似乎它提供了所有必要的功能。

方法public void stop(Runnable contextStopping){}。 您可以通過在所需的時間內執行contextStopping來繼續關閉應用程序上下文。

在我的環境中,即使在J-UNIT上也可以正常工作,當然也可以使用SpringJUnit4ClassRunner運行它們。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM