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

前端 未结 5 1600
無奈伤痛
無奈伤痛 2021-02-07 05:13

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

My LifecycleBeanTest.xml d

相关标签:
5条回答
  • 2021-02-07 05:46

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

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

    However typically I use @PostConstruct/@PreDestroy annotations or implement InitializingBean or DisposableBean:

    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 LifecycleBean the JVM remains running even when main exits.

    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:

    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();
        }
    
    }
    
    0 讨论(0)
  • 2021-02-07 05:53

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

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

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

    0 讨论(0)
  • 2021-02-07 05:56

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

    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

    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

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

    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 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.

    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:

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

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

    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.

    0 讨论(0)
  • 2021-02-07 05:59

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

    I have used SmartLifecycle for 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 ApplicationListener and events like ContextStoppedEvent.

    0 讨论(0)
  • 2021-02-07 06:00

    So, finally I foundm that if I:

    1) Define my bean as implements Lifecycle

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

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

    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

    0 讨论(0)
提交回复
热议问题