Spring Boot 应用入口与启动流程.md 26 KB

Spring Boot 应用入口与启动流程

本文引用的文件

  • pom.xml
  • ai-server/pom.xml
  • App.java
  • application.yaml
  • application-dev.yaml
  • application-pro.yaml
  • DataSourceConfig.java
  • CaseDataSourceReaper.java
  • AppLoadEndEventListener.java
  • SearchIndexInitializer.java
  • AiCleanWatchdog.java
  • AiCleanTemplateVectorIndex.java

目录

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能与启动特性
  8. 常见问题排查
  9. 结论

简介

本文面向 ai-server Spring Boot 应用,聚焦其应用入口、包扫描范围、模块依赖加载方式,以及基于 Spring Boot 3.5 + Java 25 的启动阶段横切能力。重点说明多数据源初始化、Redis 连接、Agent 工作区与系统目录初始化、全文检索索引创建、智能清洗批次恢复与看门狗、向量索引自举等 ApplicationRunner / CommandLineRunner / ApplicationListener 行为,并解释配置文件组织与环境变量覆盖机制。

项目结构

ai-server 是 Maven 子模块,位于仓库根 pom.xml 的 modules 中;父 POM 继承 Spring Boot 3.5.16 的 starter parent,因此 Spring Boot 版本由父 POM 统一约束。ai-server 自身声明 Java 25,并通过多个 Spring Boot Starter 引入 Web、Validation、Data Redis、AOP、Sa-Token、MyBatis-Plus、动态数据源、AgentScope、PostgreSQL、pgvector、DuckDB、Tika、DJL HuggingFace Tokenizers 和 ONNX Runtime 等能力。

graph TB
    RootPOM["根 POM<br/>Spring Boot 3.5.16"] --> ServerModule["ai-server 子模块<br/>Java 25"]
    ServerModule --> SpringBootStarterWeb["spring-boot-starter-web"]
    ServerModule --> SpringBootStarterValidation["spring-boot-starter-validation"]
    ServerModule --> SpringBootStarterDataRedis["spring-boot-starter-data-redis"]
    ServerModule --> SpringBootStarterAop["spring-boot-starter-aop"]
    ServerModule --> SaToken["sa-token-spring-boot3-starter"]
    ServerModule --> MybatisPlus["mybatis-plus-spring-boot3-starter"]
    ServerModule --> DynamicDatasource["dynamic-datasource-spring-boot3-starter"]
    ServerModule --> AgentScope["agentscope-harness 及扩展"]
    ServerModule --> PostgreSQL["postgresql 驱动"]
    ServerModule --> PgVector["com.pgvector pgvector"]
    ServerModule --> DuckDB["duckdb_jdbc"]
    ServerModule --> Tika["spring-ai-tika-document-reader"]
    ServerModule --> DJL["ai.djl.huggingface tokenizers"]
    ServerModule --> OnnxRuntime["onnxruntime"]

图示来源

  • pom.xml:1-23
  • ai-server/pom.xml:1-40
  • ai-server/pom.xml:40-200

章节来源

  • pom.xml:1-23
  • ai-server/pom.xml:1-40

核心组件

  • 应用主类:com.zsjz.ai.App 是 Spring Boot 启动入口,启用调度任务注解,并使用 SpringApplication.run 启动容器。
  • 配置中心:application.yaml 定义通用配置与业务配置命名空间,application-dev.yaml 与 application-pro.yaml 按 profile 注入数据库、Redis、Elasticsearch、存储路径、Sa-Token 和 Neo4j 等环境差异配置。
  • 数据源路由:通过自定义 DataSourceConfig 替换动态数据源自动装配的默认路由,使 @DS("slave") 能结合案件上下文动态路由到 DuckDB 案件库。
  • 启动期横切逻辑:AppLoadEndEventListener(CommandLineRunner)、SearchIndexInitializer(ApplicationRunner)、AiCleanWatchdog(ApplicationReadyEvent)、AiCleanTemplateVectorIndex(ApplicationReadyEvent)在启动不同阶段执行清理、缓存预热、索引重建或恢复操作。

章节来源

  • App.java:1-49
  • application.yaml:1-193
  • application-dev.yaml:1-87
  • application-pro.yaml:1-87
  • DataSourceConfig.java:1-38

架构总览

应用以 Spring Boot 容器为中心,启动后完成以下关键横切能力:

  1. 解析 application.yaml 与各 profile 配置,启用定时任务线程池、跨域、上传大小限制、AI 研判、DuckDB 预算、向量模型开关、智能清洗配置、Agent RAG、Sa-Token 等。
  2. 初始化 PostgreSQL 主数据源与动态数据源;使用 CaseRoutingDataSource 将业务 SQL 中的 @DS("slave") 路由到当前用户打开的案件 DuckDB 数据源。
  3. 初始化 Redis 连接,供 Sa-Token 会话、登录态、全局缓存、案件缓存、健康检查等使用。
  4. 创建系统目录与 Agent 工作区目录,初始化全局缓存、RocksDB 数据目录。
  5. 幂等创建 Elasticsearch 全文检索索引;若 ES 不可用则降级,不阻断启动。
  6. 应用就绪后:
    • 将上一轮未完成的智能清洗批次标记为失败,允许用户重新发起。
    • 后台检测模板向量索引是否缺失或为空,必要时进行全量重建。
  7. 运行期定时任务:SSE 心跳、数据源兜底回收、智能清洗看门狗、附件清理等。

    sequenceDiagram
    participant JVM as "JVM"
    participant App as "App 主类"
    participant Spring as "Spring 容器"
    participant Config as "配置与数据源"
    participant Redis as "Redis"
    participant Runner as "CommandLineRunner / ApplicationRunner"
    participant Ready as "ApplicationReadyEvent 监听器"
    participant Scheduler as "定时任务"
    
    JVM->>App: 调用 main()
    App->>Spring: SpringApplication.run(App.class, args)
    Spring->>Config: 加载 application.yaml 与 profile
    Spring->>Config: 初始化 PostgreSQL 与动态数据源
    Spring->>Redis: 初始化 Redis 连接
    Spring-->>Runner: 触发 CommandLineRunner / ApplicationRunner
    Runner->>Runner: 创建系统目录与 Agent 工作区
    Runner->>Runner: 初始化全局缓存与 RocksDB
    Spring-->>Ready: 触发 ApplicationReadyEvent
    Ready->>Ready: 恢复上一轮智能清洗批次
    Ready->>Ready: 检测并可能重建模板向量索引
    Spring->>Scheduler: 启动 SSE 心跳、数据源回收、看门狗等定时任务
    

图示来源

  • App.java:23-33
  • application.yaml:1-193
  • DataSourceConfig.java:20-38
  • AppLoadEndEventListener.java:15-58
  • SearchIndexInitializer.java:18-30
  • AiCleanWatchdog.java:64-95
  • AiCleanTemplateVectorIndex.java:221-260

详细组件分析

应用主类与包扫描范围

App 使用 @SpringBootApplication,因此 Spring Boot 默认从 com.zsjz.ai 开始扫描组件。代码中没有显式 @ComponentScan,也没有对 @MapperScan 做全局扫描;注释明确说明 Mapper 依靠类上的 @Mapper 注解注册。这意味着新增 Mapper 必须添加 @Mapper,否则会因找不到映射而启动失败或运行时异常。

classDiagram
    class App {
        +main(args)
        +init()
    }
    class DataSourceConfig {
        +caseRoutingDataSource(providers, properties)
    }
    class AppLoadEndEventListener {
        +run(args)
    }
    class SearchIndexInitializer {
        +run(args)
    }
    class AiCleanWatchdog {
        +failOrphansOnStartup(event)
        +failStale()
    }
    class AiCleanTemplateVectorIndex {
        +onApplicationReady(event)
        +rebuildAsync()
        +isAvailable()
    }

    App --> DataSourceConfig : "容器装配"
    App --> AppLoadEndEventListener : "启动后执行"
    App --> SearchIndexInitializer : "启动后执行"
    App --> AiCleanWatchdog : "应用就绪后执行"
    App --> AiCleanTemplateVectorIndex : "应用就绪后执行"

图示来源

  • App.java:15-33
  • DataSourceConfig.java:20-38
  • AppLoadEndEventListener.java:15-58
  • SearchIndexInitializer.java:18-30
  • AiCleanWatchdog.java:64-95
  • AiCleanTemplateVectorIndex.java:221-260

章节来源

  • App.java:15-33
  • AiCleanTemplateVectorIndex.java:221-260

多数据源初始化与案件数据源回收

项目使用 dynamic-datasource 管理 PostgreSQL 主库,同时通过 CaseRoutingDataSource 替换默认的 DynamicRoutingDataSource,使 @DS("slave") 能够根据当前会话中的 caseId 路由到 DuckDB 案件库。DataSourceConfig 把 dynamic-datasource 的 primary、strict、strategy、p6spy、seata、graceDestroy 等属性手动拷贝到自定义数据源上,否则这些配置会静默失效。

CaseDataSourceReaper 是一个定时兜底回收器:当用户会话自然过期、被顶下线或被踢下线时,如果没有对应的登出监听回调,已打开的案件 DuckDB 数据源可能长期占用内存配额。该回收器定期扫描已打开案件,判断归属 token 是否仍有效;若无效且没有业务探针报告“仍在运行”,则关闭数据源并清理对应缓存。它采用保守策略:查询会话状态失败时不释放,宁可多占一点内存也不误关正在使用的数据源。

flowchart TD
    Start(["定时任务 reap"]) --> CheckOpen["是否有已打开的案件数据源?"]
    CheckOpen -->|否| End(["结束本轮扫描"])
    CheckOpen -->|是| Iterate["遍历每个 caseId"]
    Iterate --> GetOwner["获取归属 userId 与 ownerToken"]
    GetOwner --> SessionAlive{"归属会话是否存活?"}
    SessionAlive -->|是| NextCase["下一个 caseId"]
    SessionAlive -->|否| HasWork{"是否有在途任务?"}
    HasWork -->|是| Skip["跳过回收,等待下一轮"]
    HasWork -->|否| Close["关闭案件数据源并清理缓存"]
    Close --> NextCase
    NextCase -->|还有| Iterate
    NextCase -->|无| End

图示来源

  • DataSourceConfig.java:10-38
  • CaseDataSourceReaper.java:15-125

章节来源

  • DataSourceConfig.java:10-38
  • CaseDataSourceReaper.java:15-125

Redis 连接与会话依赖

Redis 通过 spring-boot-starter-data-redis 自动装配,并在 dev/pro 配置中指定 host、port。Sa-Token 的 Redis Jackson 集成使 Redis 成为登录态的硬依赖:Redis 不可用时 StpUtil.* 直接抛异常,没有降级实现。项目中多处使用 RedisTemplate 与 StringRedisTemplate,包括系统健康检查、缓存管理、登录活动记录、案件数据缓存等。

需要注意:Sa-Token 的生产默认单端登录,后登录会顶掉前登录;联调多端时可传入 -DSA_TOKEN_CONCURRENT=true 或通过环境变量覆盖。

章节来源

  • application-dev.yaml:1-87
  • application-pro.yaml:1-87

Agent 工作区与系统目录初始化

AppLoadEndEventListener 实现 CommandLineRunner,在 Spring 容器启动完成后执行目录初始化与缓存初始化:

  • 创建临时目录、系统 RocksDB 目录、许可证目录、Agent 目录、技能目录。
  • 初始化全局缓存与系统级 RocksDB 数据。
  • 注释说明案件级 CaseDataCache 不在启动时预热,因为未开案时 slave 数据源不存在,强行读取会导致启动失败;改为开案后按需加载。
  • 初始化 Tower 相关工具。

    flowchart TD
    Entry(["CommandLineRunner.run"]) --> InitDir["创建系统目录"]
    InitDir --> GlobalCache["初始化全局缓存"]
    GlobalCache --> RocksDB["初始化系统 RocksDB 数据"]
    RocksDB --> CaseCacheNote["暂不预热案件缓存"]
    CaseCacheNote --> Tower["初始化 Tower 工具"]
    Tower --> Done(["完成"])
    

图示来源

  • AppLoadEndEventListener.java:15-58

章节来源

  • AppLoadEndEventListener.java:15-58

全文检索索引创建

SearchIndexInitializer 实现 ApplicationRunner,在应用启动时尝试创建 Elasticsearch 全文检索索引。ES 不可用时仅记录错误日志,不阻断启动;索引会在首次写入时由底层服务重试创建。这种设计让全文检索成为增强能力而非启动硬性依赖。

章节来源

  • SearchIndexInitializer.java:18-30

智能清洗批次恢复与看门狗

AiCleanWatchdog 承担两个职责:

  1. 启动恢复:监听 ApplicationReadyEvent,将所有非终态的智能清洗批次标记为 FAILED,原因是虚拟线程不跨进程重启,进程重启后旧 worker 一定已死亡。用户可重新发起清洗,从而形成恢复路径。
  2. 运行时看门狗:扫描长时间无进展的非终态批次,将其标记为 FAILED,以便用户重试。判定逻辑基于 update_time 与 stale-minutes 配置。

    flowchart TD
    Startup(["应用就绪"]) --> MarkFailed["将全部非终态批次标为 FAILED"]
    MarkFailed --> LogRestart["记录恢复日志"]
    Runtime(["定时扫描"]) --> CheckStale{"是否存在超时批次?"}
    CheckStale -->|否| WaitNext["等待下一轮"]
    CheckStale -->|是| MarkStale["标记为 FAILED 并记录原因"]
    MarkStale --> WaitNext
    

图示来源

  • AiCleanWatchdog.java:64-95

章节来源

  • AiCleanWatchdog.java:64-95

向量索引自举与可用性判断

AiCleanTemplateVectorIndex 负责表结构与 AI 模板元数据的向量索引。其启动自举逻辑监听 ApplicationReadyEvent:

  • 如果向量功能未启用或禁止启动自举,直接返回。
  • 如果嵌入模型不可用,只记日志并跳过。
  • 如果向量维度无效,跳过。
  • 如果向量表存在且有文档,跳过。
  • 否则异步执行全量重建。

整个链路遵循 fail-open:向量能力缺失时不阻断主业务,只是降低模板语义召回与 Agent 选表能力。

flowchart TD
    Ready(["ApplicationReadyEvent"]) --> Enabled{"向量索引已启用?"}
    Enabled -->|否| Exit["退出"]
    Enabled -->|是| Available{"嵌入模型可用?"}
    Available -->|否| LogUnavailable["记录不可用信息"]
    LogUnavailable --> Exit
    Available -->|是| Dimensions{"维度有效?"}
    Dimensions -->|否| LogBadDimensions["记录维度无效"]
    LogBadDimensions --> Exit
    Dimensions -->|是| Exists{"向量表存在且有数据?"}
    Exists -->|是| Exit
    Exists -->|否| RebuildAsync["异步全量重建"]

图示来源

  • AiCleanTemplateVectorIndex.java:221-260

章节来源

  • AiCleanTemplateVectorIndex.java:221-260

依赖关系分析

  • Spring Boot 版本:父 POM 锁定 spring-boot-starter-parent:3.5.16,因此 ai-server 运行于 Spring Boot 3.5 系列。
  • Java 版本:ai-server 的 java.version=25,ONNX Runtime 与 Java 25 配合时可能出现 restricted method 警告,可通过 JVM 参数 --enable-native-access=ALL-UNNAMED 消除。
  • 数据库:PostgreSQL 为主数据源,pgvector 用于向量存储;DuckDB 作为每案独立库,通过动态数据源与 CaseRoutingDataSource 接入。
  • 缓存与会话:Redis 提供 Sa-Token 会话、全局缓存、登录活动、系统健康检查等能力。
  • 检索:Elasticsearch 提供全文检索;向量检索依赖 pgvector 与嵌入模型(本地 bge-m3 ONNX 或在线 embedding)。
  • Agent 能力:AgentScope harness 及 DashScope、Ollama、OpenAI 扩展,Mem0、RAG Simple、Redis 扩展。

    graph LR
    App["App 主类"] --> Spring["Spring Boot 3.5.16"]
    Spring --> PostgreSQL["PostgreSQL"]
    Spring --> PgVector["pgvector"]
    Spring --> DuckDB["DuckDB 案件库"]
    Spring --> Redis["Redis"]
    Spring --> ES["Elasticsearch"]
    Spring --> AgentScope["AgentScope 扩展"]
    Spring --> Embedding["本地 bge-m3 / 在线 embedding"]
    

图示来源

  • pom.xml:1-23
  • ai-server/pom.xml:1-40
  • ai-server/pom.xml:40-200
  • application-dev.yaml:1-87
  • application-pro.yaml:1-87

章节来源

  • pom.xml:1-23
  • ai-server/pom.xml:1-40
  • ai-server/pom.xml:40-200

性能与启动特性

  • 调度线程池:默认 4 线程,避免 SSE 心跳、数据源回收、AI 清洗看门狗、附件清理等定时任务互相阻塞。
  • 上传限制:HTTP 编解码最大内存、表单最大文件大小与请求大小均可通过环境变量覆盖。
  • DuckDB 预算:通过 max-open-cases、memory-ratio、threads-per-instance 控制并发案件数与内存占比,防止多人开案叠加资源。
  • 向量模型预热:本地 bge-m3 权重常驻堆外内存约 2.5~3GB,首次加载耗时较长;支持 eager-load 在启动时后台预热,也支持懒加载。
  • 向量索引自举:只在索引不存在或为空时触发,避免重复重建;重建过程异步执行并有连续失败熔断。
  • Sa-Token 单端登录:生产默认后登录顶掉前登录,需要多端联调时通过参数或环境变量开启并发。

[本节为通用性能说明,不直接分析具体代码片段]

常见问题排查

端口冲突

  • 现象:启动时报端口占用,或前端无法访问后端。
  • 定位点:server.port=8980,server.servlet.context-path=/js/a。
  • 处理建议:修改端口或停止占用 8980 的进程;注意前端访问路径需包含 /js/a。

章节来源

  • application.yaml:1-20

数据库不可达

  • 现象:启动时报 PostgreSQL 连接失败、驱动异常,或后续 SQL 报连接拒绝。
  • 定位点:dev/pro 中 spring.datasource.dynamic.primary=master,master.url、username、password、driver-class-name。
  • 处理建议:
    • 确认 PostgreSQL 已启动且网络可达。
    • 检查 JDBC URL、用户名、密码是否正确。
    • 开发环境默认指向内网地址,部署环境需改为实际数据库地址。
    • 若使用 pgvector,需确认扩展已安装。

章节来源

  • application-dev.yaml:1-40
  • application-pro.yaml:1-40

Redis 连接失败

  • 现象:Sa-Token 登录失败、会话写入失败、健康检查异常,或直接抛出 Redis 连接异常。
  • 定位点:spring.data.redis.host、spring.data.redis.port;Sa-Token 依赖 Redis 且无降级实现。
  • 处理建议:
    • 确认 Redis 服务已启动且主机端口正确。
    • 如部署在容器或云平台,注意网络安全组与内网地址。
    • 若只需认证而不需要 Redis 能力,应改造架构;当前版本 Redis 是登录硬依赖。

章节来源

  • application-dev.yaml:41-87
  • application-pro.yaml:41-87

向量库未启用或向量索引不可用

  • 现象:模板语义召回为空、Agent 搜索不到表结构、AI 模板匹配候选减少。
  • 定位点:
    • zsjz.embedding.enabled:决定是否使用本地 bge-m3。
    • zsjz.embedding.model-path:本地模型目录。
    • zsjz.ai-clean.vector.enabled:向量索引总开关。
    • zsjz.ai-clean.vector.auto-rebuild-on-startup:启动自举开关。
    • pgvector 扩展与 PostgreSQL 向量表。
  • 处理建议:
    • 如果使用本地模型,确认模型目录存在且包含所需权重文件。
    • 如果切换为在线 embedding,确保 agent_model 中存在活跃类型。
    • 检查 zsjz.ai-clean.vector.enabled 是否为 true。
    • 查看启动日志中是否出现“启动自举跳过模板向量索引”或“向量索引不可用”。
    • 必要时手工调用重建接口。

章节来源

  • application.yaml:60-140
  • AiCleanTemplateVectorIndex.java:221-260

Elasticsearch 不可用

  • 现象:启动时报创建索引失败,但应用仍可启动;后续写入时重试创建。
  • 定位点:SearchIndexInitializer 捕获异常并记录日志;Elasticsearch 配置在 dev/pro 的 spring.elasticsearch.uris。
  • 处理建议:
    • 先启动 Elasticsearch。
    • 检查 uris、连接超时、套接字超时。
    • 不要因启动日志报错直接认为应用失败,应观察首次写入行为。

章节来源

  • SearchIndexInitializer.java:18-30
  • application-dev.yaml:41-56
  • application-pro.yaml:41-56

启动后案件数据源泄漏

  • 现象:多人开案后无法新建案件,提示达到 DuckDB 最大打开案件数。
  • 定位点:CaseDataSourceReaper 定时扫描,依据归属 token 与业务探针决定是否回收。
  • 处理建议:
    • 检查 Sa-Token 会话是否正常。
    • 检查是否有长任务未结束导致探针认为仍在运行。
    • 查看回收日志中的“兜底扫描”行,确认会话存活判断是否符合预期。

章节来源

  • CaseDataSourceReaper.java:15-125

启动目录创建失败

  • 现象:日志出现“初始化系统文件失败”,Agent 工作区、许可证目录或 RocksDB 目录未创建。
  • 定位点:AppLoadEndEventListener.initDir()。
  • 处理建议:
    • 确认应用进程对目标目录有写权限。
    • 确认磁盘空间充足。
    • 检查 PathConst 指向的路径是否被安全软件或容器卷挂载限制。

章节来源

  • AppLoadEndEventListener.java:33-58

结论

ai-server 以 com.zsjz.ai.App 为唯一应用入口,使用 Spring Boot 3.5.16 与 Java 25,围绕 PostgreSQL、Redis、Elasticsearch、pgvector、DuckDB 与 AgentScope 构建。启动阶段的关键横切能力包括:系统目录与 Agent 工作区初始化、全局缓存与 RocksDB 初始化、全文检索索引幂等创建、智能清洗批次恢复、模板向量索引自举,以及数据源兜底回收。配置通过 application.yaml 统一管理业务配置,并通过 ${ENV_NAME:默认值} 形式支持环境变量覆盖;数据库、Redis、Elasticsearch、存储路径、Sa-Token 与 Neo4j 等环境差异放在 application-dev.yaml 与 application-pro.yaml 中。常见启动问题主要集中在端口、数据库连通性、Redis 可用性、向量库开关与模型路径、以及案件数据源生命周期管理上。