From f06d923a28feb500621ffa054d3e49649edc1ed8 Mon Sep 17 00:00:00 2001 From: Sin-Kang Date: Wed, 27 May 2026 05:50:03 +0900 Subject: [PATCH] =?UTF-8?q?feat(native):=20RuntimeHintsRegistrar=20for=20G?= =?UTF-8?q?raalVM=20Native=20(planning=20=C2=A714)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds DevslabKitRuntimeHints + wires it via @ImportRuntimeHints on TenantAutoConfiguration (which is always active). Covers the reflection / resource surface the kit will need under nativeCompile: Reflection — full MemberCategory.values() so Hibernate can introspect constructors, methods, and field access: - PlatformUserAccountEntity - PlatformRoleEntity / PermissionEntity / UserRoleEntity / RolePermissionEntity - PlatformGroupEntity / UserGroupEntity / GroupRoleEntity - PlatformMenuEntity - PlatformAuditLogEntity Reflection — constructor + declared methods so Spring can invoke ApplicationListener payloads: - LoginSucceededEvent / LoginFailedEvent / UserAccountCreatedEvent Resources — pattern matches so the native image bundles them: - db/migration/*.sql (Flyway V1-V6) - META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports What this does NOT do: actually run ./gradlew nativeCompile. That's a 10-20 minute build with non-trivial memory needs and routinely needs another fix-cycle (per-record hints, dynamic proxies). The expectation is that consumer apps run nativeCompile in their own pipeline (sample-app already has `org.graalvm.buildtools.native` plugin from PR #1). When that produces a missing-hint failure, the hint goes into this class. Verified: ./gradlew :devslab-kit-sample-app:test -> BUILD SUCCESSFUL in 58s (hints registration doesn't run at JVM-mode test time, but the ApplicationContext still boots cleanly so the @ImportRuntimeHints wiring at least compiles + loads.) --- .../autoconfigure/DevslabKitRuntimeHints.java | 71 +++++++++++++++++++ .../TenantAutoConfiguration.java | 2 + 2 files changed, 73 insertions(+) create mode 100644 devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/DevslabKitRuntimeHints.java diff --git a/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/DevslabKitRuntimeHints.java b/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/DevslabKitRuntimeHints.java new file mode 100644 index 0000000..552ed1d --- /dev/null +++ b/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/DevslabKitRuntimeHints.java @@ -0,0 +1,71 @@ +package kr.devslab.kit.autoconfigure; + +import kr.devslab.kit.access.core.entity.PlatformGroupEntity; +import kr.devslab.kit.access.core.entity.PlatformGroupRoleEntity; +import kr.devslab.kit.access.core.entity.PlatformPermissionEntity; +import kr.devslab.kit.access.core.entity.PlatformRoleEntity; +import kr.devslab.kit.access.core.entity.PlatformRolePermissionEntity; +import kr.devslab.kit.access.core.entity.PlatformUserGroupEntity; +import kr.devslab.kit.access.core.entity.PlatformUserRoleEntity; +import kr.devslab.kit.audit.core.entity.PlatformAuditLogEntity; +import kr.devslab.kit.identity.core.entity.PlatformUserAccountEntity; +import kr.devslab.kit.identity.event.LoginFailedEvent; +import kr.devslab.kit.identity.event.LoginSucceededEvent; +import kr.devslab.kit.identity.event.UserAccountCreatedEvent; +import kr.devslab.kit.menu.core.entity.PlatformMenuEntity; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * GraalVM Native reflection / resource hints for the devslab-kit + * starter beans that depend on runtime reflection (JPA entities, event + * payloads serialised by Spring, Flyway migration resources). + * + *

Registered via {@code @ImportRuntimeHints} on + * {@link TenantAutoConfiguration} so it always loads alongside the kit. + * Planning doc §14 calls this out as a first-class requirement. + */ +public class DevslabKitRuntimeHints implements RuntimeHintsRegistrar { + + private static final Class[] JPA_ENTITIES = { + PlatformUserAccountEntity.class, + PlatformRoleEntity.class, + PlatformPermissionEntity.class, + PlatformUserRoleEntity.class, + PlatformRolePermissionEntity.class, + PlatformGroupEntity.class, + PlatformUserGroupEntity.class, + PlatformGroupRoleEntity.class, + PlatformMenuEntity.class, + PlatformAuditLogEntity.class, + }; + + private static final Class[] EVENT_PAYLOADS = { + LoginSucceededEvent.class, + LoginFailedEvent.class, + UserAccountCreatedEvent.class, + }; + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // JPA entities — Hibernate needs constructors + declared methods + declared fields at runtime + for (Class entity : JPA_ENTITIES) { + hints.reflection().registerType(entity, MemberCategory.values()); + } + + // ApplicationEvent payload records — listener invocation reflects on accessor methods + for (Class event : EVENT_PAYLOADS) { + hints.reflection().registerType(event, + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS); + } + + // Flyway migrations ship inside each -core jar + hints.resources().registerPattern("db/migration/*.sql"); + + // Spring Boot AutoConfiguration imports file (defensive — Spring usually handles this) + hints.resources().registerPattern( + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"); + } +} diff --git a/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/TenantAutoConfiguration.java b/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/TenantAutoConfiguration.java index 0514aff..0c19309 100644 --- a/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/TenantAutoConfiguration.java +++ b/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/TenantAutoConfiguration.java @@ -11,9 +11,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ImportRuntimeHints; @AutoConfiguration @EnableConfigurationProperties(DevslabKitProperties.class) +@ImportRuntimeHints(DevslabKitRuntimeHints.class) @ConditionalOnProperty( prefix = "devslab.kit.tenant", name = "enabled",