Walidacja obiektów w Spring Framework

Walidacja obiektów w Spring Framework

Jednym z podstawowych zadań każdej aplikacji jest sprawdzanie danych wejściowych. Dlatego każda aplikacja powinna być wyposażona w uniwersalny mechanizm, który taką walidację zapewni. Spring Framework ma wbudowane dwa takie mechanizmy, o tym właśnie możesz przeczytać w tym artykule.

Pierwszy z nich korzysta ze specyfikacji Bean Validation (JSR 380), drugi z mechanizmów Springa DataBinder. Oczywiście, możemy jeszcze zaimplementować swoje własne mechanizmy np. oparte o AOP (Aspect Oriented Programming), bądź najprościej jak się da, walidować obiekty ręcznie, sprawdzając ich pola i obsługiwać błędy.

Natomiast te dwa mechanizmy są wbudowane w framework i warto mieć je dobrze opanowane – na pewno się przydadzą.

 

Walidacja za pomocą Bean Validation

Historia Bean Validation sięga roku 2009, gdzie została wydana pierwsza wersja tej specyfikacji (JSR 303). W 2017 mieliśmy kolejną wersję specyfikacji (Bean Validation 2.0, JSR 380), która rozszerzała istniejący standard o nowe elementy, między innymi wsparcie dla Javy 8).

Idea działania tego mechanizmu jest bardzo prosta. Umieszczasz odpowiednią adnotację np. @NotNull na polu klasy, wrzucasz taki obiekt do walidatora, który implementuje ten standard i gotowe.

A jak wygląda to w Springu ?

class Person {
  @NotEmpty
  private String name;
  @Min(18)
  private int age;
  @Email
  private String email;

  // gettery i settery
}

I w kontrolerze:

@PostMapping("/person")
public ResponseEntity<Object> savePerson(@RequestBody @Valid Person personDto) {
  // ... implementacja ...
  return ResponseEntity.ok().build()
}

 

Uwaga: Konieczna tutaj jest adnotacja @Valid. Pisałem już o tym w: Najczęściej popełniane błędy w Spring Framework.

 

O ile ten mechanizm i podstawowe walidatory są znane większości developerów, tak przynajmniej zakładam, to ostatnia specyfikacja wprowadza kilka ciekawych nowości:

  • walidacje obiektów zawartych w kolekcjach, poprzez adnotowanie typów generycznych List<@Email String> emailList,
  • wsparcie walidacji w Optional Optional<@Past LocalDate>,
  • wsparcie dla Java 8 Date/Time API poprzez nowe adnotacje @Past, @Future, @PastOrPresent i @FutureOrPresent,
  • nowe adnotacje @Email, @NotEmpty, @NotBlank, @Positive, @PositiveOrZero, @Negative, @NegativeOrZero.

 

Customowe adnotacje / validatory

Mechanizm ten pozwala na tworzenie własnych adnotacji (walidatorów). Dzięki czemu możemy walidować bardziej zindywidualizowane dane, lub robić walidację krzyżową na różnych polach.

Przykładowa implementacja takiej adnotacji:

@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy =MyCustomeValidator.class)
public @interface MyCustomeAnnotation {
  String message() default "{mycustom.message}";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

oraz implementacja walidatora:

public class IbanValidator implements ConstraintValidator<MyCustomAnnotation, String> {
  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    // ... implementacja ...
  }
}

Taką walidację można też wywołać ręcznie, w dowolnym miejscu aplikacji, używając ValidatorFactory:

Validation.buildDefaultValidatorFactory().getValidator().validate(obj)

 

Domyślną implementacja Bean Validation w Springu jest Hibernate Validator. Można go też używać niezależnie od Springa.

 

Walidacja za pomocą Spring validation

Kolejnym sposobem walidacje w Springu jest DataBinding. Jest to mechanizm Spring Validation pozwalający przyporządkować walidator do obiektu poprzez WebDataBinder.

Przykładowa implementacja walidatora w Springu, implementuje interfejs org.springframework.validation.Validator:

public class MyCustomValidator implements Validator {

  @Override
  public boolean supports(Class<?> clazz) {
    return PersonDto.class.isAssignableFrom(clazz);
  }

  @Override
  public void validate(Object target, Errors errors) {
    PersonDto person = (PersonDto) target;
    if (person.getName().equals("admin")) {
     errors.reject("name", "Nie możesz użyć nazwy admin");
    }
  }
}

Walidatory tego typu można używać do bardziej skomplikowanych przypadków, w których adnotacje Bean Validation nie wystarczają.

 

@RestController
public class MyController {

  @InitBinder("personDto")
  protected void initBinder(WebDataBinder binder) {
    binder.addValidators(new MyCustomValidator());
  }

  @PostMapping("/binder")
  public ResponseEntity<Object> addPerson(@RequestBody @Valid PersonDto person) {
    // ... implementacja ...
    return ResponseEntity.ok().build()
  }
}

WebDataBinder i adnotacja @InitBinder("personDto") podłącza walidator do obiektu requestu PersonDto. Powiązanie to jest tworzone poprzez nazwę typu (PersonDto -> personDto – konwencja Springowa). Gdy nie podamy do jakiego parametru chcemy powiązać walidator, wtedy zostanie on podłączony do wszystkich obiektów wejściowych.

Dodatkowo, możemy tutaj skorzystać z klasy ValidationUtils, która zawiera metody pomagające sprawdzać pola walidowanego obiektu.

 

Uwaga: W tym wypadku również konieczna jest adnotacja @Valid. Kolejną rzeczą, na którą trzeba zwrócić uwagę, to użycie metody addValidators(...). Jeśli zamiast niej użyjecie setValidator(...), to wszystkie pozostałe walidacje (np. Bean Validation), zostaną wyłączone. Kiedyś niestety popełniłem ten błąd i kosztowało mnie to kilka godzin debugowania.

 

Podsumowanie

Walidacja to podstawa każdej, solidnie napisanej aplikacji. Dlatego warto zagłębić się w ten temat. A już niedługo na blogu pojawią się kolejne artykuły poświęcone nie tylko temu zagadnieniu, ale także Spring Framework, Hibernate i testom jednostkowym.

 

Źródła:

https://beanvalidation.org/2.0/

https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation

https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-config-validation

 

Mateusz Dąbrowski

Cześć jestem Mateusz, zajmuję się programowaniem już ponad 12 lat z czego ponad 8 programuję w Javie. Zapraszam Cię do lektury mojego bloga. Możesz przeczytać więcej o mnie >>TUTAJ<<

2 thoughts to “Walidacja obiektów w Spring Framework”

  1. Siema, mam pytanie. Chcę podać 2 argumenty z RequestBody, ale to niestety nie zadziała. Szukałam rozwiązania w necie jak złączyć w jedno, ale wszędzie są stare komentarze bez szczegółów przez co nie wiem jak do tego podejść. Z góry dzięki za odp.

    1. Tak się raczej nie robi. Zrób klasę DTO, która będzie zawierała argument 1 i agrument 2. Później możesz sobie wyciągnąć z tej klasy oba argumenty.

Komentarze są zamknięte.