Lifecycle and SmartLifecycle

The lifecycle interfaces let a bean run background work between a start and a stop, and run setup and cleanup at fixed points of the container's life.

Why

There are two kinds of callbacks.

Per bean, around its creation and destruction:

In application code the annotations are usually simpler. The interfaces suit framework classes and hand-written definitions.

Background work, between a start and a stop:

Only singletons take part. A lazy plain Lifecycle bean takes part only once it was created.

Phases

SmartLifecycle.phase orders the beans. Lower phases start first and stop last, so a bean that others need gets a lower phase. The default, SmartLifecycle.defaultPhase, is the highest value: the bean starts after all others and stops before them. Plain Lifecycle beans count as phase 0. A bean's dependencies start before it and its dependents stop before it, whatever their phase. Within one phase, beans start in registration order and stop in reverse order.

Stopping asynchronously

stopWithCallback(callback) stops the bean and calls callback once it has really stopped, for example when a socket is closed. closeAsync() and pauseAsync() wait for the callbacks of one phase before stopping the next, at most 10 seconds per phase by default. The synchronous close() and pause() do not wait.

Pausing

context.pause() stops the running lifecycle beans but keeps every bean; context.restart() starts the auto-startup beans again. A Flutter app calls them when it goes to the background and comes back. A SmartLifecycle bean whose isPauseable is false keeps running during a pause.

The whole order

At startup (refresh()):

  1. each singleton is created in dependency order: constructor, injection, aware callbacks, @PostConstruct, afterPropertiesSet, init method;
  2. afterSingletonsInstantiated on every SmartInitializingSingleton, in registration order;
  3. start() on the auto-startup SmartLifecycle beans, lowest phase first;
  4. ContextRefreshedEvent.

At shutdown (close() or closeAsync()):

  1. ContextClosedEvent;
  2. stop() or stopWithCallback() on the running lifecycle beans, highest phase first;
  3. each singleton is destroyed after the beans that depend on it: @PreDestroy, destroy(), destroy method.

afterPropertiesSet and destroy may return a Future. refreshAsync() and closeAsync() wait for it. The synchronous refresh() fails for an asynchronous init callback; the synchronous close() logs an asynchronous destroy callback and goes on.

Example

import 'dart:async';
import 'dart:io';

import 'package:spring_core_dart/spring_core_dart.dart';

abstract interface class Handler {
  String get topic;
}

// Registers every handler bean once all of them exist.
@Service()
class HandlerRegistry implements SmartInitializingSingleton {
  HandlerRegistry(this._handlers);
  final ObjectProvider<Handler> _handlers;
  final byTopic = <String, Handler>{};

  @override
  void afterSingletonsInstantiated() {
    for (final handler in _handlers) {
      byTopic[handler.topic] = handler;
    }
  }
}

// Connects when the application starts and disconnects at shutdown,
// before the beans in lower phases stop.
@Component()
class FeedClient with SmartLifecycle {
  WebSocket? _socket;

  @override
  int get phase => 10;

  @override
  void start() {
    unawaited(
      WebSocket.connect('wss://feed.example.com').then((s) => _socket = s),
    );
  }

  @override
  void stop() => _socket?.close();

  // closeAsync() waits until the socket is closed.
  @override
  void stopWithCallback(void Function() callback) {
    final socket = _socket;
    _socket = null;
    if (socket == null) {
      callback();
    } else {
      socket.close().whenComplete(callback);
    }
  }

  @override
  bool get isRunning => _socket != null;
}

// Polls every 5 seconds, only after an explicit context.start().
@Component()
class Poller implements Lifecycle {
  Timer? _timer;

  @override
  void start() =>
      _timer = Timer.periodic(const Duration(seconds: 5), (_) => poll());

  @override
  void stop() => _timer?.cancel();

  @override
  bool get isRunning => _timer?.isActive ?? false;

  void poll() {}
}

// A file closed when the application shuts down.
@Repository()
class Journal implements InitializingBean, DisposableBean {
  late final IOSink _sink;

  @override
  void afterPropertiesSet() =>
      _sink = File('journal.log').openWrite(mode: FileMode.append);

  @override
  Future<void> destroy() => _sink.close(); // awaited by closeAsync()
}

To change the shutdown timeouts, declare a lifecycleProcessor bean:

@Configuration()
class LifecycleConfig {
  @Bean()
  LifecycleProcessor lifecycleProcessor(
    ConfigurableListableBeanFactory factory,
  ) => DefaultLifecycleProcessor(
    factory,
    timeoutPerShutdownPhase: const Duration(seconds: 30),
  );
}

Members

Interface Member Meaning
Lifecycle void start() Starts the work. Not called when isRunning is true. An error fails the start as an ApplicationContextException.
void stop() Stops the work. Called only when isRunning is true. An error is logged; the other beans still stop.
bool get isRunning Whether the work is running.
SmartLifecycle bool get isAutoStartup true: started at the end of refresh().
int get phase SmartLifecycle.defaultPhase. Lower starts earlier and stops later.
bool get isPauseable true: stopped by pause().
void stopWithCallback(void Function() callback) Calls stop(), then callback. Override for an asynchronous stop; call callback exactly once.
InitializingBean FutureOr<void> afterPropertiesSet() Runs after @PostConstruct, before the init method.
DisposableBean FutureOr<void> destroy() Runs after @PreDestroy, before the destroy method.
SmartInitializingSingleton void afterSingletonsInstantiated() Runs once at the end of preInstantiateSingletons. Not called for a lazy singleton created later.

SmartLifecycle is a mixin class: with SmartLifecycle gives its defaults. With implements, write every member.

DefaultLifecycleProcessor(beanFactory, {timeoutPerShutdownPhase, timeoutsForShutdownPhases}) takes a timeout per phase (10 seconds by default) and overrides for single phases.

In Spring (Java)

@Component
class FeedClient implements SmartLifecycle {
  public void start() { ... }
  public void stop() { ... }
  public void stop(Runnable callback) { ... }
  public boolean isRunning() { ... }
  public int getPhase() { return 10; }
}

Differences that matter:

See also