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

- `InitializingBean.afterPropertiesSet()` runs once the bean is injected. [@PostConstruct](post-construct.md) does the same without an interface.
- `DisposableBean.destroy()` runs when the bean is destroyed. [@PreDestroy](pre-destroy.md) does the same without an interface.
- `SmartInitializingSingleton.afterSingletonsInstantiated()` runs once every non-lazy singleton exists. Use it for work that needs the other beans, such as registering every handler in a registry.

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:

- `SmartLifecycle` is the usual choice: a client that connects at startup, a scheduler, a server. The context starts it at the end of `refresh()` and stops it at shutdown.
- `Lifecycle` is the plain form. It is not started at refresh, only by an explicit `context.start()`, or when an auto-startup bean depends on it. It is still stopped by `stop()`, `pause()` and `close()`.

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

```dart
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:

```dart
@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)

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

- `stop(Runnable)` is `stopWithCallback`. Dart has no overloads.
- Dart cannot block. The synchronous `close()` does not wait for stop callbacks; `closeAsync()` does.
- `afterPropertiesSet` and `destroy` may return a `Future`, awaited by `refreshAsync()` and `closeAsync()`.
- `pause()`, `restart()` and `isPauseable` follow Spring 7.

## See also

- [@PostConstruct](post-construct.md)
- [@PreDestroy](pre-destroy.md)
- [ApplicationContext](application-context.md)
- [Aware interfaces](aware.md)
- [ObjectProvider](object-provider.md)
- [Context events](../events/context-events.md)
- [@DependsOn](depends-on.md)
