AOT 编译与 RuntimeHints
概述
Spring Boot 3 内置了 AOT(Ahead-of-Time)引擎,在构建时通过 AotProcessor 分析应用上下文,自动生成 GraalVM Native Image 所需的反射、资源、序列化、代理等配置文件(RuntimeHints)。这取代了 Spring Boot 2.x 时代需要手动编写 reflect-config.json 等方式。
本文将深入拆解 AOT 编译与 RuntimeHints 的 10 个关键细节,涵盖处理流程、Hint 分区、SPI 注册、自动扫描、配置文件结构等核心内容。
本文基于 Spring Boot 3.2.5 + GraalVM Native Image 23.x 源码分析。
基础概念可参考 GraalVM Native Image 与 Spring Boot 3。
1. AotProcessor.aotProcess() 入口
AotProcessor 是 AOT 编译处理的入口,它在 Maven process-aot 阶段被调用,通过 AotApplicationContextFactory 创建轻量级应用上下文进行分析。
public class AotProcessor {
public void aotProcess(AotProcessContext context) throws Exception {
// 1. 创建轻量级应用上下文(跳过完整 refresh)
AotApplicationContextFactory factory = new AotApplicationContextFactory();
GenericApplicationContext applicationContext = factory.createContext(context);
try {
// 2. 收集所有 RuntimeHintsRegistrar
List<RuntimeHintsRegistrar> registrars = collectRegistrars(applicationContext);
// 3. 创建 RuntimeHints 实例
RuntimeHints hints = new RuntimeHints();
// 4. 遍历所有 Registrar 并调用 registerHints()
for (RuntimeHintsRegistrar registrar : registrars) {
registrar.registerHints(hints, applicationContext.getClassLoader());
}
// 5. 自动扫描 Bean 生成 hints
NativeHintsBeanPostProcessor nativeProcessor = new NativeHintsBeanPostProcessor();
nativeProcessor.process(applicationContext, hints);
// 6. 将 hints 导出为 GraalVM 配置文件
HintsExporter exporter = new HintsExporter();
exporter.export(hints, context.getOutputDirectory());
} finally {
applicationContext.close();
}
}
}AotApplicationContextFactory 创建轻量上下文:
public class AotApplicationContextFactory {
public GenericApplicationContext createContext(AotProcessContext context) {
// 创建一个不执行完整 refresh 的轻量上下文
GenericApplicationContext ac = new GenericApplicationContext();
// 注册配置类(@SpringBootApplication 标注的启动类)
ac.registerBean(SpringApplication.class);
ac.registerBean(context.getMainApplicationClass());
// 设置 ClassLoader
ac.setClassLoader(context.getClassLoader());
// 仅处理 BeanDefinition 注册,不触发 Bean 创建
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(ac);
reader.register(context.getMainApplicationClass());
// 注册 BeanFactoryPostProcessor 但不创建 Bean 实例
ac.refreshForAotProcessing(); // 轻量 refresh
return ac;
}
}完整 AOT 处理流程:
mvn process-aot 或 Maven POM 中配置的 aot 插件
↓
AotProcessor.aotProcess()
↓
① AotApplicationContextFactory 创建轻量上下文
↓
② 收集 RuntimeHintsRegistrar(SPI + @ImportRuntimeHints)
↓
③ NativeHintsBeanPostProcessor 自动扫描 Bean
↓
④ HintsExporter 导出为 JSON 配置文件
↓
GraalVM Native Image 使用这些配置编译为原生镜像2. RuntimeHints 的 4 个分区
RuntimeHints 是 AOT 处理的核心容器,包含 4 个分区,分别对应 GraalVM 的不同配置类型:
public class RuntimeHints {
// 4 个分区
private final ReflectionHints reflections; // 反射 hints
private final ResourcesHints resources; // 资源 hints
private final SerializationHints serialization; // 序列化 hints
private final ProxiesHints proxies; // 动态代理 hints
public RuntimeHints() {
this.reflections = new ReflectionHints();
this.resources = new ResourcesHints();
this.serialization = new SerializationHints();
this.proxies = new ProxiesHints();
}
// 各分区的访问方法
public ReflectionHints reflections() { return this.reflections; }
public ResourcesHints resources() { return this.resources; }
public SerializationHints serialization() { return this.serialization; }
public ProxiesHints proxies() { return this.proxies; }
}4 个分区的作用:
| 分区 | 对应 GraalVM 配置文件 | 用途 |
|---|---|---|
ReflectionHints | reflect-config.json | 声明需要反射访问的类、方法、字段 |
ResourcesHints | resource-config.json | 声明需要包含在原生镜像中的资源文件 |
SerializationHints | serialization-config.json | 声明需要进行序列化的类 |
ProxiesHints | proxy-config.json | 声明需要动态代理的接口 |
使用示例:
RuntimeHints hints = new RuntimeHints();
// 添加反射 hint
hints.reflections().registerType(UserService.class, MemberCategory.INVOKE_PUBLIC_METHODS);
// 添加资源 hint
hints.resources().registerPattern("application.yml");
// 添加序列化 hint
hints.serialization().registerType(OrderEvent.class);
// 添加代理 hint
hints.proxies().registerJdkProxy(MyInterface.class);3. RuntimeHintsRegistrar.registerHints() SPI 注册
RuntimeHintsRegistrar 是 SPI 接口,允许框架和第三方库在 AOT 处理时注册自定义的 RuntimeHints。
@FunctionalInterface
public interface RuntimeHintsRegistrar {
void registerHints(RuntimeHints hints, ClassLoader classLoader);
}自动发现机制:
通过 @ImportRuntimeHints 注解将 RuntimeHintsRegistrar 注册到 Spring 容器:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(RuntimeHintsRegistrar.class) // 不直接 import,而是由 AOT 引擎处理
public @interface ImportRuntimeHints {
Class<? extends RuntimeHintsRegistrar>[] value();
}使用方式:
// 1. 定义 RuntimeHintsRegistrar
public class JacksonRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// 注册 Jackson 相关的反射 hint
hints.reflections()
.registerType(ObjectMapper.class, MemberCategory.INVOKE_PUBLIC_METHODS)
.registerType(JsonParser.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
// 注册 Jackson 核心模块资源
hints.resources()
.registerPattern("jackson-core*.jar");
}
}
// 2. 在配置类上使用 @ImportRuntimeHints
@Configuration
@ImportRuntimeHints(JacksonRuntimeHints.class)
public class JacksonConfig {
// ...
}收集流程:
AotProcessor.collectRegistrars()
↓
① 扫描所有 @ImportRuntimeHints 注解 → 获取 Registrar 类列表
↓
② 从 spring.factories 加载 RuntimeHintsRegistrar SPI 实现
↓
③ 实例化所有 Registrar → 调用 registerHints()
↓
填充 RuntimeHints 的 4 个分区Spring Boot 内置的 RuntimeHintsRegistrar:
| 注册器 | 功能 |
|---|---|
JacksonRuntimeHints | Jackson 序列化/反序列化反射 |
SpringWebRuntimeHints | Spring MVC 相关反射 |
HibernateRuntimeHints | Hibernate 实体映射反射 |
ValidationRuntimeHints | Bean Validation 反射 |
ReactorRuntimeHints | Reactor 响应式支持 |
4. NativeHintsBeanPostProcessor 自动扫描
NativeHintsBeanPostProcessor 是一个 Bean 后置处理器,在 AOT 处理时自动遍历所有 Bean,分析其类型信息并生成对应的 hints。
public class NativeHintsBeanPostProcessor {
public void process(GenericApplicationContext applicationContext, RuntimeHints hints) {
// 遍历所有已注册的 BeanDefinition
for (String beanName : applicationContext.getBeanDefinitionNames()) {
BeanDefinition bd = applicationContext.getBeanDefinition(beanName);
processBeanDefinition(bd, hints);
}
}
private void processBeanDefinition(BeanDefinition bd, RuntimeHints hints) {
String beanClassName = bd.getBeanClassName();
if (beanClassName == null) return;
// 1. 注册 Bean 类的反射 hint(允许创建和调用方法)
hints.reflections().registerTypeIfPresent(beanClassName,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS,
MemberCategory.PUBLIC_FIELDS);
// 2. 检查 @ConfigurationProperties 类
if (bd instanceof ConfigurationClassBeanDefinition) {
processConfigurationProperties(bd, hints);
}
// 3. 检查工厂方法返回类型
if (bd instanceof RootBeanDefinition rbd) {
ResolvableType resolvableType = rbd.getResolvableType();
processResolvableType(resolvableType, hints);
}
// 4. 检查 @EventListener 方法参数类型
processEventListenerMethods(bd, hints);
}
private void processConfigurationProperties(BeanDefinition bd, RuntimeHints hints) {
// @ConfigurationProperties 类需要完整的反射支持
String className = bd.getBeanClassName();
hints.reflections().registerTypeIfPresent(className,
MemberCategory.INTROSPECT_DECLARED_METHODS,
MemberCategory.INTROSPECT_PUBLIC_METHODS,
MemberCategory.DECLARED_FIELDS,
MemberCategory.PUBLIC_FIELDS,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
}
}自动扫描的逻辑:
| 扫描目标 | 生成的 Hint |
|---|---|
| 所有 Bean 类 | 注册构造器 + 公共方法反射 |
@ConfigurationProperties 类 | 完整反射(字段、方法、构造器) |
| 工厂方法返回类型 | 注册返回类型的反射 |
@EventListener 参数 | 注册事件类序列化 hint |
@Repository / @Service | 注册构造器反射 |
5. ReflectionHints.registerType() 的 MemberCategory
ReflectionHints 通过 MemberCategory 控制反射访问的粒度:
public enum MemberCategory {
// 公共字段
PUBLIC_FIELDS(false),
// 声明字段(含私有)
DECLARED_FIELDS(false),
// 内省公共方法(不需要 invoke 权限)
INTROSPECT_PUBLIC_METHODS(true),
// 内省声明方法
INTROSPECT_DECLARED_METHODS(true),
// 调用公共方法
INVOKE_PUBLIC_METHODS(true),
// 调用声明方法
INVOKE_DECLARED_METHODS(true),
// 调用公共构造器
INVOKE_PUBLIC_CONSTRUCTORS(true),
// 调用声明构造器
INVOKE_DECLARED_CONSTRUCTORS(true),
;
}使用方式:
public class ReflectionHints {
public ReflectionHints registerType(Class<?> type, MemberCategory... categories) {
// 为指定类型注册多个 MemberCategory
for (MemberCategory category : categories) {
registerTypeHint(type, category);
}
return this;
}
}示例:
// 仅为 Jackson 序列化需要的操作级别注册 hint
hints.reflections().registerType(User.class,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, // 无参构造器
MemberCategory.INVOKE_PUBLIC_METHODS, // getter/setter
MemberCategory.PUBLIC_FIELDS); // 公共字段
// 完整反射(框架使用)
hints.reflections().registerType(ProxyFactory.class,
MemberCategory.INTROSPECT_DECLARED_METHODS,
MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.DECLARED_FIELDS);MemberCategory 对比:
| 类别 | 说明 | 性能影响 |
|---|---|---|
PUBLIC_FIELDS / DECLARED_FIELDS | 允许访问字段 | 低 |
INTROSPECT_*_METHODS | 允许查询方法签名,不可调用 | 低 |
INVOKE_*_METHODS | 允许反射调用方法 | 中 |
INVOKE_*_CONSTRUCTORS | 允许反射创建实例 | 中 |
尽量使用最小权限原则:只需序列化时用
INVOKE_PUBLIC_METHODS即可,不要注册DECLARED_FIELDS。
6. ResourcesHints.registerPattern() 资源模式
ResourcesHints 通过 glob 模式注册需要包含在原生镜像中的资源文件。
public class ResourcesHints {
private final List<ResourceHint> resourceHints = new ArrayList<>();
public ResourcesHints registerPattern(String pattern) {
// 注册 glob 模式匹配的资源
this.resourceHints.add(new ResourceHint(pattern, false));
return this;
}
public ResourcesHints registerPattern(String pattern, boolean isBundle) {
// isBundle = true 表示 Java 资源包(.properties)
this.resourceHints.add(new ResourceHint(pattern, isBundle));
return this;
}
// 内部类
static class ResourceHint {
private final String pattern; // glob 模式
private final boolean isBundle; // 是否为 ResourceBundle
}
}常见资源模式:
// 配置文件
hints.resources()
.registerPattern("application.yml")
.registerPattern("application-*.yml")
.registerPattern("bootstrap.yml")
.registerPattern("logback-spring.xml");
// META-INF 配置
hints.resources()
.registerPattern("META-INF/*.properties")
.registerPattern("META-INF/spring/*.imports")
.registerPattern("META-INF/services/*");
// i18n 资源包
hints.resources()
.registerPattern("messages", true) // ResourceBundle
.registerPattern("messages_zh_CN", true);
// 静态资源
hints.resources()
.registerPattern("static/**")
.registerPattern("templates/**")
.registerPattern("public/**");
// 框架内部资源
hints.resources()
.registerPattern("org/springframework/boot/**")
.registerPattern("org/springframework/web/**");glob 模式语法:
| 模式 | 含义 |
|---|---|
*.yml | 匹配根目录下所有 .yml 文件 |
META-INF/*.properties | 匹配 META-INF/ 下所有 .properties 文件 |
static/** | 匹配 static/ 目录下所有文件和子目录 |
org/springframework/** | 匹配 org/springframework/ 下所有内容 |
7. AotMavenPlugin.process-aot goal
spring-boot-maven-plugin 提供了 process-aot goal,在 Maven 构建中触发 AOT 处理。
Maven 配置:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>process-aot</id>
<goals>
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>AotMojo.execute() 实现:
@Mojo(name = "process-aot", defaultPhase = LifecyclePhase.PACKAGE,
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class AotMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException {
// 1. 确定应用主类
String mainClass = getMainClass();
// 2. 获取 classpath
Set<Artifact> artifacts = getResolvedDependencies();
URL[] classpathUrls = buildClassPath(artifacts);
// 3. 创建 ClassLoader
URLClassLoader classLoader = new URLClassLoader(classpathUrls);
// 4. 调用 AotProcessInvoker
AotProcessInvoker invoker = new AotProcessInvoker(classLoader);
invoker.invokeAotProcess(mainClass, getOutputDirectory());
}
}AotProcessInvoker.invokeAotProcess():
public class AotProcessInvoker {
public void invokeAotProcess(String mainClassName, File outputDirectory) {
// 1. 加载主类
Class<?> mainClass = classLoader.loadClass(mainClassName);
// 2. 创建 AotProcessContext
AotProcessContext context = new AotProcessContext(mainClass, outputDirectory);
// 3. 反射调用 AotProcessor
AotProcessor processor = new AotProcessor();
processor.aotProcess(context);
// 4. 输出 hints 到指定目录
// 生成文件列表:
// target/classes/META-INF/native-image/reflect-config.json
// target/classes/META-INF/native-image/resource-config.json
// target/classes/META-INF/native-image/proxy-config.json
// target/classes/META-INF/native-image/serialization-config.json
}
}生成的文件位置:
target/classes/
└── META-INF/
└── native-image/
├── reflect-config.json
├── resource-config.json
├── proxy-config.json
└── serialization-config.json8. GraalVM reflect-config.json 结构
reflect-config.json 是 GraalVM Native Image 的反射配置,声明所有需要反射访问的类。
[
{
"name": "com.example.UserService",
"methods": [
{"name": "getUsername", "parameterTypes": []},
{"name": "setUsername", "parameterTypes": ["java.lang.String"]}
],
"fields": [
{"name": "username"}
],
"allDeclaredMethods": true,
"allPublicMethods": true,
"allDeclaredFields": true
},
{
"name": "com.example.UserController",
"methods": [
{"name": "getUser", "parameterTypes": ["java.lang.Long"]}
],
"allDeclaredConstructors": true,
"allPublicConstructors": true
},
{
"name": "java.lang.String",
"allDeclaredMethods": true
}
]字段说明:
| JSON 字段 | 对应 MemberCategory | 说明 |
|---|---|---|
methods[] | INVOKE_PUBLIC_METHODS | 指定的可反射调用方法列表 |
allDeclaredMethods | INTROSPECT_DECLARED_METHODS | 所有声明方法可内省 |
allPublicMethods | INVOKE_PUBLIC_METHODS | 所有公共方法可调用 |
fields[] | PUBLIC_FIELDS | 指定的可反射访问字段 |
allDeclaredFields | DECLARED_FIELDS | 所有声明字段可反射访问 |
allDeclaredConstructors | INVOKE_DECLARED_CONSTRUCTORS | 所有声明构造器可调用 |
allPublicConstructors | INVOKE_PUBLIC_CONSTRUCTORS | 所有公共构造器可调用 |
HintsExporter 生成 reflect-config.json:
class HintsExporter {
void exportReflectionHints(ReflectionHints hints, JsonWriter writer) {
writer.writeArray(hints.getTypeHints().stream()
.map(typeHint -> {
JsonObject json = new JsonObject();
json.addProperty("name", typeHint.getClassName());
if (typeHint.hasMethods()) {
JsonArray methods = new JsonArray();
for (MethodHint method : typeHint.getMethods()) {
JsonObject m = new JsonObject();
m.addProperty("name", method.getName());
m.add("parameterTypes", toJsonArray(method.getParameterTypes()));
methods.add(m);
}
json.add("methods", methods);
}
// 根据 MemberCategory 决定 boolean 属性
json.addProperty("allDeclaredMethods",
typeHint.hasCategory(MemberCategory.INTROSPECT_DECLARED_METHODS));
json.addProperty("allPublicMethods",
typeHint.hasCategory(MemberCategory.INVOKE_PUBLIC_METHODS));
json.addProperty("allDeclaredFields",
typeHint.hasCategory(MemberCategory.DECLARED_FIELDS));
return json;
}).collect(Collectors.toList()));
}
}9. proxy-config.json 动态代理接口
proxy-config.json 声明 GraalVM 需要预先知道的所有动态代理接口。
[
{
"interfaces": [
"org.springframework.data.jpa.repository.JpaRepository",
"org.springframework.data.repository.CrudRepository"
]
},
{
"interfaces": [
"com.example.MyService"
]
}
]生成场景:
public class ProxiesHints {
public ProxiesHints registerJdkProxy(Class<?>... interfaces) {
// JDK 动态代理:Proxy.newProxyInstance(classLoader, interfaces, handler)
// GraalVM 需要在构建时知道所有代理接口组合
this.proxyHints.add(new ProxyHint(interfaces));
return this;
}
public ProxiesHints registerJdkProxy(String... interfaceNames) {
// 支持字符串形式注册(类可能尚未加载)
this.proxyHints.add(new ProxyHint(interfaceNames));
return this;
}
}Spring Boot 自动生成的 proxy hint 场景:
| 场景 | 代理接口 |
|---|---|
@Repository 代理 | JpaRepository + CrudRepository |
@Configuration CGLIB 代理 | 配置类自身 |
@Transactional | 业务接口 |
AOP @Aspect | 切面标记的接口 |
FeignClient | Feign 接口 |
proxy-config.json 的生成逻辑:
class HintsExporter {
void exportProxiesHints(ProxiesHints hints, JsonWriter writer) {
writer.writeArray(hints.getProxyHints().stream()
.map(proxyHint -> {
JsonObject json = new JsonObject();
json.add("interfaces", toJsonArray(proxyHint.getInterfaceNames()));
return json;
}).collect(Collectors.toList()));
}
}10. resource-config.json 资源
resource-config.json 声明需要包含在原生镜像中的资源文件和资源包。
{
"resources": {
"includes": [
{"pattern": "\\Qapplication.yml\\E"},
{"pattern": "\\Qlogback-spring.xml\\E"},
{"pattern": "\\QMETA-INF/spring/\\E.*"},
{"pattern": "\\QMETA-INF/services/\\E.*"},
{"pattern": "\\Qorg/springframework/boot/\\E.*\\.properties"}
],
"excludes": [
{"pattern": "\\Qdev/\\E.*"}
]
},
"bundles": [
{"name": "messages"},
{"name": "messages_zh_CN"},
{"name": "javax.servlet.LocalStrings"}
]
}格式说明:
| 部分 | 说明 |
|---|---|
resources.includes | 需要包含的资源文件(glob 或 regex 模式) |
resources.excludes | 需要排除的资源文件 |
bundles | Java ResourceBundle 列表 |
GraalVM 模式的转义规则:
GraalVM 使用 \Q...\E 转义字面字符串,后面的 .* 表示通配:
| 资源模式 | GraalVM JSON 中的 pattern |
|---|---|
application.yml | \Qapplication.yml\E |
META-INF/*.properties | \QMETA-INF/\E.*\Q.properties\E |
static/** | \Qstatic/\E.* |
HintsExporter 生成 resource-config.json:
class HintsExporter {
void exportResourcesHints(ResourcesHints hints, JsonWriter writer) {
JsonObject root = new JsonObject();
// 资源 includes
JsonObject resources = new JsonObject();
JsonArray includes = new JsonArray();
for (ResourceHint resourceHint : hints.getIncludes()) {
if (!resourceHint.isBundle()) {
JsonObject pattern = new JsonObject();
pattern.addProperty("pattern", toGraalPattern(resourceHint.getPattern()));
includes.add(pattern);
}
}
resources.add("includes", includes);
root.add("resources", resources);
// ResourceBundle
JsonArray bundles = new JsonArray();
for (ResourceHint resourceHint : hints.getIncludes()) {
if (resourceHint.isBundle()) {
JsonObject bundle = new JsonObject();
bundle.addProperty("name", resourceHint.getPattern());
bundles.add(bundle);
}
}
root.add("bundles", bundles);
writer.writeObject(root);
}
private String toGraalPattern(String globPattern) {
// application.yml → \\Qapplication.yml\\E
// META-INF/spring/* → \\QMETA-INF/spring/\\E.*
if (globPattern.endsWith("*")) {
String prefix = globPattern.substring(0, globPattern.length() - 1);
return "\\Q" + prefix + "\\E.*";
}
if (globPattern.endsWith("/**")) {
String prefix = globPattern.substring(0, globPattern.length() - 3);
return "\\Q" + prefix + "\\E.*";
}
return "\\Q" + globPattern + "\\E";
}
}总结
AOT 编译与 RuntimeHints 的 10 个细节点总结如下:
| # | 细节点 | 核心类/机制 |
|---|---|---|
| ① | AotProcessor.aotProcess() 入口 | AotApplicationContextFactory 创建轻量上下文 |
| ② | RuntimeHints 的 4 个分区 | ReflectionHints / ResourcesHints / SerializationHints / ProxiesHints |
| ③ | RuntimeHintsRegistrar.registerHints() SPI 注册 | @ImportRuntimeHints 注解类 → 自动发现 |
| ④ | NativeHintsBeanPostProcessor 自动扫描 | 遍历所有 Bean → 自动生成反射/资源/序列化 hints |
| ⑤ | ReflectionHints.registerType() 的 MemberCategory | PUBLIC_FIELDS / DECLARED_FIELDS / INVOKE_PUBLIC_METHODS / INVOKE_DECLARED_CONSTRUCTORS |
| ⑥ | ResourcesHints.registerPattern() 资源模式 | *.yml / *.xml / META-INF/* 的 glob 模式 |
| ⑦ | AotMavenPlugin.process-aot goal | AotMojo.execute() → AotProcessInvoker.invokeAotProcess() |
| ⑧ | GraalVM reflect-config.json 结构 | [{"name":"com.example.MyClass","methods":[...],"fields":[...]}] |
| ⑨ | proxy-config.json 动态代理接口 | GraalVM 需要预先知道所有动态代理接口 |
| ⑩ | resource-config.json 资源 | {"pattern":"\\QMETA-INF/spring/\\E...","bundles":[...]} |