ကျွန်တော်တို့ Module ကို လေ့လာတဲ့ နေရာမှာ What, Why & How နဲ့ လေ့လာသွားကြရအောင်။
What is a Module?
“A Module is a group of closely related packages and resources along with a new module descriptor file.”
အနီးကပ်ဆုံးအသုံးပြုမည့် Java Packages တွေ၊ XML, Properties, Json, Images အစရှိတဲ့ Resources File တွေနဲ့ အတူ Module Descriptor File တစ်ခုနဲ့ စုစည်းထားတဲ့ Group တစ်ခုကို Module လို့ ခေါ်တယ်လို့ ဆိုပါတယ်။
သေချာပြန်စဉ်းစားကြည့်မယ်ဆိုရင် Package တွေနဲ့ Resources တွေကို စုစည်းထားတာဆိုတော့ Jar File ဟာလဲ ဒီလိုပဲ မဟုတ်ဘူးလားဆိုပြီး မေးစရာရှိလာပါမယ်။ နောက်ပြီး Module Project ကို Package လုပ်တဲ့နေရာမှာလဲ Jar ကိုပဲအသုံးပြုကြတဲ့ အတွက် Module နဲ့ Jar ဘာကွာလဲ မေးစရာရှိလာပါမယ်။
Module Vs Jar
Module နဲ့ Jar ဘာကွာလဲဆိုရင် မိမိကိုယ်ကိုယ်ပြန်ပြီး သတ်မှတ်နိုင်တဲ့ အချက်ဖြစ်ပါတယ်။ Module ထဲမှာ Module Descriptor File ကို ရေးသားနိုင်ပြီး လက်ရှိ Module ဟာ ဘယ်လို ဖြစ်တယ်ဆိုတာကို သတ်မှတ် ရေးသားနိုင်ပါတယ်။ Jar File တစ်ခုမှာ ဒါတွေကို သတ်မှတ်ရေးသားလို့ မရနိုင်ပါဘူး။
Title |
Description |
Name |
လက်ရှိရေးသားနေတဲ့ Module Name |
Dependencies |
လက်ရှိ Module ထဲမှာ အသုံးပြုလိုတဲ့ အခြား Module တွေ |
Public Packages |
အခြား Module တွေကို ပေးသုံးလိုတဲ့ လက်ရှိ Module ရဲ့ Package တွေ |
Services Offer |
အခြား Module တွေကို ပေးသုံးစေလိုတဲ့ Service တွေ |
Services Consume |
လက်ရှိ Module မှာ အသုံးပြုမည့် အခြား Module က Service တွေ |
Reflection Permissions |
အခြား ဘယ် Module တွေကို Reflection ပေးသုံးမလဲ ဆိုတဲ့ Permission သတ်မှတ်ချက်တွေ |
Why Java need Module?
Java Platform က ဘာဖြစ်လို့ Modular ဖြစ်ဖို့လိုရတာလဲ။ တနည်းပြောရမယ်ဆိုရင် ဘာတွေလုပ်မယ် ဆိုတာတွေကို Module Descriptor File မှာ ရေးသားထားဖို့လိုအပ်ရတာလဲ။ အဲ့ဒါကတော့ Class Path နဲ့ Jar Problem တွေကြောင့် ဖြစ်ပါတယ်။
Jar Dependencies
အတန်ကြီးတဲ့ System တစ်ခုကို ရေးသားကြပြီဆိုပါစို့။ အစအဆုံးကို တစ်နေရာထဲမှာ ရေးသားကြတယ် ဆိုတာ မရှိသလောက်ရှားပါတယ်။ Open Source Framework တွေ၊ Open Source Library တွေကို အသုံးပြု ရေးသားနေကြတာ များပါတယ်။ ဒီလိုပဲ အသုံးပြုနေတဲ့ Open Source Library တွေကလဲ အခြားသော Open Source Library တွေကို အသုံးပြုရေးသားနေကြပါတယ်။
ဒီလိုနဲ့ Runtime မှာ Load လုပ်ရမည့် Jar တွေက တဖြည်းဖြည်းနဲ့ တိုးပွါးလာခဲ့ရပါတယ်။ ပြီးတော့ Jar File တွေမှာလဲ မိမိ အသုံးပြုနေတဲ့ Library တွေကိုလဲ သတ်မှတ်နိုင်ခြင်းမရှိတဲ့ အတွက် အသုံးပြုနေတဲ့ Library တွေ အားလုံးကို Class Path ထဲကို ကောက်ထည့်ဖို့ပဲ ရှိပါတော့တယ်။
Jar File တွေအရမ်းများလာတဲ့ အခါမှာ Version မှန်ကန်မှုတွေကို ထိမ်းမရသိမ်းမရဖြစ်လာပါတော့တယ်။ Version ကို Update လုပ်လိုက်တိုင်း၊ Dependency တွေကို မှန်အောင် ရှာပြီးထည့်ရတဲ့ အလုပ်ဟာ ပြဿနာ တစ်ခုဖြစ်လာသလို၊ မှန်မှန်ကန်ကန် မထည့်နိုင်ပြန်ရင်လဲ၊ Class ကို မတွေ့ဘူးဆိုတဲ့ ပြဿနာဟာ မကြာမကြာ တွေ့ရလေ့ရှိပါတယ်။
ပြီးတော့ မိမိပရိုဂျက်က အသုံးပြုနေတဲ့ Open Source Library တွေကနေ Version မတူတဲ့ အခြား Open Source Library ကို သွားပြီး သုံးမိနေတာတွေလဲ ရှိနိုင်ပါတယ်။ ဒီလိုမျိုး Class Path ထဲမှာရှိတဲ့ Jar File တွေ ကြောင့် ဖြတ်တတ်တဲ့ ပြဿနာတွေကို JAR HELL လို့ လဲ ခေါ်ဆိုလေ့ရှိပါတယ်။
ဒီလို ပြဿနာတွေကို ဖြေရှင်းဖို့ ကြိုးစားခဲ့ကြတာကတော့၊ Maven တို့ Gradle တို့လို Build Tools တို့ဖြစ်ကြပါတယ်။ Maven Project တွေမှာ pom.xml ထဲမှာ အသုံးပြုလိုတဲ့ Dependency ရဲ့ Version တွေကို သတ်မှတ်ခြင်းအားဖြင့် Compile Time မှာ Jar File တွေရဲ့ Version ပြဿနာကို ဖြေရှင်းဖို့ ကြိုးစားခဲ့ကြပါတယ်။
Java Modular System မှာလဲ Module Description File မှာ အသုံးပြုမည့် Dependency တွေကို သတ်မှတ် ရေးသားစေခြင်းအားဖြင့် Compile Time မှာကော Run Time မှာပါ Jar ကြောင့် ဖြစ်တဲ့ ပြဿနာကို ဖြေရှင်းနိုင်ဖို့ ကြိုးစားလာခဲ့ကြတာဖြစ်ပါတယ်။
Public is too public
ဒါဟာ Library ထဲမှာပဲ သုံးသင့်တဲ့ Class တွေနဲ့ Library ပြင်ပကို ပေးသုံးစေချင်တဲ့ Class တွေကို ခွဲခြားပြီး မသတ်မှတ်နိုင်လို့ ဖြစ်ပါတယ်။ ဒီလို သတ်မှတ်နိုင်ခြင်း မရှိတဲ့ အတွက် Library အတွင်းမှာသာ သုံးသင့်တဲ့ Class တွေကို အပြင်ကနေ သွားသုံးနေမိတတ်ပါတယ်။ ဒါ့ကြောင့် အတွင်းပိုင်း ပြောင်းလဲမှုကို လုပ်လိုက်တဲ့ အခါမှာ အဲ့ဒီ Class ကို အပြင်က သုံးမိနေတဲ့ အတွက် ဖြစ်ရတဲ့ ပြဿနာက ပိုပြီးများမှာ ဖြစ်ပါတယ်။
ဒါဟာ Java Programming Language မှာ Library ထဲမှာသာ သုံးလို့ရမည့် အရာဆိုပြီး သတ်မှတ်နိုင်တဲ့ Accss Modifier မရှိတဲ့ အတွက် ဖြစ်ပါတယ်။
Encapsulation ဆိုတာက အပြင်ကို ထုတ်ပြသင့်တာ တွေကိုသာ ထုတ်ပြပြီး၊ အတွင်းပိုင်းမှာ သိမ်းထားသင့်တာတွေကို အပြင်က မမြင်အောင် လုပ်ထားနိုင်ခြင်း ဖြစ်ပါတယ်။ ဒီလို အပြင်က မမြင်သင့်တာတွေကို မမြင်အောင် လုပ်ထားနိုင်ခြင်းအားဖြင့် အတွင်းပိုင်း ပြောင်းလဲမှု အတွက် ပြင်ပကနေ သုံးနေတဲ့ နေရာတွေမှာ ထိခိုက်မှုမရှိအောင် ဆောင်ရွက်ထားနိုင်မှာပဲ ဖြစ်ပါတယ်။
Java Programming မှာတော့ Encapsulation ကို ဆောင်ရွက်နိုင်ဖို့ Access Modifier တွေကိုပြင်ဆင် ပေးထားပါတယ်။ Modifier တွေအနေနဲ့ private, default (package), protected နဲ့ public Modifier တွေကို အသုံးပြုပြီး လိုအပ်သလို အသုံးပြုခွင့်ကို သတ်မှတ်နိုင်အောင် ပြင်ဆင်ပေးထားပါတယ်။
Package တစ်ခုထဲမှာသာ အသုံးပြုစေလိုတယ်ဆိုရင် Default Modifier ကို အသုံးပြုနိုင်ပြီး၊ Package အပြင်ကပါ ပေးသုံးစေချင်တယ်ဆိုရင်တော့ Protected နဲ့ Public Modifier တွေကို အသုံးပြုရမှာ ဖြစ်ပါတယ်။ ဒါပေမဲ့ Public လို့ သတ်မှတ်လိုက်ပြီဆိုတာနဲ့ ဘယ်နေရာကမဆို အသုံးပြုသွားနိုင်မှာ ဖြစ်ပါတယ်။
ဒါ့ကြောင့် Library တစ်ခုထဲမှာရှိပြီး Package တွေအကြားမှာ အသုံးပြုစေလိုတဲ့ Class တွေ Interface တွေဆိုရင် ကျွန်တော်တို့ Public Modifier ကို အသုံးပြုရုံကလွဲပြီး တစ်ခြားနည်းလမ်းမရှိတော့ပါဘူး။ ဒါပေမဲ့ Public Modifier ကို သုံးမိပြီဆိုရင်လဲ Library အတွင်းမှာ ထားသုံးသင့်တဲ့ Class တွေကို ဘယ်နေရာက မဆို မြင်နေပြီး အသုံးပြုသွားနိုင်ပါတယ်။
ဒါ့ကြောင့် Library အတွင်းမှာသာ အသုံးပြုသင့်တဲ့ အရာတွေကို Library အတွင်းမှာသာ အသုံးပြုနိုင်အောင် သတ်မှတ်ထားနိုင်ခြင်းမရှိတဲ့ အတွက်၊ ပြောရမယ်ဆိုရင် Encapsulation ကို ကောင်းမွန်အောင် ဆောင်ရွက် ထားနိုင်ခြင်းမရှိတဲ့ အတွက် အတွင်းပိုင်းပြုပြင်ပြောင်းလဲမှုတွေဟာ အသုံးပြုနေတဲ့ဘက်ကို အကျိုးသက်ရောက်စေတာဖြစ်ပါတယ်။
ဒီပြဿနာတွေကို ဖြေရှင်းဖို့ အတွက် Project Jigsaw ဟာ Module Description File ထဲမှာ Public Packages တွေ၊ Consume လုပ်မည့် Service တွေ၊ Provide လုပ်မည့် Service တွေနဲ့ Reflaction ကို ခွင့်ပြုမည့် Permission တွေကို ရေးသား သတ်မှတ်နိုင်အောင် ပြင်ဆင်လာခဲ့တာဖြစ်ပါတယ်။
Because of Module
ဒီနေရာမှှာ Module ဆိုတာဘာလဲ ဆိုတာကို ပြန်ပြီး လေ့လာကြည့်ကြရအောင်။ Module တစ်ခုမှာ အသုံးပြုမည့် Package တွေနဲ့ Resources File တွေပါဝင်ကြပါမယ်။ ပြီးတော့ လက်ရှိအသုံးပြုနေတဲ့ Module ဟာ အခြားဘယ်လို Dependency တွေကို အသုံးပြုနေတာလဲ၊ ပြီးတော့ လက်ရှိ ရေးသားနေတဲ့ Module ရဲ့ Package တွေထဲက ဘယ် Package တွေကို Public အနေနဲ့ ထားမှာလဲ၊ ပြီးတော့ ဘယ်လို Service တွေကို အသုံးပြုချင်တာလဲဆိုတာနဲ့ လက်ရှိ Module ထဲက ဘယ် Class တွေကို Service အနေနဲ့ Privide လုပ်မလဲ ဆိုတာကို သတ်မှတ်လာနိုင်မှာ ဖြစ်ပါတယ်။
Modular Java ဖြစ်သွားခဲ့ရတဲ့ အတွက်ဘာတွေကောင်းသွားမှာလဲ။ ဒါကတော့ Modular System အကြောင်းကို လေ့လာသွားရင်းပဲ မြင်အောင် ကြည့်ကြရအောင်။
How to Module?
ကျွန်တော်တို့ Module ဆိုတာဘာလဲ၊ ပြီးတော့ Java Platform မှာ Module ဆိုတာကို ထည့်သွင်း အသုံးပြုဖို့ လိုအပ်ခဲ့ရတာလဲ ဆိုတာကို သိရှိပြီးတဲ့ နောက်မှာ Module တစ်ခုကို ဘယ်လို ရေးသား အသုံးပြုမလဲ ဆိုတာကို လေ့လာသွားကြပါမယ်။
Writing Module as a Library
ကျွန်တော်တို့ Eclipse IDE နဲ့ Java 11 ကို အသုံးပြုပြီး Java Project တစ်ခုကို တည်ဆောက်လိုက်ရင် module-info.java file ကို Create လုပ်မလား ဆိုတာကို မေးပါမယ်။
အထက်ပါအတိုင်း Create module-info.java ကို Check လုပ်ပြီးလဲ Module ကို အသုံးပြုတဲ့ Java Project တစ်ခုကို တည်ဆောက်နိုင်မှာ ဖြစ်ပါတယ်။ ဒီနေရာမှာတော့ အသုံးပြုလိုတဲ့ Module Name ကို ဖြည့်စွက်ပေးရ ပါမယ်။ Module Name ဟာလဲ Package Name တွေလိုပဲ Reverse Domain Name ကို ရေးသားပေးရပါမယ်။ ဒီနမူနာမှာတော့ com.jdc.hello.library ကို Module Name အဖြစ် အသုံးပြုပါမယ်။
တည်ဆောက်ပြီးတဲ့ Project ရဲ့ src folder အောက်မှာ module-info.java file ကို တည်ဆောက်ပေးသွားတာကို တွေ့ရမှာ ဖြစ်ပါတယ်။
module [Module Name] {
// module body
}
Module တစ်ခုကို ရေးသားတဲ့ နေရာမှာ module keyword ကို အသုံးပြုပြီး Module Name နဲ့ Module Body တို့ကို ရေးသားရမှာ ဖြစ်ပါတယ်။ Module Body ထဲမှာတော့ Dependency, Public Packages, Services တွေနဲ့ Reflection Permission တွေကို သတ်မှတ်ရေးသားရမှာ ဖြစ်ပါတယ်။
package com.jdc.hello;
public interface Hello {
String sayHello(String name);
}
Hello Interface ကတော့ ဒီ Library ကို အသုံးပြုလိုသူတွေကို ထုတ်ပေးမဲ့ API တစ်ခုဖြစ်ပြီး com.jdc.hello Package အောက်မှာ ရေးသားထားပါတယ်။
package com.jdc.hello.impl;
import com.jdc.hello.Hello;
public class HelloImpl implements Hello{
@Override
public String sayHello(String name) {
return String.format("Hi! %s, this is from Module.", name);
}
}
HelloImpl Class ကတော့ Hello Interface ရဲ့ Implementation Class ဖြစ်ပြီး Library ထဲမှာပဲ ထားသုံးမည့် Class ဖြစ်ပါတယ်။ Library ပြင်ပကနေ ဒီ Class ကို တိုက်ရိုက်ပေးမသုံးစေချင်တဲ့ အတွက် com.jdc.hello.impl Package အောက်မှာ ရေးသားထားပါတယ်။
package com.jdc.hello;
import com.jdc.hello.impl.HelloImpl;
public class HelloFactory {
public static Hello getHello() {
return new HelloImpl();
}
}
HelloFactory Class ကတော့ Hello Interface အတွက် Factory Class တစ်ခုဖြစ်ပါတယ်။ Hello Object ကို Library ပြင်ပကနေ အသုံးပြုလိုတဲ့ အခါမှာ HelloFactory ရဲ့ getHello() Method ကို ခေါ်ယူအသုံးပြု ရမှာ ဖြစ်ပါတယ်။ အထဲမှာတော့ HelloImpl Class ကို new Operator နဲ့ Object ဆောက်ပြီး Hello Interface အနေနဲ့ Return ပြန်ထားပါတယ်။
ပြီးတော့ HelloFactory Class ကိုလဲ Module ပြင်ပကနေ ပေးသုံးစေလိုတဲ့ အတွက် com.jdc.hello Package ထဲမှာ ရေးသားထားပါတယ်။
module com.jdc.hello.library {
exports com.jdc.hello;
}
Module Descriptor ထဲမှာတော့ Module ပြင်ပကေန ပေးသုံးစေလိုတဲ့ com.jdc.hello Package ကို Export လုပ်မယ်လို့ ရေးသားထားပါတယ်။ ဤနည်းအားဖြင့် com.jdc.hello Package အောက်မှာရှိတဲ့ Hello နဲ့ HelloFactory ကိုသာ Module ပြင်ပကနေ အသုံးပြုနိုင်မှာ ဖြစ်ပြီး၊ Module အတွင်းမှာသာ အသုံးပြုစေလိုတဲ့ HelloImpl Class ကို Module ပြင်ပကနေ အသုံးပြုလို့ ရမှာ မဟုတ်တော့ပါဘူး။
အကြောင်းတစ်မျိုးမျိုးကြောင့် HelloImpl Class အတွင်းပိုင်းမှာ ပြုပြင်ပြောင်းလဲမှုတွေ ရေးသားဖို့လိုလာခဲ့ရင် တောင်မှ Module ပြင်ပကနေ သုံးမထားတဲ့ အတွက် အသုံးပြုသူတွေဘက်ကို ထိခိုက်မှုတွေ ရှိလာစရာ အကြောင်း ရှိတော့မှာ မဟုတ်ပါဘူး။
အခုရေးသားထားတဲ့ Project ကို Jar File အဖြစ် Export လုပ်ပြီး အခြား Project တစ်ခုမှာ ပြန်ပြီး အသုံးပြုကြည့်ပါမယ်။
Using Module Library
ရှေ့မှာ ရေးသားထားတဲ့ hello-lib.jar File ကို အသုံးပြုဖို့ အတွက် Eclipse IDE နဲ့ hello-app ဆိုတဲ့ Java Project တစ်ခုကို တည်ဆောက်ပါတယ်။ Project Root Folder အောက်မှာ hello-lib.jar file ကို ထည့်ထား လိုက်ပါတယ်။
Module ကို Project ထဲကနေ အသုံးပြုဖို့ဆိုရင် Module Path ထဲမှာ သွားပြီး သတ်မှတ်ပေးရမှာ ဖြစ်ပါတယ်။
အထက်ပါအတိုင်း Module Path ထဲကို hello-lib.jar File ကို ဖြည့်စွက်ထားမှသာ Module Dependency အနေနဲ့ အသုံးပြုနိုင်မှာ ဖြစ်ပါတယ်။
module com.jdc.hello.app {
requires com.jdc.hello.library;
}
Hello App project ရဲ့ Module Descriptor File ထဲမှာတော့ com.jdc.hello.library Module ကို အသုံးပြုမယ် ဆိုပြီး requires statement နဲ့ ရေးသားရပါမယ်။
တစ်ခုသတိထားဖို့လိုအပ်တာက Module Path ထဲမှာ Module Library File ကို ထည့်ထားတာနဲ့ အသုံးပြုလို့ရတာမဟုတ်ပါဘူး။ သုံးချင်တယ်ဆိုရင် အသုံးပြုမည့် Module တွေကို requires statement နဲ့ သတ်မှတ်ထားမှသာ အသုံးပြုနိုင်မှာ ဖြစ်ပါတယ်။
public class UsingModuleDemo {
public static void main(String[] args) {
Hello hello = HelloFactory.getHello();
String message = hello.sayHello("Jigsaw");
System.out.println(message);
}
}
Module Dependency တွေကို သတ်မှတ်ရေးသားပြီးပြီဆိုရင်တော့ Library ထဲက Export လုပ်ထားတဲ့ Class တွေကို အသုံးပြုရေးသားနိုင်မှာ ဖြစ်ပါတယ်။ HelloImpl Class မျိုးလို Library Module ကနေ Export မလုပ်ထားတဲ့ Class တွေကို သုံးမိရင်တော့ Compile Time မှာတင် Error ဖြစ်စေမှာ ဖြစ်ပါတယ်။
Backward Compatibility
Module Project တွေကို ရေးသားတဲ့ နေရာမှာ Module Library တွေကိုကော၊ Legacy Jar Library တွေကို ရေးသားအသုံးပြုနိုင်ဖို့လိုအပ်ပါတယ်။ အဲ့ဒီအတွက် Java Platform Module System က Automatic Module နဲ့ Unnamed Module တို့ကို ပြင်ဆင်ပေးထားပါတယ်။
Automatic Module
ရေးသားနေတဲ့ Project ရဲ့ Module Path ထဲကို Jar File တွေကို Unofficial Module အနေနဲ့ ထည့်သွင်း အသုံးပြုနိုင်ပါတယ်။ Module Name ကတော့ Jar File Name ဖြစ်လာမှာ ဖြစ်ပြီး၊ Automatic Module လို့ ခေါ်ဆိုပါတယ်။ Module Path ထဲကနေ Load လုပ်ထားတဲ့ Module တွေအားလုံးကို Automatic Modules အတွက် Full Read Access ကို ရရှိစေမှာ ဖြစ်ပါတယ်။
Unnamed Module
Class တွေ Jar Library တွေကို Module Path မဟုတ်ပဲ Class Path ထဲကို ထည့်ထားမယ်ဆိုရင် အဲ့ဒီ Class တွေအားလုံးကို Unnamed Module ထဲက Class တွေ အဖြစ် သတ်မှတ်သွားပါမယ်။ Module သုံးလို့မရတဲ့ Java Version နဲ့ ရေးသားထားတာတွေကို လက်ရှိ Module Project ထဲကို ထည့်သွင်း အသုံးပြုနိုင်အောင် စီစဉ်ထားတာလဲ ဖြစ်ပါတယ်။
ဤနည်းအားဖြင့် Module မပေါ်ခင်တုန်းက ရေးသားထားတဲ့ Library တွေကို Module Project တွေမှ အသုံးပြုနိုင်အောင် ပြင်ဆင်ပေးထားပါတယ်။
ဆက်ပါဦးမည်။
မင်းလွင်