SpringBoot Custom Validasyon Anotasyonlari Olusturma

Ibrahim Halil Koyuncu
4 min readFeb 22, 2021

--

Merhaba, bu yazıda SpringBoot projesi üzerinde custom validasyon anotasyonlarından bahsedeceğim. Bu yazıda aşağıdaki sorulara cevap arayacağız. Aşağıdaki başlıklarla ilgili yaptığım örnek projenin bulunduğu GitHub linkini yazının sonunda bulabilirsiniz.

  • Custom validasyon anotasyonu nasıl oluşturulur ve neden ihtiyaç duyarız ?
  • Oluşturulan anotasyonu başka validasyon anotasyonları ile nasıl kombine edebiliriz ?
  • Validasyon yapılacak alanları gruplayarak istenilen grubu veya grupları nasıl valide ederiz ve hangi senaryolarda(use-cases) buna ihtiyaç duyulur ?
  • İlgili anotasyona özel yük(payload) nesneleri atayarak hata durumunda hangi yolu izleyeceği nasıl belirlenir ?

Custom Validasyon Anotasyonuna Neden İhtiyaç Duyarız ?

“javax.validation” paketinin sunduğu validasyon anotasyonlarının bazen yetersiz kaldığı senaryolar olabilir. Örneğin entity’imizde ENUM tipinde bir alan varsa bu alanın validasyonu için kendi custom validasyon anotasyonumuzu yazmamız gerekecektir çünkü nesne ve enum alanları valide etmeye karşılık gelen bir validasyon anotasyonu bulunmamaktadır. Yine entity’de bulunan bir alan birçok validasyon gereksinime ihtiyaç duyabilir ve bunları teker teker eklemek kod üzerinde karmaşıklığa yol açabilir. İstenilen validasyonları tek bir anotasyon üzerinde birleştirmek kodu çok daha okunulabilir hale getirecektir.

Custom Validasyon Anotasyonu Nasıl Oluşturulur ?

Yukarıdaki örnekten de anlaşılacağı üzere, custom anotasyonumuzu tanımlamak için ilk olarak bir interface oluşturmaya ihtiyacımız vardır. Interface’e vereceğimiz isim aslında oluşturmak istediğimiz anotasyonun ismi olacaktır. Oluşturduğumuz interface; diğer interface’lerden farklı olarak başında “@ ” sembolü bulunmalıdır. Interface’imizi oluşturduktan sonra anotasyonumuz ile ilgili belirli konfigürasyonlar yapmamız gerekecektir. Bu konfigürasyonlarıda yine belirli anotasyonlar kullanarak yapacağız. Örnekte görüldüğü gibi “java.lang.annotation” paketinde bulunan @Retention ve @Target anotasyonlarını ve “javax.validation” paketinde bulunan @Constraint anotosyonunu kullanıyor olacağız. Peki bu anotasyonlara neden ihtiyacımız var şimdi onları açıklayalım.

  • @ Retentation” :Uygulamamızın yaşam döngüsünde anotasyonumuzun nerede geçerli olduğunu belirtmek için kullandığımız anotasyondur. Burada kullanabileceğimiz üç farklı retention policy vardır.

RetentionPolicy.SOURCE : Ne derleyici (compiler) ne de çalışma (runtime) zamanında anotasyona ulaşılamaz.

RetentionPolicy.CLASS : Derleyici zamanında ulaşılabilir.

RetentionPolicy.RUNTIME : Derleyici ve çalışma zamanında ulaşılabilir.

  • @ Target” : Anotasyonu nerelerde kullanabileceğimizi belirtmek için kullanılan anotasyondur. Örneğin bir anotasyon sadece methodlar için kullanılırken başka bir anotasyon alan(field) tanımlamalarında kullanılabilir. Anotasyonu oluştururken birden fazla farklı yerleri hedefleyerek de kullanabiliriz (method, field, constructor, parameter gibi). Yukarıdaki örnekte ise oluşturduğumuz anotasyonu entity’de bulunan alanlar üzerinde kullanabiliriz.
  • @ Constraint” : İlgili senaryoya göre kendi validasyon logic’imizi uyguladığımız methodları içeren sınıfların kullanılacağını belirten anotasyondur. Aşağıdaki örnekte oluşturduğumuz custom anotasyonun hangi validasyon sınıfını çağırdığını görebilirsiniz.

Oluşturduğumuz validator sınıfına ConstraintValidator interface’ini implemente ettiğimizde isValid ve initialize methodlarını kullanabileceğiz. initialize methodunda anotasyonumuzda kullandığımız metadata’ları çekebiliriz. Yukarıdaki örnekte anotasyonda bulunan “value” parametresi usernamePrefix’ine setlenmiştir. Yani anotasyonu kullandığımız yerde “value” parametresi geçilmişse geçilen değer usernamePrefix’e setlenecektir. Eğer herhangi bir “value” parametresi geçilmemişse “Username.javainterface’inde görüldüğü üzere default olarak “zib” setlenecektir. isValid methodunda ise kendi validasyon logic’imizi yürüterek validasyon için boolean bir değer döneriz. Buradaki örnekte username parametresi usernamePrefix değeri ile başlıyor mu diye bir kontrol yapılarak geriye bir boolean değer döndürülmüştür.

Oluşturulan Anotasyonu Başka Validasyon Anotasyonları ile Nasıl Kombine Ederiz ?

Bu kombinasyonu yapmak düşünüldüğünden daha kolay bir işlemdir. Yukarıdaki örnekte de görüldüğü gibi anotasyonumuzu oluşturduğumuz interface üzerine; hangi validasyon anotasyonları ile kombine etmek istiyorsak onları ekliyoruz. Buradaki örnekte username için “@ Size” anotasyonu ile size kontrolü yapılmış olup min ve max size’lar setlenmiştir. Bunun yanında “@ NotNull” anotasyonu kullanılarak gelen datanın null kontrolü yapılmaktadır. Bize sağladığı avantajı yorumlayacak olursak kendi custom logic’imizle beraber 2 farklı validasyon anotasyonunu tek bir validasyon anotasyonuna çevirmiş bulunmaktayız.

Validasyon Yapılacak Alanları Gruplayarak İstenilen Grubu veya Grupları Nasıl Valide Ederiz ve Hangi Senaryolarda Buna İhtiyaç Duyulur ?

Gruplamaya neden ihtiyacımız olduğuyla ilgili örnek bir senaryodan bahsedecek olursak, iki aşamalı bir kayıt formumuz olduğunu düşünelim. İlk adımda kullanıcıdan adı, soyadı, e-posta kimliği, telefon numarası ve captcha gibi temel bilgileri vermesini istiyoruz. Kullanıcı bu verileri gönderdiğinde, sadece bu bilgiyi valide etmek istiyoruz. Bir sonraki adımda, kullanıcıdan adres gibi diğer bazı bilgileri sağlamasını istiyoruz ve bu bilgiyi de valide etmek istiyoruz. Bu gibi senaryolarda entity’de bulunan alanları gruplayarak hangilerinin valide edileceğini gruplandırabiliriz.

Gruplandırma işlemini nasıl yapacağımızı görmek için aşağıdaki örneği inceleyebiliriz.

Custom anotasyonumuzda bulunan “groups” parametresini “Username.java” interface’inde tanımlamıştık. Yukarıdaki örnekte ise 2 farklı gruplandırma olduğunu görebiliriz. BasicInfo.class ve AdvancedInfo.class yapıları aslında birer interface’dir. Interface isimleri aslında grup ismimiz olacak şekilde düzenlenmiştir.

Yukarıdaki örnekte gelen User datasının hangi grup bilgisine göre valide olacağı “@ Validated” anotasyonu ile belirtilmiştir. Burada hem BasicInfo grubunu içeren alanları hemde AdvanceInfo içeren alanları valide ediyoruz.

İlgili Anotasyona Özel Yük(payload) Nesneleri Atayarak Hata Durumunda Hangi Yolu İzleyeceği Nasıl Belirlenir ?

Oluşturulan custom anotasyonda bulunan “payload” parametresi belirtilen class’ı çağırarak validasyonun hata aldığı durumda hata senaryosunun işletilmesidir. Şimdi ise bir örnek üzerinden devam edelim.

İlk olarak “Payload” interface’inden extend alan bir interface tanımlıyoruz. Ve bu interface’e eklediğimiz methoda “ConstraintViolation” nesnesini parametre veriyoruz. Bu parametreyi validasyon sağlanmadığı durumlarda hata mesajı veya geçersiz olan değeri yakalamak için kullanmak için ekledik.

Yukarıdaki class’ta ise tanımladığımız interface’i implemente ederek validasyon başarısız olduğu durumda log basmasını sağlayan bir method örneğidir. Bu class’ı custom anotasyonumuzu kullandığımız alan için eklediğimizde validasyon sağlanmadığında gerekli hata senaryolarını işletecektir. “User.java” class’ındaki örnektede görüldüğü gibi “name” ve “username” alanları validasyondan geçemediğinde yukarıdaki “onError” methodu çağrılacaktır.

Enum veya nesneler için custom validasyon örnekleri ve daha detaylı kodları GitHub’ta bulunan örnekten inceleyebilirsiniz.

GitHub Repository

Referances/Kaynakça

https://www.baeldung.com/javax-validation-groups

https://www.logicbig.com/how-to/code-snippets/jcode-bean-validation-javax-validation-payload.html

--

--

Ibrahim Halil Koyuncu
Ibrahim Halil Koyuncu

No responses yet