生命周期界面在春季如何工作?什么是“顶级单例豆”?使用 TestContext 框架测试服务

2022-09-02 13:55:56

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

My 对 Bean 的描述如下:LifecycleBeanTest.xml

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

所以它看起来足够“顶级”和“单调”。

这是什么意思?如何让Spring知道我的豆子实现并用它做点什么?Lifecycle

假设我的主要方法在春季看起来紧随其后

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。与春天本身挂钩是很有趣的。


答案 1

您应该使用 代替 。只有前者按预期工作。确保在实现中返回 true。SmartLifecycleLifecycleLifecycleisRunning()

我用于异步作业,听起来像是为它设计的。我想它会为你工作,但同时你可能会看看和事件,如.SmartLifecycleApplicationListenerContextStoppedEvent


答案 2

您可以检查方法,并看到Spring开发人员没有提供应用程序上下文关闭的中断AbstractApplicationContext.doClose()

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一起使用,我认为您可以使用它来测试实现生命周期的服务,我使用了内部Spring测试中的技术

稍微修改了生命周期Bean(我添加了方法):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();
    }
}

测试上下文.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 用于从 getBeanNamesForType javadoc 检索实现 Lifecycle 的 Bean 列表:beanFactory.getBeanNamesForType(Lifecycle.class, false, false);

注意:此方法仅自省顶级 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在这里是可用的,因为它以正确的顺序调度事件。