java Lifecycle 接口在 Spring 中是如何工作的?什么是“顶级单例 bean”?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13551408/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 13:12:02  来源:igfitidea点击:

How does Lifecycle interface work in Spring? What are "top-level singleton beans"?

javamultithreadingspring

提问by Suzan Cioc

It is said in Spring javadoc, that "Note that the Lifecycle interface is only supported on top-level singleton beans." Here URL

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

My LifecycleBeanTest.xmldescribes bean as follows:

LifecycleBeanTest.xml对 bean 的描述如下:

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

so it looks "topish" and "singletonish" enough.

所以它看起来足够“高贵”和“单一”。

What does it mean? How to make Spring know about my bean implementing Lifecycleand do something with it?

这是什么意思?如何让 Spring 知道我的 bean 实现Lifecycle并用它做一些事情?

Suppose my main method looks following in Spring

假设我的主要方法在 Spring 中如下所示

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

so, it instantiates context and then closes it immediately.

所以,它实例化上下文然后立即关闭它。

May I create some bean in my configuration, which delays close()execution until application do all it's works? So that main method thread wait for application termination?

我可以在我的配置中创建一些 bean,它会延迟close()执行直到应用程序完成它的所有工作吗?那么主方法线程等待应用程序终止?

For example, the following bean does not work in way I thought. Neither start()not stop()is called.

例如,下面的 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();
    }

}

UPDATE 1

更新 1

I know I can wait for bean in code. It is interesting to hook into Spring itself.

我知道我可以在代码中等待 bean。钩入 Spring 本身很有趣。

回答by mrembisz

You should use SmartLifecycleinstead of Lifecycle. Only the former is working as you expected Lifecycleto work. Make sure you return true in your isRunning()implementation.

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

I have used SmartLifecyclefor asynchronous jobs for which it sounds like designed for. I suppose it will work for you but at the same time you may have a look at ApplicationListenerand events like ContextStoppedEvent.

我曾用于SmartLifecycle异步作业,听起来像是专为之设计的。我想它会为您工作,但同时您可能会查看ApplicationListener和事件,例如ContextStoppedEvent.

回答by Boris Treukhov

You can examine AbstractApplicationContext.doClose()method and see that no interruption of application context closing has been provided by the Spring developers

您可以检查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;
        }
    }
}

So you can't prevent the application context from closing.

所以你不能阻止应用程序上下文关闭。

Testing the service with TestContext framework

使用 TestContext 框架测试服务

If you are using Spring test context framework with JUnit, I think you can use it to test services that implement Lifecycle, I used the technique from one of the internal Spring tests

如果您在 JUnit 中使用 Spring 测试上下文框架,我认为您可以使用它来测试实现 Lifecycle 的服务,我使用了内部 Spring 测试之一的技术

Slightly modified LifecycleBean(I've added waitForTermination()method):

稍微修改 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;
        }
    }
}

Test class:

测试类:

@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 content:

测试上下文.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. starting and stopping the context is not a thing you may want to do many times on the same application context, so you may need to put @DirtiesContextannotation on your test methods for the best results.

PS 启动和停止上下文不是您可能希望在同一个应用程序上下文中多次执行的事情,因此您可能需要@DirtiesContext在测试方法上添加注释以获得最佳结果。

Answer to the new version of the question

回答新版问题

DefaultLifecycleProcessor uses beanFactory.getBeanNamesForType(Lifecycle.class, false, false);to retrieve the list of the beans implementing Lifecycle From getBeanNamesForType javadoc:

DefaultLifecycleProcessor 用于beanFactory.getBeanNamesForType(Lifecycle.class, false, false);从 getBeanNamesForType javadoc 检索实现 Lifecycle 的 bean 列表:

NOTE: This method introspects top-level beans only.It does notcheck nested beans which might match the specified type as well.

注意:此方法仅内省顶级 bean。它并 没有检查嵌套豆可能匹配指定类型为好。

So this method does not list the inner beans (they were called nested when only xml configuration was available - they are declared as nested beanxml elements).

所以这个方法没有列出内部 bean(当只有 xml 配置可用时它们被称为嵌套 - 它们被声明为嵌套beanxml 元素)。

Consider the following example from the documentation

考虑文档中的以下示例

<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() and Stop() are merely events that are propagated by the application context they are not connected with lifetime of the application context, for example you can implement a download manager with some service beans - when the user hits "pause" button, you will broadcast the "stop" event, then when the user hits "start" button, you can resume the processing by broadcasting the "start" event. Spring is usable here, because it dispatches events in the proper order.

Start() 和 Stop() 只是由应用程序上下文传播的事件,它们与应用程序上下文的生命周期无关,例如,您可以使用某些服务 bean 实现下载管理器 - 当用户点击“暂停”按钮时,您将广播“停止”事件,然后当用户点击“开始”按钮时,您可以通过广播“开始”事件继续处理。Spring 在这里可用,因为它以正确的顺序分派事件。

回答by Tomasz Nurkiewicz

I never used Lifecycleinterface and I am not sure how it is suppose to work. But it looks like simply calling start()on context calls these callbacks:

我从未使用过Lifecycle界面,我不确定它是如何工作的。但看起来只是调用start()上下文调用这些回调:

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

However typically I use @PostConstruct/@PreDestroyannotations or implement InitializingBeanor DisposableBean:

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

public class LifecycleBean implements InitializingBean, DisposableBean {

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

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

}

Notice I don't call close()on application context. Since you are creating non-daemon thread in LifecycleBeanthe JVM remains running even when mainexits.

注意我没有调用close()应用程序上下文。由于您在LifecycleBeanJVM中创建非守护进程线程,即使main退出时也保持运行。

When you stop that thread JVM exists but does not close application context properly. Basically last non-daemon thread stops, causing the whole JVM to terminate. Here is a bit hacky workaround - when your background non-daemon thread is about to finish, close the application context explicitly:

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

}

回答by Suzan Cioc

So, finally I foundm that if I:

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

1) Define my bean as implements Lifecycle

1)将我的bean定义为 implements Lifecycle

2) Introduce a delay in stop()method like this

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

@Override
public void stop() {
    log.info("Stopping bean");
    //thread.interrupt();
    try {
        thread.join();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return;
    }
}

3) And code context creation as follows:

3)和代码上下文创建如下:

new ClassPathXmlApplicationContext("/tests/LifecycleBeanTest.xml").stop();

Then I get what I want:

然后我得到了我想要的:

context creation code does not exit until all stops of all Lifecycle beans executed. So, this code works in JUnit tests

上下文创建代码直到所有 Lifecycle bean 都执行完毕后才会退出。所以,这段代码适用于 JUnit 测试

回答by Oleksandr_DJ

What about using SmartLifecycle? Seems like it provides all necessary functionality.

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

There is method public void stop(Runnable contextStopping) {}. And you can continue app context closing by executing passed in contextStopping in time you want.

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

In my environment all works fine even on J-UNIT, of course by running them with SpringJUnit4ClassRunner.

在我的环境中,即使在 J-UNIT 上也能正常工作,当然,通过使用 SpringJUnit4ClassRunner 运行它们。