Java Reflection API
Java Reflection (Türkçe karşılığıyla Yansıtma kütüphanesi ama ben reflection ile devam edeceğim), JVM tarafından yüklenmiş olan sınıflar, arayüzler, sınıfların statik alanları, metod ve nitelikleri hakkında bilgilere çalışma zamanında programatik olarak erişmemizi sağlayan sınıf ve arayüzleri içeren bir kütüphanedir. Reflection’ı kullanarak bir nesnenin hangi sınıfın instance’ı olduğunu öğrenebilir, bir sınıfın yapılandırıcılarına, metodlarına, niteliklerine erişebilir, çalışma zamanında bir sınıf yaratabilir, bir sınıfın çalışma zamanına kadar ismini bilmediğimiz yöntemlerini çağırabilir, boyunu çalışma zamanında belirleyebildiğimiz diziler yaratabilir, elimize çalışma zamanında gelen sınıflar üzerinden nesneler yaratabiliriz. Peki bu tarz işlemlere ne zaman ihtiyaç duyulur? Bir hata ayıklayıcıyı ele alalım. Sözgelimi Eclipse’de debug yaparken, herhangi bir anda bir nesnenin alanlarının değerini görebiliyor, değiştirebiliyoruz. İşte burada Reflection kullanılıyor. Ya da Junit framework’ünü düşünün. Siz bir test sınıfı yazıyorsunuz. Yöntem adlarınızı, test sınıflarınızın adlarını seçmekte tamamen özgürsünüz. Sadece yazdığınız yöntemlerin ne iş yaptığına dair Junit’e yine size Junit’in sunduğu Annotation’larla bilgi veriyorsunuz. Örneğin “@Test” ile işaretlediğiniz metodlar test metodu olarak çalıştırılıyor, “@Before” dedikleriniz her test metodu çalıştırılmadan önce, “@After” dediğiniz metodlar ise tanımladığınız her test metodu çağrıldıktan sonra çağrılıyor. İşte bu gibi esnekliklerin hepsi Reflection ile sağlanıyor.
Bu kadar uzun bir girişin ardından Reflection’ı incelemeye başlayabiliriz. Belirtmek istediğim bir nokta şu ki Reflection’ın bize sağladığı sınıf ve arayüzleri tek tek her detayına kadar anlatamayacağım. Daha çok mevzunun ne olduğunu kavramanız için basit açıklama ve örneklerle ilerleyeceğim.
Ayrıca JAVA bilginiz başlangıç seviyede ise içerik biraz ağır gelebilir.
Örneklerde kullanmak üzere tanımladığım sınıf ve arayüzü veriyorum.
/** * * @author E. Basri Kahveci */ public class MyClass extends MyBaseClass implements MyInterface { private final static int firstStaticAttribute = 20; private static MyClass secondStaticAttribute = new MyClass( MyClass.firstStaticAttribute); private final static String thirdStaticAttribute = "Lorem ipsum"; public class MyInnerClass { public final int myInnerAttribute; public MyInnerClass(int param) { myInnerAttribute = param; } } public interface MyInnerInterface { void doSomeInnerThings(); } public static void myStaticMethod(String param) { System.out.println("myStaticMethod() is called with the param: " + param); } private int attribute1; private String attribute2; public double attribute3; public MyClass() { attribute1 = MyClass.firstStaticAttribute; attribute2 = MyClass.thirdStaticAttribute; } private MyClass(int attribute1) { this.attribute1 = attribute1; this.attribute2 = MyClass.thirdStaticAttribute; } public MyClass(int attribute1, String attribute2) { this.attribute1 = attribute1; this.attribute2 = attribute2; } public int getAttribute1() { return attribute1; } public void setAttribute1(int value) { attribute1 = value; } public String getAttribute2() { return attribute2; } public void setAttribute2(String value) { attribute2 = value; } public final void doSomething() { System.out.println("I am doing something"); } public void doSomethingWith(MyClass thing) { System.out.println("I am doing something with " + thing); } @Deprecated private void doSecretThings() { System.out.println("Ssssssh... I am doing top secret things!"); } public void doFancyThings() { System.out.println("Now I am doing fancy things."); } public String toString() { return new StringBuilder().append("MyClass Instance[ ") .append("Attribute1 = ").append(attribute1) .append(", Attribute2 = ").append(attribute2).append(" ]") .toString(); } }
public class MyBaseClass { public String baseAttribute1; private String baseAttribute2; }
public interface MyInterface { void doFancyThings(); }
Sınıf üyelerine verdiğim anlamsız adlar için kusuruma bakmayın
Class sınıfı ile başlayalım. Class sınıfı, sınıflara ait bilgilere ulaşmak için bize bir takım metodlar sağlar. Örneğin bir sınıfının Class nesnesine ulaşarak o sınıfın yapılandırıcılarına, tanımladığı dahili sınıflara, arayüzlere, niteliklerine, metodlarına ulaşabiliriz.
Öncelikle sınıfımızın bilgilerine erişmek için Class nesnemizi ele alalım.
Class myClazz = MyClass.class; MyClass obj1 = (MyClass) myClazz.newInstance();
Class nesnemize Class.forName ya da bir myClass instance’ı üzerinden .getClass() diyerek de ulaşabilirdik.
Class nesneleri sınıfları temsil eden nesnelerdir. Şu an myClazz ile referans ettiğimiz class nesnemiz kendi tanımlamış olduğumuz MyClass sınıfına ait bilgileri tutmakta.
Class sınıfında tanımlı olan birkaç metodun açıklamasıyla başlayalım.
Class.getSuperclass() metodu, sınıfın ata sınıfını döner (Bir interface’in class nesnesi üzerinden getSuperclass() çağrısı yaparsanız her durumda null döner -o interface başka bir interface’i kalıtıyor olsa bile-).
Örneğin bizim sınıfımız için:
System.out.println(“Superclass of MyClass is “ + myClazz.getSuperclass());
Superclass of MyClass is class MyBaseClassÇıktımız böyle oldu.
Peki bir sınıfın hangi arayüzleri gerçekleştirdiğini nasıl öğrenebilirim? Çok basit, Class.getInterfaces() metodu ile. Bir sınıfın spesifik olarak belirli bir interface’i gerçekleştirip gerçekleştirmediğini ise Class.isAssignableFrom(Class) metodu le öğrenebiliriz. Örneğin şu çağrı bize true değerini dönecektir.
MyInterface.class.isAssignableFrom(MyClass.class);
Elimizdeki bir Class nesnesinin bir sınıfı mı yoksa bir interface’i mi temsil ettiğini de Class.isInterface(); metod çağrısıyla öğrenebiliriz.
Peki az önce Class.getSuperclass() çağrısının bir interface üzerinden (o interface başka bir interface’ten türüyor olsa bile) null değeri döndüğünü söylemiştik. O zaman bir interface’in atası olan interface’lere nasıl ulaşabiliriz? Çok basit, Class.getInterfaces(); çağrısı ile. Bu çağrı bir interface’i temsil eden bir Class nesnesi üzerinden yapıldığında o interface’in atalarını döndürür.
Şimdi myClazz nesnemiz üzerinden bir myClass instance’ı yaratalım.
MyClass myClassObject = myClazz.newInstance();
Evet, şu an yeni bir MyClass nesnesi yaratmış olduk. Peki newInstance() metodu ne yaptı? MyClass sınıfının varsayılan yapılandırıcısını (default constructor) çağırdı. Peki diğer yapılandırıcıları çağırarak bir nesne yaratmak isteseydik ne yapmalıydık? Class.getConstructor(Class[]) metodunu kullanmalıydık. Peki getConstructor() metodunun aldığı parametre de neyin nesi? getConstructor() metodu, çağırmak istediğimiz yapılandırıcının parametrelerinin sınıf nesnelerini argüman olarak alır. Örnek ile devam edelim.
“public MyClass(int attribute1, String attribute2);” imzalı yapılandırıcıyı kullanarak bir nesne yaratmak istiyoruz. Yani getConstructor() metoduna diyeceğiz ki, “bana int ve String türünden parametre alan yapılandırıcıyı ver”.
Constructor constructor = myClazz.getConstructor(new Class[] {int.class, String.class});
Yapılandırıcımıza, istediğimiz constructor’ın aldığı parametrelerin türlerini bir Class dizisinin içine yerleştirip getConstructor() metodunu bu dizi ile çağırarak ulaştık. Şu anda elimizde bulunan constructor nesnesi, bizim “public MyClass(int, string);” imzası ile yazmış olduğumuz yapılandırıcının bir temsilcisi.
Şimdi bir nesne daha yaratalım.
MyClass obj2 = (MyClass) constructor.newInstance(3, "Lorem ipsum");
İşte oldu! Elimizdeki constructor nesnesinin newInstance() metodunu, bu kez gerçek parametreler ile çağırdık ve nesnemiz yaratıldı.
İsterseniz bir bakalım olmuş mu.
System.out.println(obj2);
Şu çıktıyı aldık değil mi?
MyClass Instance[ Attribute1 = 3, Attribute2 = Lorem ipsum ]
Evet, şimdi Class sınıfında tanımlı diğer metodlara bir göz atalım.
getConstructor(Class[]); metodunu kullanarak belirli bir public yapılandırıcıya ulaşmık olduk. getConstructors(); metodunu kullanarak da sınıf içinde tanımlanmış tüm public yapılandırıcılara ulaşabilirdik. Peki ya public olarak tanımlanmamış bir yapılandırıcıya erişmek istersek? O zaman da getDeclaredConstructor(Class[]); ve getDeclaredConstructors(); metodlarını kullanıyoruz (getConstructors(), getFields(), getMethods() gibi çağrılar ata sınıflardan da kalıtılan, public tanımlanmış öğeleri döndürürken; getDeclaredConstructors(), getDeclaredFields(), getDeclaredMethods() çağrıları o sınıfta tanımlanmış -atadan kalıtılmamış- public, private, protected erişim belirteçli öğeleri döndürür). Örneğin private MyClass(int attribute1); imzalı yapılandırıcıyla bir nesne yaratalım.
Doğrudan yarattığımız nesneyi ekrana yazdıyorum:
System.out.println(myClazz.getDeclaredConstructor(new Class[] {int.class}).newInstance(5));
Hobaaa. Exception aldık değil mi? Bakalım ne diyor:
Exception in thread "main" java.lang.IllegalAccessException: Class Program can not access a member of class MyClass with modifiers "private"
private tanımlanmış bir öğeye öyle zort diye erişemezsin diyor
Aslına bakarsanız sınıf içinde bir öğe public tanımlanmamışsa mutlaka bir amacı vardır ve o öğeye dışarıdan müdahele etmek sınıfın durum ve işleyişini bozabilir. Biz bu durumu göz ardı ederek private yapılandırıcımıza erişmeye çalışalım. Şunu deniyoruz:
Constructor constructor2 = myClazz.getDeclaredConstructor(new Class[] {int.class}); constructor2.setAccessible(true); System.out.println(constructor2.newInstance(5));
Çalıştı değil mi? Beklediğimiz çıktıyı aldık:
MyClass Instance[ Attribute1 = 5, Attribute2 = Lorem ipsum ]
Peki nasıl çalıştı? Constructor sınıfında bulunan setAccessible() metodu, bu sınıfın atası olan AccesibleObject sınıfından kalıtılmış bir yöntemdir. (AccessibleObject sınıfından Constructor dışında, sınıfların niteliklerini temsil eden Field sınıfı ve sınıfların metodlarını temsil eden Method sınıfları da türer) Constructor nesnemiz üzerinden setAccessible(true) yöntem çağrısı yaparak nesnemizin accesible bayrağını true yapmış olduk.
setAccessible() metodu güvenlik dolayısıyla her durumda çalışmaz. Bu konuya yazının sonunda değineceğim.
Biraz da Field ve Method sınıfları ile örnekler yapalım.
Az önce obj1 adında bir MyClass nesnesi yaratmıştık. Diyelim ki o nesnenin attribute1 alanına erişmek istiyoruz, hatta değerini günlemek istiyoruz. Bunu Field sınıfını kullanarak yapabiliriz.
Aynı getConstructor() metodunda olduğu gibi, bir sınıf içinde tanımlanmış nitelikleri temsil eden Field nesnelerine ulaşmak için getField() metodunu kullanabiliriz. Fakat farkettiğiniz üzere getField() metodu bize yalnızca public nitelikleri döndürür. Biz ise private bir niteliğe erişmek istiyoruz. Evet, bunu da getDeclaredField() metoduyla yapacağız.
Field attribute1Field = myClazz.getDeclaredField("attribute1");
MyClass sınıfında tanımlanmış attribute1 niteliğini yansıtan Field nesnesine ulaşmış olduk. Peki obj1 nesnemizin attribute1 alanını okumaya çalışalım. Bunu Field.get(Object) metoduyla yapacağız.
Field attribute1Field = myClazz.getDeclaredField("attribute1"); attribute1Field.setAccessible(true); System.out.println("Obj1.attribute1: " + attribute1Field.get(obj1));
setAccessible() metodunu kullanmasaydık, az önceki gibi yine IllegalAccessException yiyecektik.
Çağrımıza tekrar göz atalım:
attribute1Field.get(obj1);
attribute1Field nesnesi, MyClass sınıfına ait attribute1 niteliğini yansıtıyor. Herhangi bir MyClass nesnesinin attribute1 niteliğine ait bir değeri değil! Burayı iyi anlamak lazım. O yüzden değeri okurken hangi MyClass nesnesinin attribute1 alanını okuyacağımızı belirtmek için obj1′i argüman olarak verdik.
attribute1 alanının değerini günlemek için de Field.set(Object) metodunu kullanabiliriz. Onu size bırakıyorum.
getFields() ile getDeclaredFields() metodlarına bakalım. Şu kodu deniyoruz:
System.out.println("getFields() :"); for(Field f : myClazz.getFields()) System.out.println("# " + f); System.out.println("getDeclaredFields() :"); for(Field f : myClazz.getDeclaredFields()) System.out.println("# " + f);
Çıktımız:
getFields() : # public double MyClass.attribute3 # public java.lang.String MyBaseClass.baseAttribute1 getDeclaredFields() : # private static final int MyClass.firstStaticAttribute # private static MyClass MyClass.secondStaticAttribute # private static final java.lang.String MyClass.thirdStaticAttribute # private int MyClass.attribute1 # private java.lang.String MyClass.attribute2 # public double MyClass.attribute3
Ne demiştik? getFields() metodu ata sınıftan kalıtılan nitelikleri dahil ederek public nitelikleri döndürürken, getDeclaredFields() metodu yalnızca o sınıfta tanımlanmış public, private veya protected erişim belirtecine sahip nitelikleri döndürür. Çıktımız da öyle söylüyor zaten.
Biraz da Method sınıfıyla uğraşalım. Method sınıfı, bir sınıf altında tanımlanmış metodları yansıtmak için kullanılır. Bir sınıfın metodları üzerinde işlemleri, o metodu temsil eden Method nesnesi üzerinden yaparız. Yine örnekle devam edelim.
Öncelikle Class sınıfında tanımlı, metodlarla alakalı yöntemlere bir göz atalım. getMethod(), getMethods(), getDeclaredMethod(), getDeclaredMethods(). Her birinin ne iş yaptığını anladığınızı düşünüyorum. getDeclaredMethods()’u kullanarak sınıfın tüm metodlarını ekrana yazdırmakla başlayalım.
for(Method method : myClazz.getDeclaredMethods()) System.out.println(method);
Çıktımız şöyle oldu:
public static void MyClass.myStaticMethod(java.lang.String) public int MyClass.getAttribute1() public void MyClass.setAttribute1(int) public java.lang.String MyClass.getAttribute2() public void MyClass.setAttribute2(java.lang.String) public void MyClass.doSomething() public void MyClass.doSomethingWith(MyClass) private void MyClass.doSecretThings() public void MyClass.doFancyThings() public java.lang.String MyClass.toString()
Gördüğünüz gibi public tanımlanmış tüm metodları yazdı. Field sınıfını anlatırken söylediğim gibi getMethods() yöntemi ata sınıflardan kalıtılan (public) metodları da döndürürken getDeclaredMethods() yöntemi ata sınıflardan kalıtılan metodları döndürmez.
obj1 nesnemizin doSomethingWith() yöntemini Reflection ile çağırmaya çalışalım. Öncelikle Method nesnesini elimize almalıyız. Constructor nesnesine ulaşırken uyguladığımız yönteme çok benzer bir yöntem uyguluyoruz.
Method method1 = myClazz.getMethod("doSomethingWith", new Class[] {MyClass.class});
Aradığımız metodun adını ve aldığı parametrelerin türlerini vererek doSomethingWith() metodunu temsil eden Method nesnesine ulaştık. Şayet doSomethingWith() metodunu overload etmiş olsaydık getMethod()’a verdiğimiz tür parametrelerini değiştirerek diğer versiyonlarına da ulaşabilirdik. Şimdi obj1′in doSomethingWith() yöntemini çağıralım.
method1.invoke(obj1, new MyClass());
Yine aynı mantığı kullandık. Bu çağrı ile obj1 nesnesinin doSomethingWith() metodu, “new MyClass()” ile yarattığımız nesne argüman olarak geçilerek çağrıldı.
Junit’te bir test sınıfı içine yazıp “@Test” ile işaretlediğiniz metodlar, test çalıştırılırken test metodu olarak çağrılırlar. “@Before” ve “@After” gibi Annotation’larla her test metodundan önce ve sonra işletilecek metodları belirtirsiniz. Peki Junit bunu nasıl yapar? Metodları tararken sahip oldukları Annotation’lara bakarak tabii ki. Biz de bunun üstüne bir örnek yapalım.
doFancyThings() metodumuza @Deprecated Annotation’ı koyalım. Sonra da şu kodu deneyelim:
Method m = myClazz.getDeclaredMethod("doSecretThings", new Class[]{}); if(m.isAnnotationPresent(Deprecated.class)) System.out.println("doSecretThings() is using the annotation: " + Deprecated.class);
“doSecretThings() is using the annotation: interface java.lang.Deprecated” dedi değil mi? Aynı sorguyu Method.getAnnotation(Class) metodunu kullanarak şu şekilde yapabiliriz:
Method m = myClazz.getDeclaredMethod("doSecretThings", new Class[]{}); if(m.getAnnotation(Deprecated.class) != null) System.out.println("doSecretThings() is using the annotation: " + Deprecated.class);
Üsttekiyle aynı çıktıyı verdi.
Bir metoda ilişkin dönüş değeri, fırlattığı exceptionlar, aldığı parametreler gibi bilgileri Method sınıfında tanımlanmış getExceptionTypes(), getParameterTypes(), getReturnType() gibi metodlarla elde edebiliriz.
Bir de Modifier sınıfından bahsetmek istiyorum. Modifier sınıfı içinde statik metodlar bulunduran “utility class” dediğimiz türden bir sınıftır. İçerdiği statik metodlar ve sabitlerle sınıfların ve sınıf üyelerinin erişim belirteçlerine ait bilgiler elde edebiliyoruz. Bir öğeye ait belirteç bilgisi bir tamsayı ile temsil ediliyor. Modifier sınıfının içinde tanımlı isFinal(int), isInterface(int), isPrivate(int), isProtected(int), isPublic(int), isStatic(int), gibi yöntemlerle belirteçler üzerinde sorgular çalıştırabiliyoruz. Modifier.toString() metodu ile de bir modifier tamsayısını anlaşılır bir şekilde ekrana basabiliyoruz. Deneyelim:
for (Field field : myClazz.getDeclaredFields()) System.out.println(field.getName() + " > " + Modifier.toString(field.getModifiers()));
Şöyle bir çıktı aldık:
firstStaticAttribute > private static final secondStaticAttribute > private static thirdStaticAttribute > private static final attribute1 > private attribute2 > private attribute3 > public
Reflection API’de bir de Array sınıfı var ama yazı çok fazla uzadığı için ondan bahsetmeyeceğim. Array sınıfı ile boyu dinamik olarak belirlenen diziler yaratabilir ve yarattığınız dizilerde değer atama, değer okuma gibi işlemler yapabiliyoruz.
Son olarak setAccessible() olayına biraz değineyim.
JAVA’nın içerisinde bulunan Security Manager, işletilen bir kodun istenmeyen davranışlar sergilemesini önlemeyi amaçlar. Örneğin bir JAVA applet’inin diskinizdeki dosyaya erişmesi tehlikelidir ve Security Manager buna izin vermez. Çünkü siz bir bağlantıya tıkladığınızda size farkettirmeden bilgisayarınızda çalıştırılan bir JAVA applet diskinizdeki dosyaları okuyup gizli bilgilerinize ulaşmayı hedefleyebilir.
private olarak tanımladığımız niteliklere erişim yapmaya çalıştığımızda yediğimiz Exception ise Field sınıfının atası olan AccessibleObject sınıfında tanımlı accessible bayrağının false değeri taşımasından kaynaklanır. setAccessible() ile bu değeri true yapıp erişimi ancak o zaman sağlayabiliyoruz.
Fakat öyle her kafamıza estiğinde setAccessible() deyip private tanımlı alanlara erişim yapamayız. Şu an kodu masaüstü ortamında çalıştırdığımız için Security Manager bu işlemi yapmamıza izin verdi, fakat bir Java appleti üzerinde bunu deneseydik Security Manager bize bir “hoop!” çekerdi.
Peki basitçe sınıflardan nesneler yaratıp, metodlarına, niteliklerine kırk takla atmadan insan gibi erişmek varken Reflection’la niye uğraşalım? Reflection’ın kullanım amacı zaten o değil. Yazdığımız herhangi bir koddaki çağrıları, erişimleri Reflection ile yapmaya kalksak gördüğünüz gibi ne kadar çok uğraşacağız. Ayrıca Reflection hayvan gibi performans yükü olan bir kütüphane. Ve az önce bahsettiğim gibi çeşitli güvenlik kısıtları da içeriyor. Bu yüzden Reflection normal işleri yapmak için değil de, test ve debug araçları, class browser’lar gibi şeyler yapmak için kullanılıyor. Veya şunu söyleyeyim. Firefox ya da Chrome gibi bir uygulamayı düşünün. Çeşitli eklenti dosyaları ile uygulamamıza yeni özellikler katabiliyoruz. İşte bunu JAVA’da Reflection kullanarak yapabiliriz. Reflection kullanarak uygulamamız çalışma anında kullanıcının bize sunduğu harici sınıfları alıp yükleyebilir, instance’larını yaratarak onları kullanabilir. Bunun üzerine basit bir senaryo hazırlayıp implementasyonunu bir başka yazıda yapmayı düşünüyorum. Şimdilik hoşçakalın.
Kaynak: http://download.oracle.com/javase/1.4.2/docs/api/java/lang/reflect/package-summary.html



Güzel bir yazı olmuş. Sağolasın
rica ederim
Teşekkürler.Çok güzel olmuş.