دروس السلسلة: 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، فشل.