跳转到内容

AOT 机制

Feat Cloud 里所谓的 AOT,不是一个抽象口号。
它的含义很具体:在编译期把一部分原本可能在运行期做的事情提前做掉,生成可直接加载的服务代码。

flowchart LR
    A["源码里的 @Controller / @Bean / @Mapper"] --> B["FeatAnnotationProcessor"]
    B --> C["生成 CloudService 实现"]
    C --> D["写入 META-INF/services/CloudService"]
    D --> E["运行期 ApplicationContext"]
    E --> F["ServiceLoader 加载 CloudService"]
    F --> G["加载 Bean / 注入依赖 / 注册路由"]

如果你只是正常使用 Feat Cloud,其实不需要先理解这一页。
但如果你想知道它为什么能以这种方式组织 Bean、Controller、Mapper 和运行时加载流程,这一页会有帮助。

在很多常见 Java Web 框架里,运行时通常要做这些事:

  • 扫描类和注解
  • 解析 Controller
  • 建立路由映射
  • 注入依赖
  • 生成或缓存一些元数据

Feat Cloud 选择把其中很大一部分提前到编译期完成。
这也是它 AOT 设计的起点。

从仓库结构可以直接看出这套机制的几个关键模块:

  • feat-cloud-aot
  • feat-cloud-starter
  • feat-cloud

其中:

  • feat-cloud-aot 提供编译期的注解处理器(APT)和代码生成逻辑
  • feat-cloud-starter 是你在工程里引入的编译期依赖,内部包装了 feat-cloud-aot
  • feat-cloud 负责运行期加载和执行这些服务

编译期最关键的入口是:

  • tech.smartboot.feat.cloud.aot.FeatAnnotationProcessor

它会处理 Feat Cloud 关心的注解,并生成对应的服务类和服务描述文件。

它生成的不是“元数据”,而是“可执行服务类”

Section titled “它生成的不是“元数据”,而是“可执行服务类””

在 Feat Cloud 的运行模型里,最终被加载的是 CloudService 实现。
运行时的 ApplicationContext 会通过 ServiceLoader.load(CloudService.class) 去加载这些服务。

这意味着 AOT 做的核心事情不是只存一份配置,而是生成一批真正可以被运行期直接执行的类。

这也是为什么在你查看 target/generated-sources/annotations 时,会看到诸如:

  • UserControllerCloudService
  • UserMapperCloudService
  • FeatApplication

这类名字的类。

如果编译期已经生成好了这些服务类,运行期就不需要再去做同样级别的动态分析。
它可以更直接地进入下面这些动作:

  • 加载服务
  • 初始化 Bean
  • 注入依赖
  • 注册路由

这会带来几个实际效果:

  • 运行期逻辑更直接
  • 启动过程的工作量更可控
  • 对 Native Image 这类场景更友好

当你写下一个普通控制器:

@Controller("users")
public class UserController {
@RequestMapping("/hello")
public String hello() {
return "hello";
}
}

在 Feat Cloud 的 AOT 体系里,编译期不会只记住“这里有个 Controller”。
它会生成对应的 CloudService 类,并在其中明确写出:

  • 如何实例化这个 Controller
  • 如何把它注册进应用上下文
  • 如何把 /users/hello 这条路由挂到 Router 上

所以从运行期视角看,它接手到的已经不是“一个待解析的 Controller 类”,而是“一个已经具备执行逻辑的 CloudService”。

Bean、Mapper、CloudOptions 也在做同样的事

Section titled “Bean、Mapper、CloudOptions 也在做同样的事”

从 feat-cloud-aot 里的实现可以看到,AOT 处理并不只针对 Controller。

它还会围绕这些方向生成代码:

  • Bean
  • Mapper
  • CloudOptions 相关扩展

这也是为什么 MyBatis、CloudOptions、Controller 这几块内容在 Feat Cloud 里能一起被纳入同一套编译期体系里理解。

运行期是怎么把这些东西串起来的

Section titled “运行期是怎么把这些东西串起来的”

真正串联这套机制的是 ApplicationContext。

它的大致运行路径可以理解成:

  1. 用 ServiceLoader 找到所有 CloudService
  2. 按顺序加载这些服务
  3. 让服务完成 Bean 加载、注入、路由注册等动作
  4. 最终把应用挂到运行时的 Router 和上下文里

所以从外部看你只是调用了:

FeatCloud.cloudServer().listen();

但内部真正工作的,是一套已经在编译期准备好的服务装配流程。

AOT 编译期生成带来的另一个好处,是让 Native Image 变得可行:生成代码把反射和动态代理的使用压到了最低,静态分析更容易收敛。

但 AOT 编译期机制和 Native Image 是两件事:前者是 Feat Cloud 自己的编译期代码生成,后者是 GraalVM 对整个应用的提前编译。如果你关心的是交付形态,请直接阅读部署与交付中的 Native 优化路线。

从使用者角度,AOT 最重要的意义不是“编译期很高级”,而是它会影响你怎么理解下面这些事:

  • 为什么 Feat Cloud 的启动过程长这样
  • 为什么某些能力更适合编译期决定,而不是运行期反射
  • 为什么它和传统运行期扫描框架的运行模型不一样

换句话说,这页的价值不在于教你“怎么手写 AOT”,而在于帮你理解 Feat Cloud 这套架构为什么是现在这个样子。

这页最适合在下面这些时机阅读:

  • 你已经用过 Feat Cloud,但想理解它的底层机制
  • 你在看 target/generated-sources/annotations,想知道这些类为什么存在
  • 你准备继续研究 Native Image 相关内容