Java 9 Yenilikleri-2-Modüler Java
Bu makalede JDK 9 ile gelen modüler olma yaklaşımından bahsetmeye çalışacağız. Yol haritamız aşağıdaki gibi olacaktır.
- Project Jigsaw
- Modülerlik
- Modül
- JDK 9 Öncesi
- JDK 9 – Java Platform Module System
Project Jigsaw
20 yılı aşkın süredir var olan Java’ya, modüler yapıyı eklemek pek de kolay olmadı. Ağustos 2008’de başlayan yolculuk, Ağustos 2014 de koda dönüşmeye başladı. Temmuz 2017 itibari ile de proje tamama ermiş oldu.
Project Jigsaw’ın amaçları;
* Geliştiricilerin, kütüphaneleri veya büyük çaplı uygulamaları geliştirmesini ve bakımını kolaylaştırmak.
* Java SE platform uygulamalarının ve JDK’nın güvenliğini ve bakım yapılabilirliğini geliştirmek
* Java uygulama performanslarını geliştirmek
* Cloud ortamlarda ve mobile vb küçük cihazlarda ölçeklenebilir Java platformu oluşturmak
Bu amaçlara ulaşabilmek için, 7 adet geliştirme spesifikasyonu oluşturuldu ve projenin adına jigsaw denildi.
* Modular JDK
* Modular Source Code
* Modular Run-Time Images
* Encapsulate Most Internal APIs
* Module System
* jlink : The Java Linker
* Java Platform Module System
Modülerlik
Modülerlik kavramı hayatın bir çok alanında karşımıza çıkar. Modülerlik birbirinden bağımsız ama bir biri ile uyumlu parçalardan(modül) ihtiyaca göre bir bütün elde edebilme yaklaşımıdır.
Yazılım açısından baktığımızda ise bir yazılım dizayn tipidir. Örneğin Spring modüler yaklaşım üzerine inşa edilmiş bir çatıdır. Istediğimiz modülü ekleyip, özelliklerini kullanırız. Modül ekleme ve çıkarma işlemleri uygulamamızın ana yapısını etkilemez. Modüller bir araya gelip daha işlevsel bir yapı oluşturur. Bu sayede, kullanmayacağımız modülleri uygulamamız içerisine almamış oluruz.
Modül
Modulü ise şöyle tanımlayabiliriz;
* kendi kendine yetebilen
* farklı modüller ile çalışabilen
* kendisini ve diğer modüller ile etkileşimini tanımlayan metadatası olan
* kod ve çeşitli kaynaklar içeren yazılım birimleridir.
Bu tanım üzerinden yola çıkarsak bir modülün uyması gereken bazı ilkeler vardır;
*Bir modül, diğer modüllerin erişimini kontrol edebilmelidir. Diğer bir değiş ile, dışarıya açacağı kodu kendisi belirlemelidir.
*Bir modül iyi tanımlanmış arayüzlere sahip olmalıdır. Diğer modüller ile bunlar üzerinden etkileşime girecektir.
*Bir modül kendi bağımlılıklarını, kendi tanımlayabilmelidir.
JDK 9 Öncesi
Her yazılımcının bir projeye başlarken ki hedefi, katmanları birbirinden kesin hatlar ile ayrılmış, katmanlar arası iletişim arayüzleri net bir şekilde belirlenmiş ve bu arayüzler dışında katmanların birbirine erişiminin olmadığı bir yapı kurabilmektir. JDK 9 öncesi bunu tam anlamı ile gerçekleştiremiyorduk.
Modül ilkeleri üzerinden JDK 9 öncesini inceleyelim;
Encapsulation paket ve access modifier’ların bir arada kullanılması ile oluşturulmaya çalışılıyordu. Bu çaba kısıtlı bir encapsulation sunuyordu. Paket ve access modifier kombinasyonlarını arayüz ile desteleyebiliyorduk fakat bu yapıyı oluşturmanın zorlukları vardı. Ayrıca public arayüzlerin bir yere yazılmaması nedeni ile takibi zor oluyordu.
Bağımlılıkları ise proje bazlı olarak maven, gradle vb araçlar ile yapıyorduk. Kütüphaneler arası bağımlılıklar bu araçlar ile düzenleniyordu. Bağımlılıkları kendimizin tanımlaması, uygulama çalışırken classpathe set etmemiz gerekiyordu. Ya da maven vb araçlar ile classpath’i düzenliyorduk. Bağımlılıklarda yapılacak bir hata runtime’da ilgili sınıfa ihtiyaç duyulduğunda exception olarak karşımıza çıkıyordu. Aynı sınıfın farklı versiyonlarının yüklenmesi ile veya ClassNotFoundException ile java ile uğraşan herkes bir kaç defa karşılaşmıştır.
Java Platform Module System
Java Platform Module System spesifikasyonu ile javaya modul sistemi adapte edildi.
Bir java modulü istenilen paketi dışarı açabilir, veya encapsule edebilir ayrıca diğer modullere bağımlılıklarını kesin bir şekilde belirtir. Gördüğümüz gibi, bir modul olabilmenin şartları artık tam olarak karşılanabilmektedir. Bir örnek üzerinden modul yapısına giriş yapalım.
Çok basit bir mesaj kuyruğu uygulaması yapalım. Örneğimizi Eclipse Oxygen 4.7.1a idesi ile JDK-9.0.1 ile yapacağız.
İki ayrı projemiz olacak. QueueApp isimli proje ana projemiz olacak. RepositoryUtil isimli projeyide, ana projemizin module path’ine ekleyeceğiz (Artık classpath yok). QuqueApp isimli uygulamamız, mesajları farklı kaynaklardan alıp, aldığı mesajda belirtildiği şekilde hedef sistemlere gönderecek. Biz sadece hedef sistemler kısmını yapacağız.
QueueApp, RepositoryUtil modülüne sadece Sender isimli sınıf üzerinden erişebilecek. Diğer sınıflarını kullanamayacak.
İlk olarak modül projemizden başlayalım.
1* RepositoryUtil isimli bir proje oluşturuyoruz.
2* 4 adet paket oluşturuyoruz.
- io.bilisim.integration.entity
- io.bilisim.processor
- io.bilisim.sender
- io.bilisim.template
io.bilisim.integration.entity paketimizden başlayalım. Bu paket içerisinde
TargetType enum tipimiz ve SenderEntity isimli bir sınıfımız olacak.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package io.bilisim.integration.entity; public enum TargetType { CSV, XML, DB, REST, CONSOLE; private TargetType() { } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
package io.bilisim.integration.entity; public class SenderEntity { private String field1; private String field2; private String field3; private TargetType targetType; public SenderEntity(String field1, String field2, String field3,TargetType targetType) { super(); this.field1 = field1; this.field2 = field2; this.field3 = field3; this.targetType = targetType; } public String getField1() { return field1; } public String getField2() { return field2; } public String getField3() { return field3; } public TargetType getTargetType() { return targetType; } } |
io.bilisim.template paketimiz ile devam edelim. IProcessor isimli bir interface yapacağız. Processor’lerimiz bu interface’i implemente edecek.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package io.bilisim.template; import io.bilisim.integration.entity.SenderEntity; public interface IProcessor { public void sendEntity(SenderEntity entity); } |
io.bilisim.processor paketimiz ile devam edelim. Bu paket içerisinde hedef sistemlere mesajımızı iletecek sınıflarımız olacak.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package io.bilisim.processor; import io.bilisim.integration.entity.SenderEntity; import io.bilisim.template.IProcessor; public class ConsoleClient implements IProcessor { @Override public void sendEntity(SenderEntity entity) { System.out.println("Console: "+entity.getField1()+" "+entity.getField2()+" "+entity.getField3()); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package io.bilisim.processor; import io.bilisim.integration.entity.SenderEntity; import io.bilisim.template.IProcessor; public class CsvClient implements IProcessor { @Override public void sendEntity(SenderEntity entity) { System.out.println("CSV: "+entity.getField1()+" "+entity.getField2()+" "+entity.getField3()); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package io.bilisim.processor; import io.bilisim.integration.entity.SenderEntity; import io.bilisim.template.IProcessor; public class DbClient implements IProcessor { @Override public void sendEntity(SenderEntity entity) { System.out.println("DB: "+entity.getField1()+" "+entity.getField2()+" "+entity.getField3()); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package io.bilisim.processor; import io.bilisim.integration.entity.SenderEntity; import io.bilisim.template.IProcessor; public class RestClient implements IProcessor { @Override public void sendEntity(SenderEntity entity) { System.out.println("Rest: "+entity.getField1()+" "+entity.getField2()+" "+entity.getField3()); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package io.bilisim.processor; import io.bilisim.integration.entity.SenderEntity; import io.bilisim.template.IProcessor; public class XmlClient implements IProcessor { @Override public void sendEntity(SenderEntity entity) { System.out.println("XML: "+entity.getField1()+" "+entity.getField2()+" "+entity.getField3()); } } |
io.bilisim.sender paketine geçmeden önce, TargetType enum içerisinde bir düzenleme yapacağız. Her tipin processor sınıfının bir örneğini döndüren metotları ekleyeceğiz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
package io.bilisim.integration.entity; import io.bilisim.processor.ConsoleClient; import io.bilisim.processor.CsvClient; import io.bilisim.processor.DbClient; import io.bilisim.processor.RestClient; import io.bilisim.processor.XmlClient; import io.bilisim.template.IProcessor; public enum TargetType { CSV { @Override public IProcessor getSenderInstance() { return new CsvClient(); } }, XML { @Override public IProcessor getSenderInstance() { return new XmlClient(); } }, DB { @Override public IProcessor getSenderInstance() { return new DbClient(); } }, REST { @Override public IProcessor getSenderInstance() { return new RestClient(); } }, CONSOLE { @Override public IProcessor getSenderInstance() { return new ConsoleClient(); } }; private TargetType() { } @SuppressWarnings("exports") public abstract IProcessor getSenderInstance(); } |
Son olarak io.bilisim.sender paketimizin içerisiğini oluşturalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package io.bilisim.sender; import io.bilisim.integration.entity.SenderEntity; public class Sender { public void sendEntity(SenderEntity entity) { entity.getTargetType().getSenderInstance().sendEntity(entity); } } |
Şuana kadar standart bir java projesi yaptık. Şimdi projemizin üzerine sağ tıklayıp module-info.java dosyamızı ekleyeceğiz.
configure -> create module-info.java ile bu işi yapıyoruz. module isimlendirmesi küçük harf ile yapılmalı.
module-info.java dosyası, modülün metadatasını tutan dosyadır. Şimdi biz bu dosya içerisine, repositoryUtil modülümüzün export edeceği paket bilgilerini vereceğiz.
1 2 3 4 5 6 7 8 |
module repositoryUtil { exports io.bilisim.sender; exports io.bilisim.integration.entity; } |
Bu tanım ile, io.bilisim.sender ve io.bilisim.integration.entity paketlerini dışarıya açmış olduk. Ayrıca istersek modül ismi de vererek sadece o modülün erişmesini de sağlayabiliriz.
exports io.bilisim.sender to queueApp;
exports io.bilisim.integration.entity to queueApp;
QueueApp isimli bir proje oluşturup içerisine io.bilisim.core isimli bir paket ve QueueProcessor isimli bir sınıf ekleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package io.bilisim.core; import io.bilisim.integration.entity.SenderEntity; import io.bilisim.integration.entity.TargetType; import io.bilisim.sender.Sender; public class QueueProcessor { public static void main(String[] args) { Sender sender = new Sender(); sender.sendEntity(new SenderEntity("Merhaba", "Java ", "9",TargetType.CONSOLE)); } } |
Bu projemizin içerisine de module-info.java dosyamızı ekleyip içerisini aşağıdaki gibi dolduralım.
1 2 3 4 5 6 7 8 9 10 11 |
/** * @author volkanozdemir * */ module queueApp { requires repositoryUtil; } |
requires ile, bu modülümüzün hangi modüle ihtiyaç duyduğunu gösterdik.
Buraya kadar olanları yaptıysanız,
“Sender cannot be resolved to a type” ve “repositoryUtil cannot be resolved to a module” hatalarını görmüş olmalısınız. Çünkü QueueApp projemizin module pathini düzenlemedik. Bunun için,
Build Path -> Configure Build Path ile aşağıdaki ekranı açacağız. Project tabı altında, Modulepath’e RepositoryUtil projemizi ekliyoruz. Artık hatalardan kurtulduk.
Buraya kadar yaptıklarımıza bir bakar isek,
Harici olarak bağımlılıkları tanımladık. Güçlü bir encapsulation meydana getirdik ve iletişim için bir arayüz oluşturduk. QueueApp üzerinden, RepositoryUtil modülümüzde dışarıya açılmamış sınıflara örneğin ConsoleClient’a erişmek istersek;
“The type io.bilisim.processor.ConsoleClient is not accessible” hatasını alırız.
QueueProcessor sınıfını çalıştırdığımızda,
Console: Merhaba Java 9 çıktısını almış olmalıyız.
Java ile modülerlik konusuna başlangıç yapmış olduk. Bir sonraki makalemizde kaldığımız yerden devam edeceğiz inşallah. Umarım faydalı olmuştur.
Gayret bizden, tevfik Allah’tan.