top of page

Java ile Özel Notasyon Geliştirimi

tl;dr: Notasyonlar, meta verisi ekleyerek mükerrer kod yazımını azaltıp, kod okunurluğunu artırmaya yardımcı olan Java dilinin güçlü yanlarından biridir.


JDK 1.5 ve üstü bir sürümle geliştirim yapmış her seviyeden Java geliştiricisi notasyon kullanmıştır. @Inject, @Entity, @Autowired, hiç olmadıysa @Override notasyonunu görmemiş, kullanmamış herhalde yoktur. Java dilinin güçlü yanlarından biri olan notasyonlar, birer dekoratör gibi çalışarak; sınıf, yapılandırıcı, metot, alan gibi program yapılarına meta verisi eklememizi sağlar. Geliştiriciye belirli bir eylem/davranışı adeta markalayıp, uygulamanın ihtiyaç duyulan noktalarına enjekte etme olanağı sağlayan notasyonlar, mükerrer kod yazımını azaltıp, kod okunurluğunu artırırlar.


Notasyonların gücünden, Java geliştiricileri kendi özel notasyonlarını oluşturarak yeterince yararlanmakta mıdır? Başta kendi geliştirim tecrübem olmak üzere kişisel gözlemim ve çeşitli yazı, makale, yayınlanmış anketlerden anladığım; Javacılar notasyon kullanmayı yazmaktan daha çok seviyor. Bu yazıyla amacım, notasyonları daha yakından tanımanıza yardımcı olmak, çalışma mantıklarını göstermek ve basit bir örnek aracılığıyla kendi notasyonunuzu nasıl yazıp kullanabileceğinizi örneklendirerek, özel notasyonların sağladığı avantajlara dikkatinizi çekmek.


Nasıl çalışıyor?

An annotation is a marker which associates information with a program construct, but has no effect at run time. JLS Section 9.7

Java dil belirtiminde ifade edildiği gibi, notasyonlar kendi başlarına çalışma zamanında herhangi bir etkiye sahip değildir. Daha açık bir ifadeyle, bir notasyon tek başına eklendiği programın çalışma zamanı davranışını değiştirmez. Aslında bir notasyon, uygulandığı noktada elde edilmek istenen davranışı bildiren bir işaretçi(an annotation is a marker - JSL) gibi davranır. Bu sebeple, notasyonların geliştirici tarafından belirlenmiş davranış/eylemi gerçekleştirmesi için çalışma zamanı çerçeveleri(runtime frameworks) veya derleyici tarafından işlenmesi gerekir. Notasyonlar, derleme zamanında Java derleyicisinin bir tür eklentisi sayılabilecek notasyon işleyiciler(annotation processors), çalışma zamanında ise Java Reflection API tarafından işlenir. Bu yazıda, çalışma zamanında Reflection API ile işlenecek bir notasyon örneğini inceleyeceğiz.


Nasıl tanımlanır?

Bir notasyon oluşturmak için temelde iki bilgiyi sağlamak gerekmektedir. Bunlardan biri Retention politikası, diğeri Target. Retention politikası, notasyona erişim zamanını tanımlarken, target hangi yapıya(sınıf, method, alan) uygulanacağını tanımlamaktadır.


Retention politikası, RetentionPolicy ile tanımlanır. RetentionPolicy enum tipinde bir veri yapısıdır ve java.lang.annotation paketi altında bulunmaktadır.


Görüldüğü üzere 3 retention politikası vardır.

  • SOURCE: Notasyon derleyici tarafından atılır.

  • CLASS: Notasyon derleyici tarafından oluşturulan sınıf dosyasına kaydedilir ve JVM tarafından saklanması gerekmez. Varsayılan davranış biçimidir.

  • RUNTIME: Notasyon sınıf dosyasına derleyici tarafından kaydedilir ve çalışma zamanında JVM tarafından saklanır, böylece reflection ile okunabilir.

Runtime, uygulamaların notasyonlara ve ilişkili verilerine reflection ile erişip kod yürütülmesine izin verdiği için en bilinen ve yaygın olarak kullanılan retention politikasıdır.


Java 9 sonrası, 11 target vardır; bir başka deyişle 11 yapıya notasyon uygulayabiliriz.

  • TYPE: Sınıf, arayüz, notasyon, enum gibi tipleri hedefler

  • FIELD: Sınıf değişkenleri, enum sabitleri gibi alan tiplerini hedefler

  • METHOD: Sınıf metotlarını hedefler

  • MODULE: Java 9 ile gelmiştir, modülleri hedefler

  • PARAMETER: Metot, yapılandırıcı parametrelerini hedefler

  • CONSTRUCTOR: Yapılandırıcıları hedefler

  • LOCAL_VARIABLE: Yerel değişkenleri hedefler

  • ANNOTATION_TYPE: Notasyonları hedefler ve başka bir notasyon ekler

  • PACKAGE: Paketleri hedefler

  • TYPE_PARAMETER: Java 1.8 ile gelmiştir, MyClass<T>’deki T gibi jenerik parametreleri hedefler

  • TYPE_USE: Java 1.8 ile gelmiştir, herhangi bir türün kullanımını(örneğin new ile oluşturulma, arayüz implementasyonu, cast işlemi vb) hedefler


Notasyon Parametreleri ve Notasyon Tipleri

Notasyonlar program yapılarına meta verisini, parametreleri aracılığıyla ekler. Bir notasyonun parametreye sahip olup olmaması tipini belirler. Parametre bildirimi içermeyen notasyonlar işaretçi notasyon(marker annotation type) türünü, tek bir parametre bildirimi içeren notasyonlar tek elemanlı(single element annotation type), birden fazla parametre bildirimi içeren notasyonlar ise kompleks notasyon(complex annotation type) türünü oluşturur. Primitif tipler, String, Class, Enum, bir başka notasyon ve bu anılan tiplerin dizi tipi, bir notasyon parametresi olarak bildirilebilir.


Senaryomuz

Profiling için kodumuzda çalışma sürelerini ölçmek istediğimiz metotlara sahip olduğumuzu düşünelim. Her metodun değil, bazı metotların çalışma süresini ölçmek istiyoruz; üstelik bu metotlara ilerde yenileri eklenebilir. Bunun için bir notasyon yazmak iyi bir fikirdir çünkü en başta belirttiğimiz üzere kod okunurluğunu bozmadan ek olarak aynı davranışı tekrar tekrar dilediğimiz noktalarda(örneğin kodumuza eklenecek yeni metotlarda) elde etmek için notasyonlar biçilmiş kaftandır.

Retention policy ve Target’tan daha önce bahsettiğimiz için, yukarıda görülen notasyonun çalışma zamanında Reflection API ile işlenebileceğini ve ancak metotlara(başka bir yapıya uygulanırsa derleme zamanında hata alınır) uygulanabileceğini kestirmeniz zor değil. Bu noktada Target ile ilgili şu detayı da paylaşalım, birden fazla hedef tanımlayabilirsiniz.

Notasyonumuzun gövdesinde ise enum tipinde MonitorPolicy parametresinin deklare edildiğini görüyorsunuz. İlgili enum değerine çalışma zamanında value() metodu üzerinden erişeceğimiz gibi, notasyonumuza değer geçirmek için value’yu anahtar olarak da kullanabiliriz.

Fakat değer geçirmek için value anahtarını kullanmak zorunlu değildir. Aşağıdaki kullanım ile yukarıdaki eşdeğerdir.

Value dışında farklı bir ismi kullansaydık bunu belirtmemiz gerekirdi çünkü value, notasyonlarda varsayılan anahtar ismidir. MonitorPolicy SHORT ve DETAILED değerlerine sahiptir. Bu değerlerle, çalışma süresini ölçeceğimiz metotlara dair döndürülecek bilginin biçimini belirliyoruz, ayrıntılı veya kısa.

Tanımladığımız parametreye varsayılan bir değer atamak istediğimizde ise default anahtarını kullanırız.

Bu durumda aşağıdaki gibi bir kullanımda, monitor policy short olacaktır.

İşleyicimiz

Daha önce ifade ettiğimiz gibi, notasyonlar kendi başlarına herhangi bir kod çalıştırmazlar. Monitor notasyonunda Retention policy olarak RUNTIME tanımlandığı için, çalışma zamanında Java Reflection API kullanılarak işlenmesi gerekir. Aşağıdaki metot bu vazifeyi gerçekleştirir.

Executor metodu, biri Monitor notasyonunu kullanan sınıf referansı, diğeri varargs tipinde; ölçüm yapılacak metotların parametreleri olmak üzere 2 argüman alıyor.


Önce object referansı üzerinden, ilgili nesnenin deklare edilmiş metotlarını çekip ardından bir döngü yardımıyla gezilen metotlarda Monitor notasyonunun uygulanıp uygulanmadığını Reflection API’ye ait isAnnotationPresent() metoduyla(metoda notasyon nesnesini geçirdiğimize dikkat edin) kontrol ediyoruz. Monitor notasyonunu uygulayan metod varsa, koşturulması için invoker metodunu çağırıyoruz.

Public olarak deklare edilmemiş metotlara da erişebilmek için, invoker metodunda ilk olarak method.setAccessible(true); ile erişim kontrolünü yapılandırıyoruz; aksi halde private metotlar için IllegalAccessException istisnasıyla karşılaşırız. Sonraki adımda, çağırıldığı yapıyla(bizim örneğimizde metot) ilişkilendirilmiş, belirtilen tipteki(metoda notasyon nesnesini geçirdiğimize dikkat edin) notasyonu döndüren Reflection API’ye ait getAnnotation metodunu kullanarak, notasyonumuzda tanımlanmış değeri çekiyoruz. Bu değeri, çalışma süresine dair bilgiyi çıktılama biçimizi belirlemede kullanacağız. Bu aşamadan sonra, o anki zaman mührünü milisaniye cinsinden start değişkeninde depoluyoruz; Monitor notasyonunu uygulamış metodu, Reflection API’nin invoke metoduyla koşturduktan sonra elde edilen zaman mührünü ise end değişkeninde. Son aşamada ise, MonitorPolicy’ye bağlı olarak çalışma süresini çıktılıyoruz.


Aşağıda, paylaşılan örnekteki gibi bir kullanımda alacağımız muhtemel çıktıyı görüyorsunuz.

Total execution time of getUserInfo method is 1459 ms

Monitor notasyonunu içeren uygulamaya buradan ulaşabilirsiniz.


Sonuç

Java notasyonları sınıf, yapılandırıcı, metot, alan gibi Java program yapılarına meta verisi eklememizi sağlayan, bu şekilde mükerrer kod yazımını azaltıp, kod okunurluğunu artıran Java dilinin güçlü yanlarından biridir. Notasyonlar kendi başlarına herhangi bir kod çalıştırmazlar, bunun yerine derleyici veya ait oldukları çerçeve(framework) için, uygulandığı noktada elde edilmek istenen davranışı bildiren bir işaretçi gibi davranıp, derleme zamanında Java Annotation Processors’ler, çalışma zamanında ise Java Reflection API tarafından işlenirler.

0 yorum

Son Yazılar

Hepsini Gör
bottom of page