تطوير الويب

Spring Boot 4 + GraalVM Native Image: تجميع AOT في الإنتاج

4 min de lecture

دروس السلسلة: Virtual threads Java 25 · JPA Hibernate المتقدم · Spring Cloud Gateway 4.3 · Resilience4j 3

Spring Boot 4.0 GA صدر في 20 نوفمبر 2025، تلاه 4.0.6 في أبريل 2026 (الإصدار المستقر الجاري). هذا الإصدار الرئيسي يُوطّد تكامل GraalVM Native Image: مشروع Spring Boot يمكنه الآن التجميع في تنفيذ أصلي عبر plugins Maven/Gradle الرسمية دون إعداد خاص، يبدأ في أقل من 100 ms ويستهلك 50-80 MB من الذاكرة بدل 2-5 ثوانٍ و200-500 MB لـ JVM الكلاسيكية. للخدمات serverless (AWS Lambda، GCP Cloud Run، Azure Container Apps) أو CLIs Java، هذا المكسب تحوّلي.

المتطلبات

  • Java 25 LTS
  • GraalVM 25+ أو Liberica GraalVM 25 (توزيعة Java + native-image)
  • Maven 3.9+ أو Gradle 8.10+
  • تطبيق Spring Boot 4.0+ وظيفي
  • الوقت المُقدَّر: 90 دقيقة

الخطوة 1 — تثبيت GraalVM 25

عدة توزيعات GraalVM موجودة: Oracle GraalVM (الرسمية)، Liberica GraalVM (BellSoft)، Mandrel (Red Hat، مُحاذاة لـ Quarkus). لـ Spring Boot، Oracle GraalVM أو Liberica يناسبان بشكل متطابق. SDKMAN يُبسّط التثبيت والتبديل.

# Avec SDKMAN (recommandé)
curl -s "https://get.sdkman.io" | bash
source ~/.sdkman/bin/sdkman-init.sh

sdk list java | grep grl

sdk install java 25-graal

java -version
# openjdk 25 2025-09-16
# Java HotSpot(TM) 64-Bit Server VM GraalVM CE 25+...
native-image --version

native-image هو الأداة المفتاحية: يُجمّع bytecode Java في تنفيذ أصلي في build-time، باستخدام تحليل ثابت للاحتفاظ فقط بـ classes/methods القابلة للوصول فعليًا. النتيجة: binary 30-150 MB، بدء فوري، لا JVM ضرورية للتنفيذ. الكلفة: build طويل (1-5 دقائق) وبعض القيود على الانعكاس والتحميل الديناميكي.

الخطوة 2 — إعداد plugin GraalVM في Spring Boot

Plugin org.graalvm.buildtools.native يُنسّق التجميع الأصلي. Spring Boot 4.0 يدمجه في Spring Initializr (خيار « GraalVM Native Support »).

<!-- pom.xml -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.0.6</version>
</parent>

<build>
    <plugins>
        <plugin>
            <groupId>org.graalvm.buildtools</groupId>
            <artifactId>native-maven-plugin</artifactId>
            <configuration>
                <buildArgs>
                    <arg>-O2</arg>
                    <arg>--no-fallback</arg>
                    <arg>-H:+ReportExceptionStackTraces</arg>
                </buildArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

ثلاثة خيارات هيكلية. -O2 يُفعّل تحسين مستوى 2. --no-fallback يُجبر فشلًا إذا واجه التحليل مشكلة غير قابلة للحل (بدل إنتاج binary مُتدهور). +ReportExceptionStackTraces يحفظ آثار الأخطاء قابلة للقراءة.

الخطوة 3 — بناء التنفيذ الأصلي

# Build natif
./mvnw -Pnative native:compile

# Output progressif (5-10 minutes en moyenne) :
# [1/8] Initializing... (3.5s @ 0.20GB)
# [2/8] Performing analysis... (45s @ 0.94GB)
# [3/8] Building universe... (2.1s)
# [4/8] Parsing methods... (8s @ 1.20GB)
# [5/8] Inlining methods... (6s)
# [6/8] Compiling methods... (35s)
# [7/8] Layouting methods... (4s)
# [8/8] Creating image... (3s)

./target/mon-service
# Started MonServiceApplication in 0.072 seconds

ls -lh target/mon-service
# -rwxr-xr-x 1 user staff 78M mai 18 10:30 target/mon-service

البدء في < 100 ms يحل محل 2-3 ثوانٍ JVM الكلاسيكية. الحجم (نموذجيًا 60-150 MB حسب التبعيات) معقول لـ container. استهلاك الذاكرة عند الراحة ينخفض إلى 30-80 MB (مقابل 200-400 MB JVM).

الخطوة 4 — Hints للانعكاس للمكتبات غير المُعتمَدة

GraalVM يُحلّل الكود ثابتًا لتحديد classes/methods القابلة للوصول. هذا التحليل يفوّت كل ما يمرّ بالانعكاس الديناميكي.

// Approche moderne (Spring Boot 4) : @RegisterReflectionForBinding
@RegisterReflectionForBinding({UserDto.class, OrderDto.class})
@SpringBootApplication
public class MonApp { ... }

// Hints programmatiques
@Component
public class ReflectionHints implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        hints.reflection()
            .registerType(MaClasseLegacy.class,
                MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
                MemberCategory.DECLARED_FIELDS);

        hints.resources().registerPattern("templates/*.html");
    }
}

@SpringBootApplication
@ImportRuntimeHints(ReflectionHints.class)
public class MonApp { ... }

للمكتبات الخارجية، GraalVM Reachability Metadata Repository يحوي metadata مُختارة لمئات المكتبات (Logback، Caffeine، HikariCP). plugin GraalVM يُحمّلها تلقائيًا في الـ build.

الخطوة 5 — التجميع AOT والكود المُولَّد Spring

Spring Boot 4 يُقدّم مرحلة AOT (Ahead-of-Time) processing متمايزة عن تجميع GraalVM. هذه المرحلة تُحلّل سياق Spring في الـ build، تُولّد كود Java ثابت لاستبدال الانعكاس عند التنفيذ.

# Build AOT processing (sans native)
./mvnw spring-boot:process-aot

./mvnw spring-boot:run -Pnative

# Mesurer le démarrage
# - JVM classique : 2.5 s
# - JVM + AOT processing : 1.4 s
# - Native image : 0.07 s

الكود المُولَّد (في target/spring-aot/) يحوي @Configuration مُسطَّحة، BeanFactory مُجَهَّزة مسبقًا، وhints انعكاس مُستنتجة تلقائيًا.

الخطوة 6 — بناء صورة Docker أصلية

# Via Cloud Native Buildpacks (recommandé, zéro Dockerfile)
./mvnw -Pnative spring-boot:build-image

docker images | grep mon-service
# mon-service latest abc123 3 minutes ago 178MB

# Dockerfile manuel équivalent
FROM ghcr.io/graalvm/graalvm-community:25 AS build
WORKDIR /app
COPY . .
RUN ./mvnw -Pnative native:compile -DskipTests

FROM debian:stable-slim
COPY --from=build /app/target/mon-service /app/mon-service
EXPOSE 8080
ENTRYPOINT ["/app/mon-service"]

على Cloud Run أو App Runner، البدء البارد لصورة Spring Boot native نموذجيًا 200-400 ms، مقابل 5-15 ثانية لصورة JVM مكافئة. لـ workloads بـ scale-to-zero، هذا الفرق يُغيّر تجربة المستخدم.

الخطوة 7 — القيود التي يجب معرفتها

// Limitations principales :
// 1. Pas de réflexion dynamique sans hints
// Class.forName("com.foo." + suffix) → ÉCHEC sauf si registerType

// 2. Pas de proxies dynamiques arbitraires
// Spring AOP, mock-objects, MapStruct dynamic : OK car hints connus

// 3. Pas de class loading dynamique
// OSGi, plugins runtime via URLClassLoader : impossible

// 4. ServiceLoader doit être déclaré
// Si une bibliothèque utilise META-INF/services/...,
// les implémentations doivent être annotées ou listées dans hints

// 5. Pas de JIT runtime
// Profile-Guided Optimization (PGO) compense en partie au build-time

لـ 95% من تطبيقات Spring Boot الكلاسيكية (REST، JPA، Kafka، Redis)، هذه القيود لا تطرح أي مشكلة في 2026 بفضل hints المُضمَّنة.

الخطوة 8 — Profile-Guided Optimization (PGO)

PGO هي التقنية لاستعادة جزء من أداء JIT في التجميع الأصلي: نُطلق الـ binary في وضع instrumented، نجمع profile تنفيذ حقيقي، ونُعيد التجميع باستخدام هذا profile. النتيجة: 20-40% أداء إضافي على المسارات الساخنة.

# Étape 1 : compiler avec instrumentation
./mvnw -Pnative native:compile -Dnative.image.args="--pgo-instrument"

# Étape 2 : lancer avec charge représentative
./target/mon-service &
ab -n 10000 -c 50 http://localhost:8080/api/products

# Étape 3 : recompiler avec le profil
./mvnw -Pnative native:compile -Dnative.image.args="--pgo=default.iprof"

للخدمات بـ throughput عالٍ (10,000+ req/s) حيث كلفة CPU تُهيمن، PGO يُعيد جزءًا كبيرًا من فجوة الأداء JIT vs AOT.

أخطاء شائعة

العَرَض السبب الحل
« NoClassDefFoundError » في runtime native انعكاس غير مُعلَن أضف hints عبر @RegisterReflectionForBinding أو RuntimeHints
Build أصلي يستهلك 12 GB RAM مرحلة analysis مكثّفة زِد heap GraalVM: -J-Xmx12g
صورة أصلية تتعطّل عند البدء تهيئة في build-time لـ singleton بمورد مفقود إجبار تهيئة runtime: --initialize-at-run-time=com.foo
Cloud Native Buildpacks بطيء تنزيل القاعدة في كل build Cache Docker layer + builder محلي
خطأ ServiceLoader غير موجود META-INF/services ليس في metadata أضف --initialize-at-build-time أو registerType
التطبيق أبطأ بكثير من JVM لا PGO + مسار ساخن غير مُتَّجه فعّل PGO أو ابقَ JVM لهذه الخدمة

الأسئلة الشائعة

متى نختار native vs JVM؟
Native لـ serverless (cold start حرج)، CLI، sidecars، microservices بـ scaling عدواني. JVM للخدمات طويلة العمل بحاجات throughput max وhot-reload.

توافق Hibernate؟
Hibernate 6.4+ يدعم native. القديمة 5.x تتطلّب عمل hints مهم — مُعَيَّب.

كم من الوقت موفَّر في cold start؟
نموذجيًا 95-98%: 5 ث JVM → 100 ms native. على AWS Lambda بـ 100k استدعاء/يوم، توفيرات كلفة جوهرية.

Quarkus أم Spring Boot native؟
Quarkus أكثر نضجًا في native (هندسة مُفكَّر فيها native من الأصل). Spring Boot native لحق في 4.0.

كلفة الذاكرة في build؟
4-8 GB RAM ضرورية لمرحلة analysis native. على runners CI بـ 2 GB، فشل.

مقالات ذات صلة

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité