Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 describes bean as follows:

<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 Lifecycle and do something with it?

Suppose my main method looks following in 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?

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

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

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

like image 433
Suzan Cioc Avatar asked Nov 25 '12 13:11

Suzan Cioc


People also ask

How does Spring bean life cycle work?

Bean life cycle is managed by the spring container. When we run the program then, first of all, the spring container gets started. After that, the container creates the instance of a bean as per the request, and then dependencies are injected. And finally, the bean is destroyed when the spring container is closed.

What are the 2 important bean lifecycle methods in Spring?

There are two ways of beginning and ending the spring bean lifecycle. If InitializingBean and DisposableBean interfaces are used, it binds the code to Spring. A better way is to identify the init-method and destroy-method in the bean configuration file.

Which interface is responsible for managing Spring beans?

beans and org. springframework. context packages are the basis for Spring Framework's IoC container. The BeanFactory interface provides an advanced configuration mechanism capable of managing any type of object.


2 Answers

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.

like image 140
mrembisz Avatar answered Sep 18 '22 09:09

mrembisz


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.

like image 31
Boris Treukhov Avatar answered Sep 18 '22 09:09

Boris Treukhov