Spring'de DTO'larda custom validation annotationlar nasıl gerçekleştirilir? İleri düzey Java kod örnekleriyle beraber




Spring Framework'te özelleştirilmiş doğrulama kurallarını oluşturmanın birkaç yolu vardır. En popüler yaklaşım, Java'nın Bean Validation API'sini (JSR 380) kullanmaktır. Bu yaklaşımı kullanarak kendi özel doğrulama annotation'larınızı oluşturabilirsiniz.


Bu adımları takip ederek bir özel doğrulama annotation oluşturabilirsiniz:


İlk olarak özel bir annotation oluşturmanız gerekir. Bu annotation, doğrulamanın kullanılacağı yerlerde kullanılacak:


import javax.validation.Constraint;

import javax.validation.Payload;

import java.lang.annotation.*;


@Documented

@Constraint(validatedBy = CustomValidator.class)

@Target( { ElementType.METHOD, ElementType.FIELD })

@Retention(RetentionPolicy.RUNTIME)

public @interface CustomValidation {

    String message() default "{customvalidation}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

Bu kod, bir "CustomValidation" adında özel bir doğrulama annotation oluşturur. @Constraint(validatedBy = CustomValidator.class) satırı, bu annotation'ı kullandığınızda hangi sınıfın kullanılacağını belirtir.


Şimdi, belirlediğimiz CustomValidator sınıfını oluşturmalıyız. Bu sınıf, özel doğrulamanın nasıl gerçekleştirileceğini belirler:


import javax.validation.ConstraintValidator;

import javax.validation.ConstraintValidatorContext;


public class CustomValidator implements ConstraintValidator<CustomValidation, Object> {


    @Override

    public void initialize(CustomValidation constraintAnnotation) {

        // Özelleştirilmiş doğrulamanın başlatılması gereken herhangi bir şey varsa, burada yapılır.

    }


    @Override

    public boolean isValid(Object value, ConstraintValidatorContext context) {

        // value, doğrulamayı yapacağınız değerdir.

        // Bu değeri kontrol edip, true veya false döndürmelisiniz.

        // Örneğin: 

        if(value == null){

            return false;

        }

        return true;

    }

}

Son adımda, oluşturduğunuz annotation'ı DTO'da istediğiniz yerde kullanabilirsiniz:


public class MyDto {


    @CustomValidation

    private String myField;


    // getter ve setter'lar

}

Bu şekilde, myField'ın null olmadığını kontrol eden özel bir doğrulama oluşturduk. Özelleştirilmiş doğrulamanızı genişletebilir ve farklı senaryolar için değiştirebilirsiniz.


Bu örnek, basit bir senaryo için oluşturulmuştur ve genellikle karmaşık durumlar için daha fazla kod ve düşünme gerektirir. Ayrıca, bu doğrulamanın nasıl çalıştığını ve hatalarını nasıl işleyeceğinizi de düşünmelisiniz. Her durumda, Spring'in güçlü doğrulama mekanizması size bu konuda yardımcı olabilir.


Daha karmaşık bir senaryo için tam bir örnek verir misin?


Elbette, daha karmaşık bir senaryo için özelleştirilmiş bir doğrulama annotation'ı oluşturabiliriz. Örneğin, bir @ValidPassword annotation'ı oluşturalım. Bu annotation, parolanın belirli kurallara (en az bir büyük harf, en az bir küçük harf, en az bir sayı ve belirli bir minimum uzunluk) uyup uymadığını kontrol eder.


Öncelikle, @ValidPassword annotation'ını oluştururuz:


import javax.validation.Constraint;

import javax.validation.Payload;

import java.lang.annotation.*;


@Documented

@Constraint(validatedBy = PasswordValidator.class)

@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface ValidPassword {

    String message() default "Invalid password. Password must have at least 8 characters, including at least one uppercase letter, one lowercase letter and one number.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

Daha sonra, belirlediğimiz PasswordValidator sınıfını oluştururuz:


import javax.validation.ConstraintValidator;

import javax.validation.ConstraintValidatorContext;

import java.util.regex.Pattern;


public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {


    private static final String PASSWORD_PATTERN = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,}$";


    @Override

    public void initialize(ValidPassword constraintAnnotation) {

        // Initialization code if needed

    }


    @Override

    public boolean isValid(String password, ConstraintValidatorContext context) {

        return password != null && Pattern.matches(PASSWORD_PATTERN, password);

    }

}

Bu sınıf, parolanın belirli bir düzeni karşılayıp karşılamadığını kontrol eder. Regex (düzenli ifade) kullanılarak bir pattern oluşturulur ve bu pattern ile parolanın uyumlu olup olmadığı kontrol edilir.


Son olarak, oluşturduğumuz annotation'ı DTO'da kullanabiliriz:


public class UserDto {


    private String username;


    @ValidPassword

    private String password;


    // getter ve setter'lar

}

Bu örnekte, password alanının belirli kurallara (en az 8 karakter, en az bir büyük harf, en az bir küçük harf ve en az bir sayı içerme) uyması gerektiğini belirttik.


Bu, daha karmaşık bir özelleştirilmiş doğrulama örneğidir. Ancak, burada belirttiğimiz doğrulama kuralları hala oldukça basittir ve genellikle daha karmaşık ve özelleştirilmiş kurallar gerekebilir. Her durumda, burada gösterildiği gibi özelleştirilmiş annotation'lar oluşturarak bu tür kuralları kolayca belirleyebilirsiniz.


Advanced bir örnek daha verir misin?


Elbette, biraz daha karmaşık bir örnek verelim. Bu örnekte, bir DTO'nun birden fazla alanı üzerinde bir doğrulama yapacağız. "Başlangıç tarihi", "Bitiş tarihi"nden önce olmalı ve bu iki tarihi doğrulayacak bir özel annotation oluşturacağız.


İlk olarak, @DateRange annotation'ı oluştururuz:


import javax.validation.Constraint;

import javax.validation.Payload;

import java.lang.annotation.*;


@Documented

@Constraint(validatedBy = DateRangeValidator.class)

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface DateRange {

    String message() default "Start date must be before end date";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String startDate();

    String endDate();

}

Ardından DateRangeValidator sınıfını oluştururuz:


import javax.validation.ConstraintValidator;

import javax.validation.ConstraintValidatorContext;

import java.time.LocalDate;


public class DateRangeValidator implements ConstraintValidator<DateRange, Object> {


    private String startDateFieldName;

    private String endDateFieldName;


    @Override

    public void initialize(DateRange constraintAnnotation) {

        this.startDateFieldName = constraintAnnotation.startDate();

        this.endDateFieldName = constraintAnnotation.endDate();

    }


    @Override

    public boolean isValid(Object value, ConstraintValidatorContext context) {

        try {

            LocalDate startDate = (LocalDate) value.getClass().getDeclaredField(startDateFieldName).get(value);

            LocalDate endDate = (LocalDate) value.getClass().getDeclaredField(endDateFieldName).get(value);

            return startDate.isBefore(endDate);

        } catch (Exception e) {

            // Handle the exception appropriately

            e.printStackTrace();

        }

        return false;

    }

}

Son olarak, oluşturduğumuz @DateRange annotation'ı DTO üzerinde kullanabiliriz:


@DateRange(startDate = "startDate", endDate = "endDate")

public class EventDto {

    private String name;

    private LocalDate startDate;

    private LocalDate endDate;


    // getter ve setter'lar

}

Bu örnekte, startDate ve endDate alanlarının başlangıç tarihinin bitiş tarihinden önce olması gerektiğini belirttik. Bu, birden çok alanın değerlerini karşılaştırmak için özelleştirilmiş bir annotation oluşturmak için karmaşık bir örnektir.


Ayrıca ConstraintValidator sınıfında reflection kullanmayı ve bir hata durumunda ne yapılacağını (hatayı bastırmayı, özel bir hata atmayı vs.) düşünmeyi de not edin. Bu tür durumlar genellikle uygulamanın gereksinimlerine ve uygulama içerisinde nasıl hata yönetimi yapılacağına bağlıdır.


Daha advanced bir örnek verir misin?


Çok ileri düzey bir senaryoda, DTO'nun bir bölümünün başka bir bölümünün durumuna bağlı olarak doğrulanması gerektiğini düşünelim. Bir kullanıcının kredi kartı bilgilerini sakladığımız bir DTO'nun örneğini ele alalım. Bir kullanıcı eğer "Kartı Kaydet" seçeneğini seçerse, kart bilgilerinin eksiksiz olduğunu kontrol etmek isteyebiliriz.


Bu durumda DTO'muz şu şekilde görünebilir:


public class UserDto {

    private String username;

    private String password;


    private boolean saveCard;

    private String cardNumber;

    private String cardHolderName;

    private String expiryDate;


    // getter ve setter'lar

}

saveCard boolean değeri true olduğunda, cardNumber, cardHolderName ve expiryDate alanlarının doldurulmuş olması gerekiyor. Bu durumu özel bir validation annotation ile kontrol edebiliriz.


Öncelikle, @ValidCardInfo annotation'ını oluştururuz:


import javax.validation.Constraint;

import javax.validation.Payload;

import java.lang.annotation.*;


@Documented

@Constraint(validatedBy = CardInfoValidator.class)

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface ValidCardInfo {

    String message() default "Card information must be provided when save card option is selected.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

Ardından CardInfoValidator sınıfını oluştururuz:

import javax.validation.ConstraintValidator;

import javax.validation.ConstraintValidatorContext;


public class CardInfoValidator implements ConstraintValidator<ValidCardInfo, UserDto> {


    @Override

    public void initialize(ValidCardInfo constraintAnnotation) {

        // Initialization code if needed

    }


    @Override

    public boolean isValid(UserDto userDto, ConstraintValidatorContext context) {

        if (userDto.isSaveCard()) {

            return userDto.getCardNumber() != null && !userDto.getCardNumber().isEmpty() &&

                    userDto.getCardHolderName() != null && !userDto.getCardHolderName().isEmpty() &&

                    userDto.getExpiryDate() != null && !userDto.getExpiryDate().isEmpty();

        } else {

            return true;

        }

    }

}

Bu sınıf, saveCard boolean değeri true olduğunda kart bilgilerinin dolu olup olmadığını kontrol eder.


Son olarak, oluşturduğumuz @ValidCardInfo annotation'ını DTO üzerinde kullanabiliriz:


@ValidCardInfo

public class UserDto {

    private String username;

    private String password;


    private boolean saveCard;

    private String cardNumber;

    private String cardHolderName;

    private String expiryDate;


    // getter ve setter'lar

}

Bu örnekte, birden fazla alanın değerlerini karşılaştırmak için özelleştirilmiş bir annotation oluşturduk ve bu doğrulamanın bir alanın durumuna bağlı olarak tetiklendiğini belirttik. Bu, daha karmaşık bir durum için ileri düzey bir örnektir ve DTO'nun birden fazla alanının durumunu kontrol etmek gerektiğinde kullanışlıdır.


Spring servis sınıflarında dto validasyonları için arka arkaya validasyon ihtiyacında iterator ve chain of responsibility patternleri nasıl kullanılır? : https://www.turkninja.com/2023/07/spring-servis-snflarnda-dto.html

1 Yorumlar

Please Select Embedded Mode To Show The Comment System.*

Daha yeni Daha eski

نموذج الاتصال