معرفی  (JSR 376)JPMS

تو این مقاله میخوایم ببینیم JPMS یا همون Java Platform Module System که در جاوا 9 معرفی شد دقیقا چیه.طبق گفته ی oracle یکی از مهمترین فناوری مهندسی نرم افزار در جاواست که نتیجه ای از پروژه ی Jigsaw بوده و کمک میکنه بهره وری پروژه بیشتر بشه.پروژه ی ماژولار کردن Java از نسخه ی 7 کلید خورد و نهایتا در نسخه ی 9 اضافه شد.هدف JPMS رفع دو مشکل بزرگ در جاوا بود classpath ها وmonolithic jdk :در حالت عادی هنگامی که کتابخونه (Jar file) هایی که در خود Jdk موجود نیستند استفاده میکنیم باید با -classpath هنگام ران کردن برنامه ادرسیش رو بدیم. حالا اگر اشتباهی دو تا لایبری با ورژن های متفاوت ادد کرده باشیم چی؟مشکلی به وجود میاد به اسم Jar Hellخب حالا مشکل این Jar hell چیه؟وقتی یه متدی مثلا از کتابخونه ی x رو فراخوانی کردیم که در ورژن 1 هست ولی در ورژن 2 حذف شده و هر دو توی classpath ادد شدن یکیشون رندوم استفاده میشن! درواقع هنگام کامپایل هیچ خبری نیست ولی موقع ران شدن توی یکسری شرایط به ارور بر میخورید.وmonolithic jdk چیه؟ کلاس های جاوا به شدت به هم وابستگی داشتن و وقتی یک برنامه ی ساده که مثلا میخواستیم چیزی رو فقط پرینت کنیم (مثلا hello world) باید کل JRE رو روی سیستممون نصب میکردیم مثلا چیزی حدود 50مگابایت فقط برای hello world و حالا اگر برنامه ی سخت افزاری میخواستیم بنویسیم با محدودیت حافظه هم مواجه میشدیم و...نتیجه ی ماژولار کردن Java بهبود عملکرد خود Jvm هم هست .JSR 376 (Java Specification Requests)نشون داده که عملکرد بهینه سازی Jvm وقتی که ماژول ها تفکیک شدن و فقط اونایی که لازم هستن اضافه شدن بهبود پیدا کردهیکی دیگه از مواردی که با JPMS به ارمغان اومده و درواقع از مهمترین اهداف JPMS بود کپسوله سازی قدرتمند تر هست (Strong encapsulation):پکیج های یک ماژول فقط در صورتی در اختیار دیگر ماژول ها قرار میگیرن که در ماژول به صراحت اعلام بشه میخواد فلان پکیج رو در اختیار دیگر ماژول ها قرار بده ! تازه اوناهم فقط وقتی میتونن ازش استفاده کنن که به صراحت اعلام کنن بهش نیاز دارن.این حرکت کپسوله سازی رو قوی تر میکنه برای مثال کلاس هایی در خود جاوا هست (internal Api ) که فقط برای APIداخلی JDK طراحی شدن ولی توسط کلاس های خارجی مورداستفاده قرار میگرفتن این قضیه میتونه باعث کاهش امنیت بشه ولی با JPMS این مشکل هم مرتفع شده.مشاهده ی لیست ماژول های جاوا در سیستم های مختلف :java --list-modulesو اگه بخوایم اطلاعات یک ماژول رو به دست بیاریم :java --describe-module java.sqlنحوه ی تعریف module :ابتدا باید یک فایل به نام module-info.java ایجاد کنید و اون رو در root folder قرار بدید (مثلا پکیج اصلی برنامه هست com.example.myprogram باید خارج از این پکیج و دقیقا کنار پوشه ی com باشه .. درونش نباشه ها فقط توی مسیری باشه که پوشه ی com هست):module modulename { }فایل باید به صورت بالا شروع باشه با نام ماژول.کلمات کلیدی رزرو شده در تعریف ماژول هم اینها هستند:exports, module, open, opens, provides, requires, uses, with, to transitive.بریم سراغ توضیح کلمات کلیدی بالا :(کد هارو رو درون اکولادی که بالا تعریف کردیم مینویسیم) requires modulename;همینطور که از اسمش مشخصه داره میگه اقای jvm این ماژول من وابستگی داره به فلان ماژول و تو اددش کن به classpath من.البته برای ماژول هایی که فقط موقع کامپایل نیازشون داریم از حرکت زیر استفاده میکنیم:requires static modulename;اما خب دقیقا منظورمون از ماژول هایی که فقط زمان کامپایل نیازشون داریم چیه؟ خیلی از کتابخونه ها فقط زمان کامپایل به کار میان و اصلا قرار نیست زمان رانتایم استفاده بشن (مثل Lombok که درواقع بر اساس annotation هایی که زدیم میاد سورس کد رو تولید میکنه نکته ی مهم اینکه تمام انوتیشن هایی که فقط زمان کامپایل باید در دسترس باشن RetentionPolicy شون CLASS یا SOURCE هست ) این ماژول ها برای زمان رانتایم اختیاری هستن.یه چیز دیگه داریم به اسم :requires transitive modulename;اینم میاد میگه ای کسایی که دارید از ماژول من استفاده میکنید و ای منی که خودم دارم از یه ماژول دیگه استفاده میکنم که کار شمارو انجام بدم.. پس شما بصورت ضمنی داری از اون ماژول منم استفاده میکنی.فرض کنید یک ماژول Bar دارید که اون داره از ماژول Drink استفاده میکنه و یه همچین کدی هم در نظر بگیرید :// in module _bar_ public class Bar { // `Drink` comes from the module _drink_, // which _bar_ requires public Drink buyDrink() { /* ... */ } }حالا اگر ماژولی به نام customer که داره از Bar استفاده میکنه بیاد متد buyDrink رو کال کنه خطای کامپایل رخ میده با این منطق که ماژول customer به ماژول Drink دسترسی نداره و باید با استفاده از requiers Drink; توی فایل Module-info خودش مشخص کنه . اما راه حل منطقی تر اینه :مایی که کتابخونه نوشتیم باید تمام این داستانارو گردن بگیریم و بگیم هرکس از ما استفاده کرد درواقع از فلان ماژول هم استفاده کرده. پس توی فایل module-info ای که در ماژول Bar هست مینویسیم: requires transitive drink; حالا دیگه ماژول customer میتونه راحت buyDrink رو کال کنه.کلمه کلیدی exports مشخص میکنه ماژول ما میتونه برای دیگر ماژول ها قابل دسترسی باشه:exports modulename;حالا اگر بخوایم مشخص کنیم ماژول ما فقط برای بعضی ماژولای دیگه قابل مشاهده باشه چی ؟(بهش میگن qualified export) خیلی راحت میایم از exports...to استفاده میکنیم :exports mymodulename to othermodulename;exports mymodulename to othermodulename2;exports mymodulename to othermodulename3;و الی اخر..میدونیم که توسط رفلکشن (reflection) اعضای private کلاس و... قابل دسترسیه ولی توی jpms این خبرا نیست باید اجازه بدیم که ماژول دیگری ایا میتونه توسط رفلکشن از ماژول ما استفاده کنه یا نه:opens mymodulename;و اگر بخوایم فقط برای یک ماژول خاص باشه مثل exports...to از opens...to استفاده میکنیم.مثال opens رو میتونیم توی یک پروژه ی جاوا اف ایکسی که داخل fxml اومدیم برای یک ویو fx:id تنظیم کردیم و توی کنترل با انوتیشن @FXML اون رو گرفتیم. این حرکت با reflection انجام میشه و باید حتما ماژولمون برای javafx.fxml چی باشه؟افرین ! opens باشه:opens mymodule to javafx.fxml;اگر کمی دقت کنید دیگه ما برای کپسوله سازی فقط با private, protected ,public, package طرف نیستیم بلکه با پابلیکی که فقط برای ماژول ما پابلیکه. پابلیکی که برای همه ی ماژول ها پابلیکه و پابلیکی که برای بعضی ماژول ها پابلیک هم طرف هستیم که این دقیقا میشه همون Strong encapsulationدو مورد دیگه میمونه به نام uses و provides..with که مربوط میشه به ServiceLoader که خواندنش رو به خودتون واگذار میکنم .در مطلب بعدی اینکه چطور با ماژولاریتی کردن پروژه میتونیم یک JDKسفارشی بسازیم که حجمش فقط بر اساس ماژول های مورد نیاز ما باشه صحبت میکنیم.منابع :Openjfx jigsawUnderstanding Java 9 ModulesJavaland