Skip to content

应用生命周期

Rain 的生命周期不是单一回调,而是一条从配置、容器、扫描、元数据注册到资源启动的编排链。框架扩展应根据工作发生的时机选择 ModuleLoaderApplicationService,而不是把全部初始化塞进入口函数。

启动流程

Application.start() 依次执行以下工作:

  1. 读取 rain.runModerain.launchPackage 系统属性。
  2. 创建并初始化 ConfigImpl
  3. 创建 DI 上下文 ContextImpl
  4. 获取 AppLoader,扫描并注册 Bean、Loader 与应用服务。
  5. 按优先级启动全部 ApplicationService
  6. 发布 AppStatusEvent.AppStarted
  7. 注册 JVM shutdown hook。

在存在 pom.xmlbuild.gradlebuild.gradle.kts 的目录中启动时,默认运行模式为 dev;否则默认落到 prod

启动对象如何协作

text
Application
├─ ConfigImpl
│  └─ 合并模块默认配置与应用配置
├─ ContextImpl
│  ├─ 创建 ClassContext
│  ├─ 创建 BeanCreator / BeanInjector
│  └─ 提供 ConfigReader / DataReader
├─ AppLoader
│  ├─ Module.onLoad
│  ├─ 扫描 rain.scanPackages
│  ├─ 建立 AutoBind 与 BeanFactory 上下文
│  ├─ 调用 ClassRegister
│  └─ 执行 Loader
└─ ApplicationServiceLoader
   ├─ start
   └─ stop

Application 直接构造 ConfigImplContextImpl,随后才从容器取得 AppLoader。这意味着普通应用扩展不能仅靠定义一个 Bean 来替换配置或基础 Context;需要替换它们时,应自定义启动编排,而不是假设 @AutoBind 会影响已经完成的构造。

三种初始化入口

Module:扫描前

rain.modules 中列出的类型会在类扫描前由容器创建并调用:

kotlin
class MetricsModule(
    private val context: DiContext,
) : Module {
    override fun onLoad() {
        context.putBean(MetricRegistry::class.java, MetricRegistry())
    }
}
properties
rain.modules=com.example.metrics.MetricsModule

仅当工作必须早于扫描时使用 Module。普通元数据发现应交给 Loader。

Loader:扫描后

Loader 获得已经按触发方式分组的 LoadItem,适合构建路由、监听器、任务、命令等静态注册表。此时容器已经可用,但 ApplicationService 尚未启动。

ApplicationService:资源启动

ApplicationService 适合线程、端口、连接池等长期资源。它能依赖 Loader 已经建立的元数据,因此服务器启动时不需要再次扫描业务类。

优先级与确定性

Loader 和 ApplicationService 都按 priority() 数值升序执行,默认值为 10。优先级是模块间契约,不应随意使用极端数值争抢顺序。

框架模块需要另一个模块的 Loader 结果时,建议:

  1. 在公开文档中声明顺序依赖。
  2. 给两个 Loader 使用有间隔的稳定优先级。
  3. 在消费方启动时验证前置注册表是否已完成。
  4. 避免依赖同优先级对象的偶然扫描顺序。

启动失败语义

  • Module.onLoad() 抛错会直接中断扫描。
  • Loader 创建失败或 load() 抛错会中断应用启动。
  • ApplicationService.start() 抛错会记录日志并继续向上抛出。
  • 当前 Application 没有自动回滚已经启动成功的服务。

因此每个 Service 的 start() 应尽量先构造局部资源,确认成功后再发布到字段;框架若管理多个资源,需自行在失败分支关闭此前已创建的部分。

停止流程

应用停止时先发布 AppStatusEvent.AppStopping,再依次调用应用服务的 stop()

kotlin
class StorageService : ApplicationService {
    override fun priority() = 20

    override fun start() {
        // 建立连接
    }

    override fun stop() {
        // 释放资源
    }
}

WARNING

当前实现中服务停止顺序与启动顺序相同,并非反向顺序。设计依赖关系时不要假设后启动的服务会先停止。

Application.stop() 先发布 AppStopping,再调用 ApplicationServiceLoader。监听该事件的代码仍可访问尚未停止的服务,但不应启动新的长期任务。

当前实现没有重复停止保护。JVM shutdown hook、测试代码或嵌入式宿主若可能多次调用 stop(),服务实现应自行保持幂等:

kotlin
class IdempotentServer : ApplicationService {
    private var server: Server? = null

    override fun start() {
        check(server == null) { "server already started" }
        server = Server().also { it.open() }
    }

    override fun stop() {
        val current = server ?: return
        server = null
        current.close()
    }
}

框架作者检查表

  • 元数据发现是否在 Loader 中完成?
  • 外部资源是否延迟到 ApplicationService 启动?
  • Loader 和服务优先级是否形成文档化契约?
  • 启动中途失败是否会泄漏已创建资源?
  • stop() 是否允许重复调用?
  • 停止逻辑是否错误假设了反向顺序?
  • 测试是否覆盖 AppStarted、AppStopping 和启动失败?

所有扩展阶段的选择见扩展点总览

基于 Apache License 2.0 发布