Doğrulama (Validation)
Bir uygulamanın en kırılgan sınırı, dış dünyadan veri aldığı yerdir: kullanıcı formları, API istekleri, dosya içerikleri. "Yaş negatif olmasın", "e-posta geçerli olsun", "ad boş olmasın" gibi kuralları her yerde elle…
Doğrulama (Validation)
Bir uygulamanın en kırılgan sınırı, dış dünyadan veri aldığı yerdir: kullanıcı formları, API
istekleri, dosya içerikleri. "Yaş negatif olmasın", "e-posta geçerli olsun", "ad boş olmasın"
gibi kuralları her yerde elle if/else ile kontrol etmek hem yorucu hem de hataya açıktır.
Bean Validation (JSR-380), bu kuralları anotasyonlarla bildirimsel olarak tanımlamanı ve
tek bir mekanizmayla uygulamanı sağlar. Spring bunu derinlemesine destekler.
Bildirimsel kurallar
Kuralları, doğrulanacak alanların üstüne anotasyon olarak koyarsın:
class KullaniciKaydi { @NotBlank(message = "ad boş olamaz") String ad; @Email String eposta; @Min(18) @Max(120) int yas; @Size(min = 8) String parola; }
En sık kullanılan kısıtlar: @NotNull, @NotBlank, @NotEmpty, @Size, @Min/@Max,
@Email, @Pattern, @Positive/@Negative, @Past/@Future. Doğrulamayı bir Validator
çalıştırır ve tüm ihlalleri tek seferde toplar (ilk hatada durmaz):
Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); Set<ConstraintViolation<KullaniciKaydi>> ihlaller = validator.validate(kayit);
Örnek 1 (./Ornek1.java) geçerli ve hatalı bir kayıt formunu doğrular ve hatalı olanın tüm
ihlallerini (alan + mesaj) listeler.
Bean Validation bir standarttır (API); en yaygın uygulaması Hibernate Validator'dır. Spring Boot,
spring-boot-starter-validationile bunu hazır getirir.
Özel (custom) kurallar
Standart kısıtlar yetmediğinde — "güçlü parola", "TC kimlik no", "IBAN", "ürün kodu formatı" gibi
domain kuralları — kendi anotasyonunu yazabilirsin. İki parça gerekir: @Constraint ile
işaretli bir anotasyon ve mantığı içeren bir ConstraintValidator:
@Constraint(validatedBy = GucluParolaValidator.class) @Target(FIELD) @Retention(RUNTIME) @interface GucluParola { String message() default "..."; /* groups, payload */ } class GucluParolaValidator implements ConstraintValidator<GucluParola, String> { public boolean isValid(String v, ConstraintValidatorContext c) { /* kural */ } }
Artık @GucluParola tek anotasyonla her yerde kullanılır (DRY) ve standart @Email ile aynı
mekanizmaya dahildir. Örnek 2 (./Ornek2.java) bir "güçlü parola" kuralını uçtan uca yazar.
Spring entegrasyonu: @Validated ve @Valid
Spring, doğrulamayı manuel Validator çağırmadan otomatikleştirir:
- Metot doğrulaması: Bir bean'i
@Validatedile işaretler, metot parametrelerine kısıt koyarsan, Spring her çağrıda parametreleri otomatik doğrular ve ihlaldeConstraintViolationExceptionfırlatır. Örnek 3 (./Ornek3.java) bunu gösterir (MethodValidationPostProcessor+@Validated). - Web/controller doğrulaması: Spring MVC'de bir
@RequestBodyDTO'yu@Validile işaretlersin; Spring gelen JSON'u doğrular, hata varsaMethodArgumentNotValidExceptionile isteği 400 Bad Request'e çevirir. Bunu Spring Boot bölümünde, gerçek bir REST endpoint'inde göreceğiz.
@PostMapping("/kullanici") public ... kayit(@Valid @RequestBody KullaniciKaydi k) { ... } // otomatik doğrulama
@Validatedve metot doğrulaması proxy tabanlıdır (AOP); bu yüzden self-invocation tuzağı burada da geçerlidir.@Validise iç içe (nested) nesneleri de doğrulamak için kullanılır.
Özet
Bean Validation ile kuralları anotasyonla bildirmeyi ve Validator ile tüm ihlalleri toplamayı
(Örnek 1), @Constraint + ConstraintValidator ile özel kurallar yazmayı (Örnek 2) ve
@Validated ile Spring'in metot parametrelerini otomatik doğrulamasını (Örnek 3) öğrendik. Bu,
uygulamanın "giriş kapısını" temiz ve güvenli tutmanın standart yoludur.
Bununla Spring Framework çekirdeği ve temel uygulama katmanlarını (veri erişimi, transaction, validation) tamamlamış olduk. Güvenlik (Spring Security) bir web bağlamında çok daha anlamlı olduğundan, onu Spring Boot bölümünde gerçek bir uygulama üzerinde ele alacağız. Sırada: Spring Boot.
▶ Kod Örnekleri(3)
Ornek1
ortam gerekir1// Ornek1: Bean Validation — kuralları anotasyonla bildir, Validator ile topluca kontrol et.
2// Gerçek senaryo: bir kayıt formundaki tüm hataları tek seferde toplamak.
3// Çalıştırma: portal Spring + hibernate-validator classpath'iyle çalıştırır.
4import jakarta.validation.Validation;
5import jakarta.validation.Validator;
6import jakarta.validation.constraints.Email;
7import jakarta.validation.constraints.Max;
8import jakarta.validation.constraints.Min;
9import jakarta.validation.constraints.NotBlank;
10import jakarta.validation.constraints.Size;
11
12public class Ornek1 {
13
14 // Kurallar, anotasyonlarla doğrudan alanların üstünde TANIMLANIR (bildirimsel).
15 static class KullaniciKaydi {
16 @NotBlank(message = "ad boş olamaz")
17 String ad;
18
19 @Email(message = "geçerli bir e-posta girin")
20 String eposta;
21
22 @Min(value = 18, message = "yaş en az 18 olmalı")
23 @Max(value = 120, message = "yaş en fazla 120 olabilir")
24 int yas;
25
26 @Size(min = 8, message = "parola en az 8 karakter olmalı")
27 String parola;
28
29 KullaniciKaydi(String ad, String eposta, int yas, String parola) {
30 this.ad = ad; this.eposta = eposta; this.yas = yas; this.parola = parola;
31 }
32 }
33
34 public static void main(String[] args) {
35 Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
36
37 var gecerli = new KullaniciKaydi("Ada", "ada@site.com", 30, "guclu-parola");
38 var hatali = new KullaniciKaydi("", "gecersiz-eposta", 15, "123");
39
40 System.out.println("Geçerli kayıt -> ihlal sayısı: " + validator.validate(gecerli).size());
41
42 System.out.println("\nHatalı kayıt -> ihlaller:");
43 // validate() TÜM ihlalleri tek seferde döndürür (ilk hatada durmaz).
44 validator.validate(hatali).forEach(v ->
45 System.out.println(" - " + v.getPropertyPath() + ": " + v.getMessage()));
46
47 System.out.println("""
48
49 --- Bean Validation (JSR-380) ---
50 Kuralları if/else ile elle yazmak yerine anotasyonla bildirirsin: @NotBlank, @Email,
51 @Min/@Max, @Size, @NotNull, @Pattern, @Positive...
52 Validator tüm nesneyi gezip ihlalleri toplar. Spring MVC'de bu işi @Valid otomatik yapar
53 ve hataları 400 Bad Request'e çevirir (Spring Boot bölümünde göreceğiz).""");
54 }
55}Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek1.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.
Ornek2
ortam gerekir1// Ornek2: Özel (custom) doğrulama kuralı — kendi @Constraint anotasyonun ve ConstraintValidator'ın.
2// Gerçek senaryo: "güçlü parola" kuralını yeniden kullanılabilir bir anotasyona dönüştürmek.
3// Çalıştırma: portal bu dosyayı derleyip Spring + hibernate-validator classpath'iyle çalıştırır.
4package com.egitim.spring.validation;
5
6import jakarta.validation.Constraint;
7import jakarta.validation.ConstraintValidator;
8import jakarta.validation.ConstraintValidatorContext;
9import jakarta.validation.Payload;
10import jakarta.validation.Validation;
11import jakarta.validation.Validator;
12
13import java.lang.annotation.ElementType;
14import java.lang.annotation.Retention;
15import java.lang.annotation.RetentionPolicy;
16import java.lang.annotation.Target;
17
18public class Ornek2 {
19
20 // 1) Özel anotasyon: hangi validator'ın kontrol edeceğini @Constraint ile belirtiriz.
21 @Constraint(validatedBy = GucluParolaValidator.class)
22 @Target(ElementType.FIELD)
23 @Retention(RetentionPolicy.RUNTIME)
24 @interface GucluParola {
25 String message() default "parola en az 8 karakter, bir büyük harf ve bir rakam içermeli";
26 Class<?>[] groups() default {};
27 Class<? extends Payload>[] payload() default {};
28 }
29
30 // 2) Kuralın asıl mantığı: ConstraintValidator. (public: doğrulayıcı reflection ile örneklenir.)
31 public static class GucluParolaValidator implements ConstraintValidator<GucluParola, String> {
32 @Override
33 public boolean isValid(String deger, ConstraintValidatorContext ctx) {
34 if (deger == null) return false;
35 boolean uzun = deger.length() >= 8;
36 boolean buyukVar = deger.chars().anyMatch(Character::isUpperCase);
37 boolean rakamVar = deger.chars().anyMatch(Character::isDigit);
38 return uzun && buyukVar && rakamVar;
39 }
40 }
41
42 // 3) Kullanım: artık tek anotasyonla uygulanır.
43 static class Hesap {
44 @GucluParola
45 String parola;
46 Hesap(String parola) { this.parola = parola; }
47 }
48
49 public static void main(String[] args) {
50 Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
51
52 String[] denemeler = {"abc", "uzunamarakamyok", "Guclu123", "ZAYIF123"};
53 for (String p : denemeler) {
54 var ihlaller = validator.validate(new Hesap(p));
55 String sonuc = ihlaller.isEmpty() ? "GEÇERLİ"
56 : "GEÇERSİZ (" + ihlaller.iterator().next().getMessage() + ")";
57 System.out.printf(" %-18s -> %s%n", "'" + p + "'", sonuc);
58 }
59
60 System.out.println("""
61
62 --- Özel kural ne kazandırır? ---
63 'Güçlü parola' kuralını bir kez yazıp her yerde @GucluParola ile uygularsın (DRY).
64 @Constraint + ConstraintValidator, standart anotasyonların (@Email vb.) aynı mekanizmasıdır;
65 yani kendi domain kurallarını (TC kimlik, IBAN, ürün kodu...) standart sisteme eklersin.""");
66 }
67}Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek2.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.
Ornek3
ortam gerekir1// Ornek3: Spring entegrasyonu — @Validated ile metot parametrelerinin OTOMATİK doğrulanması.
2// @Validated proxy tabanlıdır; bu yüzden portal bu dosyayı derleyip çalıştırır.
3package com.egitim.spring.validation;
4
5import jakarta.validation.ConstraintViolationException;
6import jakarta.validation.constraints.Min;
7import jakarta.validation.constraints.NotBlank;
8import org.springframework.context.annotation.AnnotationConfigApplicationContext;
9import org.springframework.context.annotation.Bean;
10import org.springframework.context.annotation.Configuration;
11import org.springframework.validation.annotation.Validated;
12import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
13
14public class Ornek3 {
15
16 public static void main(String[] args) {
17 var ctx = new AnnotationConfigApplicationContext(Config.class);
18 SiparisServisi servis = ctx.getBean(SiparisServisi.class);
19
20 // Geçerli çağrı: sorunsuz.
21 servis.siparisVer("Klavye", 2);
22
23 // Geçersiz çağrılar: Spring, metoda GİRMEDEN parametreleri doğrular ve hata fırlatır.
24 denemeHatali(() -> servis.siparisVer("", 2), "boş ürün adı");
25 denemeHatali(() -> servis.siparisVer("Mouse", 0), "adet < 1");
26
27 ctx.close();
28 System.out.println("""
29
30 --- @Validated metot doğrulaması ---
31 @Validated + MethodValidationPostProcessor ile metot parametrelerindeki kısıtlar
32 (@NotBlank, @Min...) çağrı anında OTOMATİK kontrol edilir; ihlalde ConstraintViolationException.
33 Spring MVC'de @Valid + @RequestBody ile gelen DTO'lar da aynı şekilde doğrulanır
34 ve hata 400 Bad Request'e çevrilir (Spring Boot bölümünde).""");
35 }
36
37 static void denemeHatali(Runnable r, String aciklama) {
38 try {
39 r.run();
40 System.out.println(" (beklenmedik: hata fırlamadı) " + aciklama);
41 } catch (ConstraintViolationException e) {
42 System.out.println(" Reddedildi (" + aciklama + "): " + e.getConstraintViolations().iterator().next().getMessage());
43 }
44 }
45
46 @Configuration
47 public static class Config {
48 // Bu post-processor, @Validated bean'lerin metotlarını doğrulayan proxy'yi kurar.
49 @Bean static MethodValidationPostProcessor mvpp() { return new MethodValidationPostProcessor(); }
50 @Bean SiparisServisi siparisServisi() { return new SiparisServisi(); }
51 }
52
53 // @Validated: bu bean'in metot parametreleri doğrulamaya tabi olsun.
54 @Validated
55 public static class SiparisServisi {
56 public void siparisVer(@NotBlank(message = "ürün adı zorunlu") String urun,
57 @Min(value = 1, message = "adet en az 1 olmalı") int adet) {
58 System.out.println("Sipariş alındı: " + adet + " adet " + urun);
59 }
60 }
61}Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek3.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.