19

在 Spring javadoc 中说,“请注意,Lifecycle 接口仅在顶级单例 bean 上受支持。” 这里网址

LifecycleBeanTest.xml对 bean 的描述如下:

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

所以它看起来足够“topish”和“singletonish”。

这是什么意思?如何让 Spring 了解我的 bean 实现Lifecycle并对其进行处理?

假设我的主要方法在 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 本身挂钩很有趣。

4

5 回答 5

4

您应该使用SmartLifecycle而不是Lifecycle. 只有前者按您预期Lifecycle的方式工作。确保在isRunning()实现中返回 true。

我已经用于SmartLifecycle异步作业,这听起来像是为之设计的。我想它会为你工作,但同时你可能会看看ApplicationListenerContextStoppedEvent.

于 2012-11-25T18:08:21.470 回答
4

您可以检查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 框架测试服务

如果您使用带有 JUnit 的 Spring 测试上下文框架,我认为您可以使用它来测试实现 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 中检索实现 Lifecycle 的 bean 列表:

注意:此方法仅内省顶级 bean。它不 检查也可能与指定类型匹配的嵌套 bean。

所以这个方法没有列出内部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 在这里可用,因为它以正确的顺序调度事件。

于 2012-11-25T20:49:02.697 回答
3

我从未使用过Lifecycle界面,我不确定它应该如何工作。但它看起来像简单地调用start()上下文调用这些回调:

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

但是通常我使用@PostConstruct/@PreDestroy注释或实现InitializingBeanor DisposableBean

public class LifecycleBean implements InitializingBean, DisposableBean {

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

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

}

请注意,我不调用close()应用程序上下文。由于您在 JVM 中创建非守护线程,LifecycleBean因此即使main退出也仍然运行。

当您停止该线程时,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();
    }

}
于 2012-11-25T13:57:59.390 回答
0

所以,最后我发现如果我:

1)将我的bean定义为implements Lifecycle

stop()2)在这样的方法中引入延迟

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

然后我得到我想要的:

上下文创建代码在所有生命周期 bean 执行完毕后才会退出。因此,此代码适用于 JUnit 测试

于 2012-11-26T12:03:00.270 回答
0

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

有方法 public void stop(Runnable contextStopping) {}。您可以通过在您想要的时间执行传入的 contextStopping 来继续关闭应用程序上下文。

在我的环境中,即使在 J-UNIT 上也一切正常,当然是通过 SpringJUnit4ClassRunner 运行它们。

于 2014-10-14T16:07:31.370 回答