Walidacja

Aplikacje mają to do siebie, że często pracują na danych z zewnątrz. Praktycznie zawsze. Nieważne czy są to dane pochodzące od użytkownika, wczytywane z pliku, pobierane z bazy danych lub innego serwisu – we wszystkich tych przypadkach dane poprostu mogą być niepoprawne.

Pracując z jakimikolwiek danymi trzeba mieć na uwadzę, że mogą dotrzeć one do aplikacji w złej postaci lub mogą niespełniać założonych kryteriów. Poprawność schematu danych często jest sprawdzana przez bibliotekę odpowiedzialną za deserializację danych. Gorzej jest w drugim przypadku.

Za sprawdzenie poprawności danych jesteśmy odpowiedzialni my – programiści. Nigdy nie możemy zakładać, że do aplikacji zawsze trafią dane takie jakich się spodziewamy. Jest to pewne, że w końcu nadejdzie moment apokalipsy – funkcjonalność aplikacji przestanie działać, a my będziemy się zastanawiać co poszło nie tak.

Dlatego tak ważna jest walidacja danych. Sprawdzając poprawność danych jesteśmy w stanie już na samym początku procesu “wyrzucić” odpowiedni komunikat błędu. Dzięki temu możemy uniknąć mozolnego debugowania kodu i pomocy klientowi. Bardzo prawdopodobne, że komunikat błędu wskaże klientowi gdzie dokładnie leży problem.

Bean Validation

Po wstępie odnośnie walidacji danych przejdźmy teraz do światy Javy, a dokładnie do standardu Bean Validation.

Jakiś czas temu pewnia grupa ekspertów, postawiła sobie za cel stworzenie standardu, który to dokładnie opisze jak powinny być walidowane JavaBean’y w aplikacjach enterprise tj. JavaEE.

Java Beans

Wspomniałem o JavaBeanach, więc jeszcze kilka słów wyjaśnienia dla osób nie mających o nich pojęcia.

Java Beans to kolejny standard odnoszący się bezpośrednio do klas – jest to zbiór reguł jakie powinny być spełnione przez klasę, aby móc nazwać ją beanem. Według tej specyfikacji klasa jest beanem jeśli spełnia następujące warunki:

  • Ma pola prywatne,
  • Udostępnia gettery i settery,
  • Posiada publiczny konstruktor bezargumentowy,
  • Implementuje interfejs Serializable.

W takim razie utworzony obiekt takiego typu klasy może być serializowany lub deserializowany. Krótko mówiąc taki obiekt można bez problemu zapisać do pliku, do bazy danych lub przesłać przez protokół TCP.

JSR 303 i JSR 380

Skoro rozumiesz już konwencję JavaBean to możemy wrócić do walidacji.

Standard Bean Validation wprowadził zbiór adnotacji oraz API, które mają być używane do walidacji Beanów. Ważne, że ten standard – tak samo jak np. JPA (Java Persistence API) – nie wprowadza żadnych implementacji, a jedynie API.

Pierwsza wersja standardu JSR 303 lub Bean Validation 1.0 powstała już w roku 2009 – czyli jest dostępna od Javy w wersji 6.

JSR 303 wprowadził do użycia adnotacje takie jak:

  • @Max– maksymalna wartość liczby;
  • @Min – analogicznie co @Max;
  • @Size – rozmiar listy;
  • @NotNull – sprawdzenie czy element nie jest null-em;
  • @Null – sprawdzenie czy element jest null-em;
  • @Pattern – sprawdzenie poprawności Stringa na podstawie wyrażenia regularnego;
  • @Future –  czy podana data jest większa niż aktualna;
  • @Past – czy podana data jest starsza niż aktualna.

Nie było może tego dużo, ale trzeba przyznać, że pozwalały one już na całkiem sprawne sprawdzanie poprawności danych. Oprócz adnotacji do weryfikacji poprawności danych, w API pojawiły się również interfejsy do obiektów, które miałyby być odpowiedzialne za późniejszą walidację np. Validator, ConstraintValidator.

Zaznaczam, że wszystko co znajdziecie w standardzie to jedynie API – czyli są to jedynie interfejsy oraz adnotacje. Implementacja może być dowolna, byle była zgodna z przyjętą specyfikacją.

Po 8 latach doczekaliśmy się Bean Validation 2.0 – JSR 380, który dodał jeszcze takie adnotacje:

  • @Email – sprawdzenie poprawność adresu e-mail;
  • @FutureOrPresent – analogiczna do @Future;
  • @PastOrPresent – analogiczna do @Past;
  • @Negative/@Positive/@PositiveOrZero/@NegativeOrZero – do sprawdzania wartości liczb;
  • @NotEmpty – do sprawdzenia czy String, tablica lub kolekcja nie jest pusta;
  • @NotBlank – do sprawdzenia czy Stringa zawiera inne znaki niż białe znaki;

Oprócz pojawienia się nowych adnotacji, JSR 380 dodał wsparcie dla typu Optional oraz nowych typów dat wprowadzonych w Java 8 (LocalDate, OffsetDateTime itd.). Dodatkowo pojawiła się możliwość używania adnotacji na typach parametryzowanych tzn. @NotEmpty
List<@NotBlank String> names;

Myślę, że wystarczająco Ci przybliżyłem Bean Validation. Jeśli chcesz poczytać więcej lub zapoznać się z JavaDocs tych standardów to takie informacje znajdziesz na tej stronie.

Implementacja

Jak już wspomniałem Bean Validation to jedynie standard – dokument oraz  zbiór interfejsów i adnotacji. Jeśli chcielibyśmy użyć go w projekcie to sam standard nam nic nie da. Potrzebujemy jego implementacji.

Najpopularniejszą implementacją JSR jest HibernateValidator. Tak naprawdę jest zazwyczaj używana nie tylko ze względu na swoją popularność, ale również swój status. Hibernate Validator jest oficjalnie certyfikowaną implementacją – dzięki temu mamy pewność, że dostarczona implementacja jest w 100% zgodna ze specyfikacją. Certyfikat został przyznany w oparciu o Technology Compatibility Kit.

Jak walidować zgodnie ze standardem Bean Validation?

Przykład przedstawię na projekcie Maven. Na początek należy do projektu dodać zależność hibernate-validator w wersji 7.x – czyli implementację standardu JSR 380.

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>7.0.4.Final</version>
</dependency>

Hibernate-validator wymaga jeszcze jednej zależności – Expression Language, aby móc ewaluować dynamiczne wyrażenia. Jeśli aplikacja jest uruchamiana w kontenerze takim jak WildFly lub JBoss EAP, to poniższa zależność jest już dostarczona przez kontener.

<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <version>4.0.1</version>
</dependency>

I wszystko jest już gotowe – teraz potrzebujemy klasy, którą będziemy chcieli walidować.

class User {
    @NotBlank
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Nad polem name pojawiła się adnotacja @NotBlank, która zapewni, że podany String nigdy nie będzie pusty.

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Tworzymy obiekt validatora, który posłuży nam do sprawdzania poprawności danych obiektu.

User unknown = new User("");
User pablo = new User("Pablo");

Set<ConstraintViolation<User>> unknownUserValidationViolations = validator.validate(unknown);
System.out.println("Unknown=" + unknownUserValidationViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(",")));

Set<ConstraintViolation<User>> pabloUserValidationViolations = validator.validate(pablo);
System.out.println("Pablo=" + pabloUserValidationViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(",")));

Następnie tworzymy dwa obiektu typu User – jeden poprawny według założonych kryteriów, zaś drugi niepoprawny. Oba obiekty są sprawdzane przy użyciu metody validate z stworzonego wcześniej walidatora.

Po uruchomieniu tego kodu otrzymujemy w konsoli taki rezultat:

Unknown=nie może być puste
Pablo=

Jak widać walidator spełnił swoją rolę i “wyrzucił” dokładną wiadomosć co jest nie tak ze stanem obiektu.

Wiadomości błędu są generowane na podstawie ustawienia Locale – jeśli chcesz otrzymywać komunikaty w języku angielskim to na początku swojego kodu dodaj Locale.setDefault(Locale.ENGLISH).

Podsumowanie

W tym wpisie przedstawiłem Ci na czym polega standard Bean Validation i wyjaśniłem czym są enigmatyczne zapisy JSR 303 i JSR 380. Na przykładzie prostego projektu pokazałem jak użyć implementacji hibernate-validator do walidowania java beanów.

Mam nadzieję, że po tym wpisie uświadomiłeś sobie jak istotna jest walidacja danych oraz jak można jej dokonać przy standardu Bean Validation. W innym wpisie pojawi się przykład walidacji w aplikacji web przy użyciu Spring Boota, w której użyjemy już znanego Ci hibernate-validatora.

Kamil Klimek

Od 2016 jestem programistą Java. Przez pierwsze 4 lata pracowałem jako Full Stack Java Developer. Później postanowiłem postawić nacisk na Javę, żeby jeszcze lepiej ją poznać.

Subscribe
Powiadom o
guest
0 komentarzy
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x