Reflection ile Dependency Injection nasıl gerçekleştirilir?
Merhaba arkadaşlar;
Bugün sizlere Java Reflection kütüphanesinden ve notasyon (annotation) bazlı yapılandırıcılardan ve ayrıca notasyon bazlı yapılandırıcılar ile basit bir Dependency Injection (Bağımlılık Zerki) uygulamasının nasıl yapılabileceğinden bahsetmek istiyorum.
Java Reflection kütüphanesi Java içerisinde 1.1 versiyonundan beri bulunuyor. Genel olarak amacı; Çalışma anında (runtime) sınıflara (Class), sınıflara ait metodlara (Methods), sınıf ya da temel tipteki değişken alanlarına (Fields) ve Annotation tanımlamalarına vs. erişmek, düzenleyebilmek, yeni bir nesne örneğini çalışma anında oluşturmak ve hatta private ve protected gibi sınırlandırılmış alanlara erişebilmektir.
Notasyon bazlı tanımlayıcılarsa, Java 1.5 sürümünden itibaren getirilen bir yenilik. @ işaretiyle başlayan notasyon tanımlayıcıları, genel olarak yapılandırma veya geliştirici tarafından tanımlanan ifadeleri uygulamalarda erişmek için kullanılıyor. Notasyonlar compile işlemi öncesinde pre-compiler araçları tarafından değerlendirilebileceği gibi, Java Reflection kütüphanesiyle runtime’ da erişilebiliyor.
Bu yazıda örnek olarak gösterilecek uygulamada; Java Reflection kütüphanesi ve notasyon bazlı tanımlayıcılar kullanılarak “Bağımlılık Zerki (Dependency Injection)” yaklaşımının nasıl uygulandığı, temel olarak açıklanacaktır. Eğer Dependency Injection nedir diyorsanız, 1–[2] takip edebilirsiniz.
Uygulama:
Uygulamamızda Arac adında bir arayüz (interface), bu arayüzü uygulayan (implementation) 2 sınıf (Araba ve Traktor), Zerk adında bir notasyon ve bir de uygulamayı koşturmak adına App sınıfı yer alıyor.
Yukarıdaki UML diagramda bulunan bileşenlerin alanlarından, hangi metodlara ve veri alanlarına sahip olduğu anlaşılabilir. Şimdi sırayla açıklayalım.
interface Arac { String calis(); }
Arac isimli arayüz içinde bir adet calis() yordamı bulunuyor. Bu yordam Arac arayüzünün iskeletini oluşturuyor. Aşağıda yer alan Araba ve Traktor sınıflarıysa calis() yordamını uygulayarak String sınıfı türünden “X çalışıyor..” mesajını geri döndürüyor.
public class Araba implements Arac{ public Araba(){ System.out.println("Araba nesnesi oluşturuldu.."); } public String calis() { return "Araba çalışıyor.."; } }
public class Traktor implements Arac{ public Traktor(){ System.out.println("Traktor nesnesi oluşturuldu.."); } public String calis() { return "Traktör çalışıyor.."; } }
Java EE 6 veya Spring Framework teknolojileriyle uğraşanlar, @EJB, @Inject, @AutoWired, @PersistenceContext, @ManagedBean gibi notasyon arayüzleriyle enjeksiyon işlemlerinin nasıl uygulandığını bilirler. Bu uygulama ile arkaplanda bu zerk işlemlerinin nasıl yapıldığı temel olarak anlaşılabilecektir. Notasyonlar da aslında başında @ işareti barındıran birer arayüzdür. Şimdi örnek olarak javax.ejb paketinde bulunan @EJB notasyonunu incelemeye alalım;
@Target(value = {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Retention(value = RetentionPolicy.RUNTIME) public @interface EJB { public String name() default ""; public String description() default ""; public String beanName() default ""; public Class beanInterface() default Object.class; public String mappedName() default ""; public String lookup() default ""; }
Yukarıdaki EJB notasyonunun @interface türünden olduğu görülebilir. Bu notasyonun başında tanımlı olan @Target ve @Retention da aslında Java tarafından desteklenen özel notasyonlardır. @Target notasyonunun value niteliğine tanımlanan özellikler @EJB notasyonunun nerelerde kullanılabileceği yetkisini tanımlar. Örneğin value niteliğine tanımlanan ElementType.TYPE özelliği @EJB notasyonunun sınıf, arayüz gibi birimlerin başında kullanılabileceğini yetkisini tanımlar. Diğer özellikler de adlarından anlaşılacağı üzere sırasıyla metod başında ve veri alanı(değişken) başında kullanılabilirliği tanımlar. @Retention notasyonu ise @EJB notasyonunun ne zaman erişilebilir olduğunu tanımlamaktadır. Bu örnekteki RetentionPolicy.RUNTIME ifadesi bu notasyonun çalışma anında erişilebileceğini tanımlamaktadır. Çalışma anında erişimse daha önce belirtildiği üzere Java Reflection kütüphanesiyle sağlanabilmektedir.
Notasyonlar içerisinde tanımlanan veri alanları yazılım geliştirici tarafından değer atanabilecek alanlardır. Burada örneğin name() veri alanı @EJB(name=”myEJB”) şeklinde geliştirici tarafından belirlenebilir. default niteliği ise, mevcut veri alanının varsayılan içeriğini belirtmektedir.
Şimdi sıra bizim tanımladığımız @Zerk notasyonuna geldi;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Zerk { public Class hangi(); }
@EJB notasyon arayüzünde açıklananlar üzere @Zerk notasyonunun çalışma anında (runtime) erişilebileceği ve yalnızca değişken alanlarında kullanılabileceği anlaşılabilir. Burada yer alan hangi() veri alanı ise, hangi sınıf türünden nesnenin zerk (injection) işlemine tabi tutulacağını tanımlamaktadır. hangi() veri alanının Class türünden bir referans aldığı da açık bir şekilde görülebilir.
Java’da herbir sınıf ya da temel tipin, bir de Class sınıf türünden bir karşılığı bulunur. Reflection ile işlemler yapabilmek için ilgili türün Class nesnesi öncelikle elde edilmelidir. Elde edilen bu nesne aracılığıyla; Sınıf içindeki veri alanlarına, kurucu metodlara, tanımlı notasyonlara ve diğer metodlara erişim sağlanabilir. Class türünden bir nesne elde edebilmek için SinifAdi.class, temelTip.class (int.class gibi) veya Class.forName(“com.kodcu.SinifAdi”); biçimleri kullanılabilir.
Şimdi uygulamayı koşturacak App sınıfına göz atalım. Yorum satırları sizlere tam olarak ne yapıldığı konusunda yardımcı olacaktır.
public class App { @Zerk(hangi = Araba.class) // Hangi türden nesne zerk edilecek? public static Arac araba; @Zerk(hangi = Traktor.class) // Hangi türden nesne zerk edilecek? public static Arac traktor; public static void main(String[] args) throws InstantiationException, IllegalAccessException { // App sınıfına ait Class nesnesini çek. Class app = App.class; // App sınıfının veri alanlarını elde et. (araba ve traktor) Field[] alanlar = app.getFields(); // Tüm alanları sırasıyla dön for (Field alan : alanlar) { // Bu alanlarda tanımlı notasyonları elde et. Annotation[] notasyonlar = alan.getAnnotations(); // Tüm notasyonları dön (2 tane) for (Annotation notasyon : notasyonlar) { // Eğer notasyon Zerk türünden ise; if (notasyon instanceof Zerk) { // Zerk türüne aşağı doğru evir.(DownCasting) Zerk zerk = (Zerk) notasyon; // notasyona ait hangi() veri alanı içeriğini edin. Class clazz = zerk.hangi(); // Elde edilen sınıf türünden bir nesne oluştur. Object obj = clazz.newInstance(); // Eğer nesne Araba türünden ise; if (obj instanceof Araba) { // Nesneyi Araba türüne aşağı doğru evir.(DownCasting) Araba arb = (Araba) obj; /* App içindeki hangi alan ise (araba veya traktor), o alana bu nesneyi zerk et. * ilk parametre hangi nesneye ait işlem gerçekleştireceğini belirler, * buradaki alanlar static oldukları için null geçirilir. * İkinci paremetre ise, zerk edilecek nesneyi bekler. */ alan.set(null, arb); // App sınıfı içindeki araba alanına zerk edilen nesneyi çalıştır. System.out.println(araba.calis()); } // Aynı işlemler eğer nesne Traktor türünden ise aynı şekilde gerçekleştirilir. if (obj instanceof Traktor) { Traktor trktr = (Traktor) obj; alan.set(null, trktr); System.out.println(traktor.calis()); } }}} }}
Uygulama çıktısı: