۱. مرور کلی
در این آزمایشگاه کد، ما در مورد پروژه Spring Native، ساخت برنامهای که از آن استفاده میکند و استقرار آن در Google Cloud خواهیم آموخت.
ما اجزای آن، تاریخچه اخیر پروژه، برخی موارد استفاده و البته مراحل مورد نیاز برای استفاده از آن در پروژههایتان را بررسی خواهیم کرد.
پروژه Spring Native در حال حاضر در مرحله آزمایشی است، بنابراین برای شروع به پیکربندی خاصی نیاز دارد. با این حال، همانطور که در SpringOne 2021 اعلام شد، Spring Native قرار است با پشتیبانی درجه یک در Spring Framework 6.0 و Spring Boot 3.0 ادغام شود، بنابراین اکنون زمان مناسبی برای بررسی دقیقتر این پروژه چند ماه قبل از انتشار آن است.
اگرچه کامپایل درجا برای مواردی مانند فرآیندهای طولانی مدت بسیار بهینه شده است، اما موارد استفاده خاصی وجود دارد که در آنها برنامههای کامپایل شده از قبل حتی بهتر عمل میکنند، که در طول آزمایشگاه کد در مورد آنها بحث خواهیم کرد.
یاد خواهید گرفت که چگونه
- از پوسته ابری استفاده کنید
- فعال کردن API اجرای ابری
- یک برنامه بومی Spring ایجاد و مستقر کنید
- چنین برنامهای را روی Cloud Run مستقر کنید
آنچه نیاز دارید
- یک پروژه پلتفرم ابری گوگل با یک حساب پرداخت فعال GCP
- نصب gcloud cli یا دسترسی به Cloud Shell
- مهارتهای پایه جاوا + XML
- آشنایی کامل با دستورات رایج لینوکس
نظرسنجی
چگونه از این آموزش استفاده خواهید کرد؟
تجربه خود را با جاوا چگونه ارزیابی میکنید؟
تجربه خود را در استفاده از خدمات ابری گوگل چگونه ارزیابی میکنید؟
۲. پیشینه
پروژه Spring Native از چندین فناوری برای ارائه عملکرد برنامههای بومی به توسعهدهندگان استفاده میکند.
برای درک کامل Spring Native، درک چند مورد از این فناوریهای کامپوننت، آنچه که برای ما فراهم میکنند و نحوه همکاری آنها در اینجا مفید است.
تدوین AOT
وقتی توسعهدهندگان به طور معمول javac را در زمان کامپایل اجرا میکنند، کد منبع .java ما به فایلهای .class کامپایل میشود که به صورت بایتکد نوشته شدهاند. این بایتکد فقط برای درک توسط ماشین مجازی جاوا در نظر گرفته شده است، بنابراین JVM باید این کد را در دستگاههای دیگر تفسیر کند تا ما بتوانیم کد خود را اجرا کنیم.
این فرآیند همان چیزی است که قابلیت حمل جاوا را به ما میدهد - به ما این امکان را میدهد که "یک بار بنویسیم و همه جا اجرا کنیم"، اما در مقایسه با اجرای کد بومی، پرهزینه است.
خوشبختانه، اکثر پیادهسازیهای JVM از کامپایل درجا برای کاهش این هزینه تفسیر استفاده میکنند. این کار با شمارش فراخوانیهای یک تابع انجام میشود و اگر به اندازه کافی فراخوانی شود تا از یک آستانه ( به طور پیشفرض ۱۰۰۰۰ ) عبور کند، در زمان اجرا به کد بومی کامپایل میشود تا از تفسیر پرهزینه بیشتر جلوگیری شود.
کامپایل پیش از زمان، رویکردی معکوس را در پیش میگیرد و تمام کدهای قابل دسترسی را در زمان کامپایل به یک فایل اجرایی بومی کامپایل میکند. این روش، قابلیت حمل را فدای بهرهوری از حافظه و سایر مزایای عملکرد در زمان اجرا میکند.

البته این یک معامله است و همیشه ارزش انجام دادن ندارد. با این حال، کامپایل AOT میتواند در موارد استفاده خاصی مانند موارد زیر بدرخشد:
- برنامههای کوتاهمدت که زمان راهاندازی آنها مهم است
- محیطهایی با محدودیت حافظه بالا که در آنها JIT ممکن است بسیار پرهزینه باشد
جالب است بدانید که کامپایل AOT به عنوان یک ویژگی آزمایشی در JDK 9 معرفی شد، هرچند نگهداری این پیادهسازی پرهزینه بود و هرگز کاملاً مورد توجه قرار نگرفت، بنابراین بیسروصدا در جاوا ۱۷ حذف شد و توسعهدهندگان فقط از GraalVM استفاده کردند.
GraalVM
GraalVM یک توزیع JDK متنباز بسیار بهینهشده است که دارای زمان راهاندازی بسیار سریع، کامپایل تصویر بومی AOT و قابلیتهای چندزبانه است که به توسعهدهندگان اجازه میدهد چندین زبان را در یک برنامه واحد ترکیب کنند.
GraalVM در حال توسعهی فعال است، قابلیتهای جدیدی به آن اضافه میشود و قابلیتهای موجود بهبود مییابند، بنابراین من توسعهدهندگان را تشویق میکنم که با ما همراه باشند.
برخی از نقاط عطف اخیر عبارتند از:
- خروجی ساخت ایمیج بومی جدید و کاربرپسند ( 2021-01-18 )
- پشتیبانی از جاوا ۱۷ ( ۲۰۲۲-۰۱-۱۸ )
- فعال کردن کامپایل چند لایه به طور پیشفرض برای بهبود زمان کامپایل چندزبانه ( 2021-04-20 )
بومی بهاری
به عبارت ساده - Spring Native امکان استفاده از کامپایلر تصویر بومی GraalVM را برای تبدیل برنامههای Spring به فایلهای اجرایی بومی فراهم میکند.
این فرآیند شامل انجام یک تحلیل استاتیک از برنامه شما در زمان کامپایل است تا تمام متدهای موجود در برنامه شما که از نقطه ورود قابل دسترسی هستند را پیدا کند.
این اساساً یک مفهوم "دنیای بسته" از برنامه شما ایجاد میکند، که در آن فرض میشود تمام کد در زمان کامپایل شناخته شده است و هیچ کد جدیدی اجازه بارگیری در زمان اجرا را ندارد.
لازم به ذکر است که تولید تصویر بومی یک فرآیند فشرده حافظه است که نسبت به کامپایل یک برنامه معمولی زمان بیشتری میبرد و محدودیتهایی را بر جنبههای خاصی از جاوا اعمال میکند.
در برخی موارد، برای اینکه یک برنامه با Spring Native کار کند، نیازی به تغییر کد نیست. با این حال، برخی موقعیتها برای عملکرد صحیح نیاز به پیکربندی خاص native دارند. در این موقعیتها، Spring Native اغلب نکات بومی را برای سادهسازی این فرآیند ارائه میدهد.
۳. تنظیمات/پیشپردازش
قبل از شروع پیادهسازی Spring Native، باید برنامه خود را ایجاد و مستقر کنیم تا یک مبنای عملکرد ایجاد کنیم که بعداً بتوانیم آن را با نسخه native مقایسه کنیم.
۱. ایجاد پروژه
ما با دریافت برنامه خود از start.spring.io شروع خواهیم کرد:
curl https://start.spring.io/starter.zip -d dependencies=web \
-d javaVersion=11 \
-d bootVersion=2.6.4 -o io-native-starter.zip
این برنامهی آغازین از Spring Boot 2.6.4 استفاده میکند که آخرین نسخهای است که پروژهی spring-native در زمان نگارش این مطلب پشتیبانی میکند .
توجه داشته باشید که از زمان انتشار GraalVM 21.0.3 ، میتوانید از جاوا ۱۷ نیز برای این نمونه استفاده کنید. ما همچنان برای این آموزش از جاوا ۱۱ استفاده خواهیم کرد تا پیکربندیهای مورد نیاز را به حداقل برسانیم.
وقتی فایل زیپ را در خط فرمان داشتیم، میتوانیم یک زیرشاخه برای پروژه خود ایجاد کنیم و پوشه را در آنجا از حالت فشرده خارج کنیم:
mkdir spring-native cd spring-native unzip ../io-native-starter.zip
۲. تغییرات کد
به محض اینکه پروژه را باز کنیم، به سرعت نشانهای از زندگی به آن اضافه میکنیم و عملکرد بومیان بهاری را پس از اجرای آن به نمایش میگذاریم.
فایل DemoApplication.java را ویرایش کنید تا با این مورد مطابقت داشته باشد:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.time.Instant;
@RestController
@SpringBootApplication
public class DemoApplication {
private static Instant startTime;
private static Instant readyTime;
public static void main(String[] args) {
startTime = Instant.now();
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("/")
public String index() {
return "Time between start and ApplicationReadyEvent: "
+ Duration.between(startTime, readyTime).toMillis()
+ "ms";
}
@EventListener(ApplicationReadyEvent.class)
public void ready() {
readyTime = Instant.now();
}
}
در این مرحله، برنامه پایه ما آماده اجرا است، بنابراین میتوانید یک تصویر بسازید و آن را به صورت محلی اجرا کنید تا قبل از تبدیل آن به یک برنامه بومی، ایدهای از زمان راهاندازی داشته باشید.
برای ساختن تصویرمان:
mvn spring-boot:build-image
همچنین میتوانید docker images demo برای اطلاع از اندازه تصویر پایه استفاده کنید: 
برای اجرای برنامه ما:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
۳. برنامه پایه را مستقر کنید
حالا که برنامهمان را داریم، آن را مستقر میکنیم و زمانها را یادداشت میکنیم که بعداً با زمانهای راهاندازی برنامه بومیمان مقایسه خواهیم کرد.
بسته به نوع برنامهای که میسازید، چندین هاست مختلف برای میزبانی محتوای شما وجود دارد.
با این حال، از آنجایی که مثال ما یک برنامه وب بسیار ساده و سرراست است، میتوانیم همه چیز را ساده نگه داریم و به Cloud Run تکیه کنیم.
اگر این آموزش را روی دستگاه خودتان دنبال میکنید، مطمئن شوید که ابزار gcloud CLI را نصب و بهروزرسانی کردهاید.
اگر از Cloud Shell استفاده میکنید، همه چیز به خوبی انجام میشود و میتوانید به سادگی دستور زیر را در دایرکتوری منبع اجرا کنید:
gcloud run deploy
۴. پیکربندی برنامه
۱. پیکربندی مخازن Maven
از آنجایی که این پروژه هنوز در مرحله آزمایشی است، باید برنامه خود را طوری پیکربندی کنیم که بتواند مصنوعات آزمایشی را که در مخزن مرکزی maven موجود نیستند، پیدا کند.
این شامل اضافه کردن عناصر زیر به pom.xml ما میشود که میتوانید در ویرایشگر دلخواه خود انجام دهید.
بخشهای repositories و pluginRepositories زیر را به pom خود اضافه کنید:
<repositories>
<repository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</pluginRepository>
</pluginRepositories>
۲. اضافه کردن وابستگیهایمان
در مرحله بعد، وابستگی spring-native را اضافه کنید که برای اجرای یک برنامه Spring به عنوان یک تصویر native لازم است. توجه: اگر از Gradle استفاده میکنید، این مرحله ضروری نیست.
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.11.2</version>
</dependency>
</dependencies>
۳. افزودن/فعال کردن افزونههای ما
حالا افزونه AOT را برای بهبود سازگاری و حجم تصاویر بومی اضافه کنید ( بیشتر بخوانید ):
<plugins>
<!-- ... -->
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.11.2</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
حالا ما spring-boot-maven-plugin را بهروزرسانی میکنیم تا پشتیبانی از تصویر بومی را فعال کنیم و از سازنده paketo برای ساخت تصویر بومی خود استفاده کنیم:
<plugins>
<!-- ... -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
</plugins>
توجه داشته باشید که تصویر سازنده کوچک تنها یکی از چندین گزینه است. این انتخاب خوبی برای مورد استفاده ما است زیرا کتابخانهها و ابزارهای اضافی بسیار کمی دارد که به کاهش سطح حمله ما کمک میکند.
اگر برای مثال در حال ساخت برنامهای هستید که نیاز به دسترسی به برخی کتابخانههای رایج C دارد، یا هنوز از الزامات برنامه خود مطمئن نیستید، ممکن است استفاده از یک برنامهنویس تمامعیار مناسبتر باشد.
۵. ساخت و اجرای اپلیکیشن نیتیو
وقتی همه اینها سر جای خود قرار گرفتند، باید بتوانیم ایمیج خود را بسازیم و برنامه کامپایلشده و بومی خود را اجرا کنیم.
قبل از اجرای طرح توجیهی، به چند نکته توجه کنید:
- این کار نسبت به یک ساخت معمولی زمان بیشتری میبرد (چند دقیقه)

- این فرآیند ساخت میتواند حافظه زیادی (چند گیگابایت) را اشغال کند.

- این فرآیند ساخت مستلزم آن است که سرویس داکر (Docker daemon) قابل دسترسی باشد.
- اگرچه در این مثال ما این فرآیند را به صورت دستی انجام میدهیم، شما میتوانید مراحل ساخت خود را طوری پیکربندی کنید که به طور خودکار یک پروفایل ساخت بومی را فعال کند .
برای ساختن تصویرمان:
mvn spring-boot:build-image
وقتی ساخته شد، آمادهایم تا اپلیکیشن نیتیو را در عمل ببینیم!
برای اجرای برنامه ما:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
در این مرحله، ما در موقعیت بسیار خوبی برای دیدن هر دو طرف معادله برنامه بومی هستیم.
ما کمی از زمان و استفاده اضافی از حافظه را در زمان کامپایل از دست دادهایم، اما در عوض برنامهای دریافت میکنیم که میتواند خیلی سریعتر راهاندازی شود و حافظه بسیار کمتری (بسته به حجم کار) مصرف کند.
اگر docker images demo اجرا کنیم تا اندازه تصویر بومی را با تصویر اصلی مقایسه کنیم، میتوانیم کاهش چشمگیری را مشاهده کنیم:

همچنین باید توجه داشته باشیم که در موارد استفاده پیچیدهتر، اصلاحات اضافی مورد نیاز است تا کامپایلر AOT از آنچه برنامه شما در زمان اجرا انجام خواهد داد، مطلع شود. به همین دلیل، برخی از حجمهای کاری قابل پیشبینی (مانند کارهای دستهای) ممکن است برای این کار بسیار مناسب باشند، در حالی که برخی دیگر ممکن است نیاز به ارتقاء بیشتری داشته باشند.
۶. استقرار برنامه بومی ما
برای استقرار برنامه خود در Cloud Run، باید تصویر بومی خود را در یک مدیر بسته مانند Artifact Registry قرار دهیم.
۱. آمادهسازی مخزن داکر
میتوانیم این فرآیند را با ایجاد یک مخزن (repository) آغاز کنیم:
gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"
در مرحله بعد، میخواهیم مطمئن شویم که برای ارسال به رجیستری جدید خود، احراز هویت شدهایم .
رابط خط فرمان gcloud میتواند این فرآیند را تا حد زیادی ساده کند:
gcloud auth configure-docker us-central1-docker.pkg.dev
۲. انتقال تصویر به رجیستری مصنوعات
در مرحله بعد تصویر خود را برچسب گذاری خواهیم کرد:
export PROJECT_ID=$(gcloud config list --format 'value(core.project)')
docker tag demo:0.0.1-SNAPSHOT \
us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
و سپس میتوانیم docker push برای ارسال آن به Artifact Registry استفاده کنیم:
docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
۳. استقرار در Cloud Run
اکنون آمادهایم تا ایمیجی که در Artifact Registry ذخیره کردهایم را در Cloud Run مستقر کنیم:
gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
از آنجایی که ما برنامه خود را به عنوان یک تصویر بومی ساخته و مستقر کردهایم، میتوانیم مطمئن باشیم که برنامه ما در حین اجرا، از هزینههای زیرساخت ما به بهترین نحو استفاده میکند.
میتوانید زمان شروع به کار برنامهی پایهی ما را با این برنامهی بومی جدید خودتان مقایسه کنید!

۷. خلاصه/پاکسازی
تبریک بابت ساخت و استقرار یک برنامه Spring Native در Google Cloud!
امیدوارم این آموزش شما را تشویق کند تا با پروژه Spring Native بیشتر آشنا شوید و اگر در آینده نیازهای شما را برآورده کرد، آن را در نظر داشته باشید.
اختیاری: پاکسازی و/یا غیرفعال کردن سرویس
چه برای این آزمایشگاه کد، یک پروژه گوگل کلود ایجاد کرده باشید و چه از یک پروژه موجود دوباره استفاده کنید، مراقب باشید که از هزینههای غیرضروری ناشی از منابعی که ما استفاده کردهایم، جلوگیری کنید.
شما میتوانید سرویسهای Cloud Run که ایجاد کردهایم را حذف یا غیرفعال کنید ، ایمیجی که میزبانی کردهایم را حذف کنید یا کل پروژه را متوقف کنید .
۸. منابع اضافی
اگرچه پروژه Spring Native در حال حاضر یک پروژه جدید و آزمایشی است، اما در حال حاضر منابع خوب زیادی برای کمک به کاربران اولیه برای عیبیابی مشکلات و مشارکت در آن وجود دارد:
منابع اضافی
در زیر منابع آنلاینی وجود دارد که ممکن است برای این آموزش مرتبط باشند:
- درباره نکات بومی بیشتر بدانید
- درباره GraalVM بیشتر بدانید
- چگونه مشارکت کنیم
- خطای کمبود حافظه هنگام ساخت تصاویر بومی
- خطای شروع نشدن برنامه
مجوز
این اثر تحت مجوز عمومی Creative Commons Attribution 2.0 منتشر شده است.