جاوا 9 و Jigsaw

جاوا ۹ قرار است در اواخر شهریور، آنطور که اعلام شده ۲۱ سپتامبر ۲۰۱۷، به صورت عمومی عرضه شود. در مقاله گذشته در مورد یکی از قابلیتهای جدید جاوا ۹ یعنی JShell صحبت کردیم. در این مقاله به صورت ساده، انقلابیترین و جنجالی ترین اتفاقی که در اکوسیستم جاوا رخ داده، یعنی پروژه Jigsaw را بررسی خواهیم کرد.
مقدمه:
اگر در این چند ماهه اخبار مرتبط با جاوا ۹ را دنبال کرده باشید، حرف و حدیثهای زیادی در مورد ماژولاریتی (Modularity)، بستر ماژولار جاوا (Java Module Platform System) و ماژولها (Module) شنیدهاید. اما اصلا همه این جار و جنجالها برای چیست؟ چرا ماژولار شدن JDK انقدر اتفاق مهمی است؟ ماژولاریتی چه کمکی به ما میکند و به چه دردی میخورد؟ چرا به ماژولاریتی احتیاج داریم و چگونه میتوانیم پروژه ماژولار خودمان را بسازیم؟
احتمالا بخش زیادی از این پرسشها با خواندن این مقاله رفع خواهد شد و قدمی برای حرکت به سوی جاوا ۹ برای شما خواهد بود.
چیستی و چرایی:
نگهداری (Maintainability) یکی از مهمترین مواردی است که در فرایند توسعه و رشد یک سیستم نرمافزاری نقش ایفا میکند. ما برنامههایی مقیاسپذیر، خوانا با قابلیت استفاده مجدد و قابل فهم میخواهیم تا بتوانیم هزینه نگهداری سیستمهای نرمافزاری خود را کاهش دهیم. پس کدهای خود را خوانا مینویسیم، اسم کلاسها را قابل فهم انتخاب میکنیم و کدهای مرتبط را درون پکیجهای مشخصی قرار میدهیم تا آنها را از هم جدا کنیم. همه این کارها برای خواناتر شدن کدها و مشخص بودن وظایف و هدف هریک از این اجزا است. اما اگر سیستم بزرگی داشته باشیم که ۱۰۰ها پکیج در کنار هم باشند آنوقت چی؟ احتمالا باید با ابزارهای مختلف، Dependency بین این پکیجها را پیدا کنیم و بفهمیم آنها چه ارتباطی با یکدیگر دارند. به نظر چندان راه حل خواناای نیست. هدف ما خوانایی بالاتر است، حتی اگر به قیمت چند خط کد اضافه زدن و انتخاب اسامی طولانیتر باشد. برای همین پکیجها و مکانیزم فعلی چندان کارا به نظر نمیرسد. ما چیزی بیشتر از این نیاز داریم.
مشکلی که در جاوا وجود دارد، classpath و نحوه اجرای برنامهها است. در حال حاضر تمامی کتابخانهها و jar فایلهایی که استفاده کردهاید، اطلاعات مربوط به کلاسها و … در درون یک فایل classpath گنده قرار میگیرد و دیگر هیچ سطح منطقی جدا کنندهای بین آنها برقرار نمیشود. می توان سناریوهایی را تصور کرد که از یک کلاس با چند ورژن مختلف در این jar فایلها موجود باشند. مشکلی که به وجود خواهد آمد این است که در هنگام اجرای برنامه Java Class Loader تنها یک ورژن از این کلاسها را میتواند بارگذاری کند و این میتواند باعث ابهام در نحوه کار کردن برنامه و حتی ایجاد خطا در حین اجرای برنامه شود و شما سرگردان برای پیدا کردن مشکل باشید. در حقیقت این سناریو آنقدر تکرار شده که حتی نام مخصوص به خود را دارد! جهنم jar فایل ها یا JAR Hell که می توانید در سایت DZone در مورد آن بخوانید.
اما classpath یک مشکل دیگر هم دارد. یک اصلی در طراحی سامانهها وجود دارد که میگوید اگر قرار است خراب شوی، همان اول خراب شو. ممکن است در محیط توسعه شما، کتابخانهها و کلاسهایی وجود داشته باشند که به هر دلیلی این کتابخانهها در محیط اجرایی وجود نداشته باشند. از آنجایی که تا زمانی که از کلاس استفاده نشده باشد، در حافظه بارگذاری نمیشود، پس احتمالا وقتی در خانه و در خواب راحت هستید، سیستم شما بخاطر JavaClassDefError کرش خواهد کرد و از خواب ناز خود بیدار خواهید شد، پس چه بهتر که همان ابتدای اجرای برنامه متوجه این ایراد شوید و آن را رفع کنید. اما بزرگترین اشکال این است که تمامی کلاسهای موجود در classpath به همدیگر دسترسی دارند. این نقض آشکاری از اصل محصورسازی (Encapsulation) است. مکانیزمی که نبودش باعث شده بسیاری از APIهای داخلی JDK توسط توسعهدهندگان استفاده شود مانند sun.misc.Base64Encoder یا sun.misc.Unsafe که همگی جزو Internal APIهای JDK است. هدف ما این است که کدها، متدها و کلاسهایی که قرار نیست از بیرون از آنها استفاده شود را پنهان کنیم. اما با مکانیزم فعلی چنین امری ممکن نیست. پس ما نیاز به سطح بالاتر و قویتری از محصورسازی (Strong Encapsulation) نیاز داریم.
داشتن یک پلتفرم ماژولار و ماژولار شدن JDK به توسعهدهندگان آن کمک میکند چنین پلتفرم قدیمی، گنده و بزرگی را مدیریت کنند و به جلو حرکت دهند. آنطور که معماران آن میگویند، الان تنها فرصتی بود که به سمت ماژولار کردن JDK پیش بروند وگرنه در آینده هرگز چنین اتفاقی ممکن نمیشد. ماژولاریتی علاوه بر کمک کردن به توسعهدهندگان JDK برای حرکت آن رو به جلو، فرصتی استثناییتر را برای ما ایجاد میکند. تصور کنید که یک ماشین حساب ساده تحت کنسول نوشتهاید، نه از امکانات JavaEE استفاده میکنید، نه کاری به CORBA دارید، نه JPA. احتمالا تنها چیزی که نیاز دارید موارد پایهای است. اما برای اجرا و توزیع برنامهتان نیاز به یک JRE کامل و حجیم دارید که احتمالا راه انداختنش هم چندان ارزان نیست. اما با ماژولار شدن جاوا شما میتوانید فقط ماژولهایی که استفاده کردهاید را در قالب یک Runtime Image داشته باشید که دیگر چیزهای اضافیای که از آنها استفاده نکردهاید را ندارد. با این قابلیت، حجم برنامهها کم شده و شما میتوانید از دستگاههای Embedded که حافظه محدودتری دارند هم استفاده کنید.
ماژولها میتوانند این مشکلات را حل کنند. اما اصلا ماژول چیست؟ ماژول دارای یک اسم است، کد های مرتبط و پکیج ها را در درون خود نگه میدارد و کامل است به این معنی که یک کار مشخص را انجام میدهد و وظایفش را از طریق APIهای مشخص شده در دسترس دیگران قرار می دهد. یک ماژول به صورت صریح بیان میکند برای کار کردن به چه ماژولهایی نیاز دارد و کدام بخش از پکیج هایش برای دیگر ماژولها قابل دسترسی است. اینگونه به سادگی میتوانیم وابستگی بین ماژولها را تشخیص دهیم، بر روی Internal API هایمان کنترل داشته باشیم و در صورت نبود یک ماژول در آغاز برنامه از نبود آن فورا مطلع شویم و در صورت وجود هرگونه تداخلی به سرعت به خطا بربخوریم.
این گراف حال و هوای این روزهای JDK است. در پایین پایین ماژول java.base را داریم که تمامی ماژولهای دیگر خواه و ناخواه به آن وابسته هستند. این گراف یک خاصیت جالب دیگر نیز دارد. احتمالا شنیدهاید که Circular Dependency یا وابستگی حلقوی طراحی مناسبی نیست و میتواند دردسرزا باشد. در واقع این گراف، یک گراف جهتدار بدون دور یا DAG است و شما نمیتوانید بین ماژول هایتان وابستگی حلقوی داشته باشید.
عکس زیر در سادهترین شکل نشان میدهد یک ماژول چه اجزائی دارد:
هر ماژول دارای یک توضیحدهنده یا توصیفگر است که فایلی به نام module-info با پسوند java است. در این فایل نام ماژول خود را مینویسیم، به صورت واضح بیان میکنیم که ماژولمان از چه ماژولهای دیگری استفاده میکند و چه پکیجهایی از این ماژول برای دیگر ماژولها قابل دسترسی و دیدن است.
به عنوان مثال میتوانید ببینید ماژول java.sql چه ماژولهایی را استفاده میکند و کدام پکیج خود را برای دیگر ماژولها در دسترس قرار داده است:
شکل زیر بیان کننده سادهترین توصیف از یک ماژول است:
چگونه:
خوب قبل از همه چیز نیاز دارید که جاوا ۹ را دانلود و نصب کنید. میتوانید آخرین ورژن JDK را از این لینک دریافت کنید.
$ java -version java version "9" Java(TM) SE Runtime Environment (build 9+181) Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode
حال میخواهیم با استفاده از IntelliJ IDEA اولین پروژه ماژولار خود را بسازیم:
برای ساختن یک ماژول بر روی پروژه خود راست کلیک کنید و از قسمت new گزینه module را انتخاب کنید:
بعد از ساختن یک ماژول نیاز دارید که فایل module-info را به صورت جداگانه بسازید. برا ساختن این فایل روی src کلیک کنید و از قسمت new گزینه module-info را انتخاب کنید:
من پروژهای با دو ماژول به نامهای com.mhrimaz.logic و com.mhrimaz.gui ساختهام که ساختار این پروژه به شکل زیر است:
در ماژول com.mhrimaz.logic دو کلاس Greeting و InternalGreeting وجود دارد.
محتویات کلاس Greeting:
package com.mhrimaz.logic; public class Greeting { public static String sayHello(String name){ return "Hello, " + name; } }
محتویات کلاس InternalGreeting:
package com.mhrimaz.logic.internals; public class InternalGreeting { public static String sayHello(String name){ return "Hello, This Greeting is internal dear "+ name; } }
محتویات فایل module-info:
module com.mhrimaz.logic { exports com.mhrimaz.logic; }
همانطور که میبینید در فایل module-info ابتدا نام ماژول آورده شده و سپس پکیج com.mhrimaz.logic (دقت کنید که هر چند نام این پکیج با نام ماژولمان یکسان است اما شما فقط می توانید پکیجها را export کنید) را برای ماژولهای دیگر قابل دسترسی کردهایم و دیگر به پکیج com.mhrimaz.logic.internals دسترسی ندادهایم.
محتوایت برنامه اصلی MainApplication که یک برنامه ساده JavaFXای است (اگر نمیدانید JavaFX چیست به این مطلب مراجعه کنید):
package com.mhrimaz.gui; import com.mhrimaz.logic.Greeting; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class MainApplication extends Application { @Override public void start(Stage primaryStage) throws Exception { Label label = new Label(Greeting.sayHello("Java")); StackPane pane = new StackPane(); pane.getChildren().add(label); Scene scene = new Scene(pane); primaryStage.setScene(scene); primaryStage.show(); } }
برنامه سادهای که نوشتهایم فقط یک پنجره است که بر روی آن hello, Java نوشته میشود. این ماژول در نگاه اول نیاز به دو ماژول دیگر JavaFX یعنی javafx.base و javafx.controls دارد. همینطور برای دسترسی به کلاس Greeting نیاز به دسترسی به ماژول com.mhrimaz.logic دارد. پس احتمالا محتویات فایل module-info این ماژول به شکل زیر است:
module com.mhrimaz.gui { requires javafx.base; requires javafx.controls; requires com.mhrimaz.logic; }
با اجرای برنامه به خطای زیر برمیخوریم:
Caused by: java.lang.IllegalAccessException: class com.sun.javafx.application.LauncherImpl (in module javafx.graphics) cannot access class com.mhrimaz.gui.MainApplication (in module com.mhrimaz.gui) because module com.mhrimaz.gui does not export com.mhrimaz.gui to module javafx.graphics
در خطا به وضوح توضیح داده شده که ماژول javafx.graphics برا انجام عملیاتهای خود نیاز به دسترسی به ماژول com.mhrimaz.gui دارد، به همین دلیل ما باید پکیج com.mhrimaz.gui از ماژول com.mhrimaz.gui را برای ماژول javafx.graphics قابل دسترس و export کنیم. (همانطور که میبینید میتوانید یک پکیج را برای ماژولهای خاصی قابل دسترس کنید.)
محتویات فایل module-info به شکل زیر خواهد شد:
module com.mhrimaz.gui { requires javafx.base; requires javafx.controls; requires com.mhrimaz.logic; exports com.mhrimaz.gui to javafx.graphics; }
حال بدون مشکل میتوانیم برنامه خودمان را اجرا کنیم. نتیجه بیشتر شبیه یک باگ در پیاده سازی JavaFX است، اما در هر صورت خروجیای که خواهید گرفت به شکل زیر است:
داستان ماژولها به همین جا ختم نمیشود. ریزه کاریها و نکات بسیار زیادی میتوان در ماژولاریتی گفت. برای مطالعه بیشتر میتوانید به دو کتاب خوب و جدید Java 9 Revealed و Java 9 Modularity مراجعه کنید. اگر میخواهید کمی بیشتر تمرین کنید و با ماژولها دست و پنجه نرم کنید پیشنهاد میشود که به این مخزن در گیتهاب نگاهی بیاندازید.
اصل این مطلب به انگلیسی در این سایت و در سایت DZone منتشر شده.
سلام، من هیچ سررشته ای از جاوا ندارم و تازه میخوام شروع کنم. و همین الان هم خبردار شدم که شما قراره امتحان عملی جاوا رو در آبان برگزار کنید. میخواستم بدونم شانسی دارم که از الان شروع کنم و با آموزش های شما و جاهای دیگه رتبه خوبی کسب کنم در آزمونتون؟
سلام
شروع کنید. تو این دو ماه میتونید فیلمهای آموزشی جاواکاپ رو کامل ببینید و همزمان کدنویسی هم کنید. برای هر جلسه آموزش، یک یا چند سوال عملی خودآموز هم وجود داره که سبکشون مشابه آزمون جاواکاپ هست.
برای شرکت در آزمون هم فقط به هدف کسب رتبه نیاید. مهمترین دستاورد این آزمونها، محک زدن خودتونه
ممنون از آموزش های مفیدتون.