Jak poprawnie używać @Value i @ConfigurationProperties w Spring Boot?

You are currently viewing Jak poprawnie używać @Value i @ConfigurationProperties w Spring Boot?

Wstęp

Każdy z nas, pracując w niemal każdym projekcie w technologii Spring Boot, codziennie korzysta z plików application.properties czy application.yml. Wykorzystujemy je do pobierania adresu do bazy danych, klucza do zewnętrznego API, czy choćby prostej flagi włączającej nową funkcjonalność. Najczęściej widzimy w kodzie dwa podejścia do pobrania : adnotację @Value i @ConfigurationProperties. Które jest lepsze? Kiedy i dlaczego powinniśmy wybrać jedno zamiast drugiego? W tym wpisie z serii „Szybki strzał” przyjrzymy się obu podejściom i pokażę, w jakich sytuacjach które z nich sprawdzi się najlepiej.

@Value – szybkie rozwiązanie dla pojedynczych wartości

Adnotacja @Value to najprostszy sposób na wstrzyknięcie pojedynczej wartości z pliku konfiguracyjnego prosto do pola w naszym komponencie. Wyobraź sobie, że chcesz wczytać nazwę swojej aplikacji.

W application.properties dodajesz wpis:

spring.application.name="Moja Super Nazwa Aplikacji"

A w kodzie serwisu lub komponentu wstrzykujesz tę wartość:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class AppInfoService {

    @Value("${spring.application.name}")
    private String appName;

    public String getAppName() {
        return appName;
    }
}

Proste, prawda? @Value świetnie sprawdza się w takich właśnie sytuacjach. Możemy też łatwo zdefiniować wartość domyślną, na wypadek gdyby właściwość nie została zdefiniowana w pliku:

@Value("${feature.toggle.new-dashboard:false}")
private boolean newDashboardEnabled;

Kiedy @Value staje się problemem? Gdy mamy do czynienia z grupą powiązanych właściwości. Wyobraź sobie konfigurację klienta do zewnętrznego API: URL, klucz, sekret, timeout i tak dalej. Wstrzykiwanie każdej z tych wartości osobno za pomocą @Value szybko prowadzi do bałaganu i powtórzeń. Co więcej, nie mamy tu wbudowanej walidacji, a kod staje się mniej czytelny. Dlatego pod takie sytuacji stworzony inny sposób wstrzykiwania wartości poprzez użycie adnotacji @ConfigurationProperties.

@ConfigurationProperties w Spring Boot – dla złożonych konfiguracji

@ConfigurationProperties zamiast wstrzykiwać pojedyncze stringi, możemy zmapować całą gałąź konfiguracji na dedykowany, typowany obiekt. To podejście, które zdecydowanie polecam w większości przypadków.

Zobaczmy to na przykładzie konfiguracji serwera pocztowego. Tym razem wykorzystamy plik application.yml i zdefiniujemy w nim właściwości, co ważne, pod wspólnym prefiksem.

uprogramisty:
  mail:
    host: smtp.example.com
    port: 587
    username: admin
    enabled: true

Następnie tworzymy klase, która będzie reprezentować te ustawienia:

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "uprogramisty.mail")
public class MailProperties {
    @NotEmpty
    private String host;

    @Min(1)
    private int port;

    private String username;
    private boolean enabled;

    public MailProperties() {
    }

    // Gettery i Settery
}

I to wszystko. Taki komponent możemy sobie wstrzyknąć w dowolne miejsce i dostajemy informację zawarte w naszym pliku konfiguracyjnym.

@Service
public class NotificationService {

    private final MailProperties mailProperties;

    public NotificationService(MailProperties mailProperties) {
        this.mailProperties = mailProperties;
        // Teraz możemy używać np. mailProperties.host()
    }
}

Warto też tutaj zwrócić uwagę na adnotacje walidacyjne, takie jak @NotEmpty czy @Min. Jeśli dodamy jeszcze @Validated do naszej klasy MailProperties, Spring automatycznie sprawdzi poprawność wartości podczas uruchamiania aplikacji (wymagana dodatkowa zależność w projekcie np. org.hibernate.validator:hibernate-validator). Genialne w swojej prostocie! Przykładowo jak w zmiennej port ustawimy wartość 0 oraz w zmiennej host zostawimy pustą wartość to dostaniemy błąd przy starcie aplikacji:

Wynik błędnej konfiguracji

Na koniec taka mała dygresja. Całą konfigurację z użyciem @ConfigurationProperties zamknęliśmy w jednej klasie ze względu na to, że dodatkowo dołożyliśmy adnotacje @Component. Spring Boot automatycznie wykryje taką klasę z @ConfigurationProperties i ją zarejestruje. Dzięki temu możemy swobodnie jej używać. Podobnie byłoby w sytuacji jakbyśmy ręcznie utworzyli taką klasę jako bean z użyciem adnotacji @Bean. Efekt byłby taki sam.

Natomiast jeśli z jakiegoś powodu nie chcielibyśmy tak robić to istnieją na to inne mechanizmy, aby Spring mógł wykryć tego typu konfiguracje. W takiej sytuacji możemy użyć adnotacji @EnableConfigurationProperties. Wtedy informujemy Springa, że ma taką klasę przetworzyć, dodać ją do swojego kontekstu i wstrzyknąć odpowiednie wartości. Taką adnotację dodajemy w klasie konfiguracyjnej albo też możemy dodać w głównej klasie startującej całą aplikację:

@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class AppConfig {
    // ... inne konfiguracje
}

// Albo alternatywnie możemy dodać to w klasie Application
@SpringBootApplication
@EnableConfigurationProperties(MailProperties.class)
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Użycie klasy Rekord, zamiast zwykłej klasy

Jeśli pracujemy na nowszej wersji Javy to zamiast używania zwykłej klasy do wstrzykiwania danych z pliku konfiguracyjnego można też wykorzystać klasy Rekord do tego. Takie rozwiązanie, przynajmniej według mnie, wydaje się dużo czytelniejsze niż używania standardowego podejścia. W końcu po coś te Rekordy dodawali.

Tak, więc naszą poprzednio utworzoną konfiguracje możemy zapisać teraz w taki sposób:

import org.springframework.boot.context.properties.ConfigurationProperties;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Min;

@ConfigurationProperties(prefix = "uprogramisty.mail")
public record MailPropertiesRecord(
        @NotEmpty String host,
        @Min(1) int port,
        String username,
        boolean enabled
) {
}

Aby nam działała wersja z rekordami to potrzebujemy dodatkową konfiguracja, żeby Springa mógł wykryć tego typu wstrzykiwanie danych. Najprościej jest dodać adnotację @ConfigurationPropertiesScan w klasie konfiguracyjnej albo też można dodać tą adnotację w głównej klasie startującej całą aplikację:

@Configuration
@ConfigurationPropertiesScan
public class AppConfig {
    // ... inne konfiguracje
}

// Albo alternatywnie możemy dodać to w klasie Application
@SpringBootApplication
@ConfigurationPropertiesScan
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Kiedy używać którego rozwiązania?

Kiedy najlepiej używać @Value:

  • Potrzebujesz 1-3 prostych wartości
  • Potrzebujesz tylko kilku, niepowiązanych ze sobą wartości
  • Konfiguracja jest prosta i nie będzie się rozrastać
  • Wartości są używane w pojedynczym komponencie

Natomiast @ConfigurationProperties dobrze użyć, gdy:

  • Masz grupę logicznie powiązanych właściwości
  • Chcesz mieć typ-safe dostęp do konfiguracji
  • Potrzebujesz nested objects
  • Konfiguracja jest używana w wielu miejscach

W praktyce często widzę mieszane podejście – @ConfigurationProperties dla głównych bloków konfiguracyjnych (database, security, external APIs) i @Value dla pojedynczych flag czy prostych wartości.

Wskazówki

Poniżej kilka wskazówek, które mogą się przydać podczas projektowania:

Relaxed binding – Spring Boot automatycznie mapuje różne formaty. Właściwość my-property, myProperty, MY_PROPERTY czy my_property – wszystkie będą zmapowane na pole myProperty w klasie.

Prefix organization – Organizowanie konfiguracji hierarchicznie. Zamiast emailHost, emailPort lepiej jest używać app.email.host, app.email.port. To ułatwi późniejszy refaktoring.

Immutable Configuration – Jak pracujemy z nowszymi wersja Javy (14+) dobrze jest rozważyć użycie record’ów dla @ConfigurationProperties:

@ConfigurationProperties(prefix = "app.email")
public record EmailProperties(
    String from,
    int timeout,
    boolean enabled,
    SmtpProperties smtp
) {
    public record SmtpProperties(String host, int port) {}
}

Environment-specific configs – W naszym projekcie dobrze jest też wykorzystywać profile do wstrzykiwania odpowiednich konfiguracji w zależności od środowiska np. application-dev.properties, application-prod.properties.

application.properties vs application.yml

W konfiguracji Spring Boota możemy używać dwóch typów plików do przygotowywania konfiguracji aplikacji. Niezależnie od wyboru danego typu, efekt końcowy będzie taki sam. Natomiast różnica w nich polega na czytelności.

W przypadku application.properties mamy dość prostą strukturę. Każde zagnieżdżenie konfiguracji oddzielamy kropką:

app.email.from=noreply@firma.pl
app.email.timeout=5000
app.email.smtp.host=smtp.gmail.com
app.email.smtp.port=587

W przypadku application.yml mamy bardziej czytelną strukturę dla złożonych struktur:

app:
  email:
    from: noreply@firma.pl
    timeout: 5000
    smtp:
      host: smtp.gmail.com
      port: 587

Z mojego doświadczenia dla prostych projektów jest dobrze zostać przy .properties. W przypadku typu YAML zyskuje sens, gdy mamy dużo zagnieżdżonych właściwości (nestes properties) lub używamy takich struktur jak list czy map w konfiguracji.

Podsumowanie

Ostatecznie wybór pomiędzy @Value a @ConfigurationProperties sprowadza się do tego, jak bardzo rozbudowaną masz konfigurację. Do prostych wartości @Value sprawdza się idealnie – wrzucasz, działa i nie zawracasz sobie głowy. Ale gdy tych ustawień robi się więcej, lepiej od razu użyć adnotacji @ConfigurationProperties. Masz wtedy wszystko w jednym miejscu, większą czytelność i spokój, że typy się zgadzają.

Warto też nie zapominać o wartościach domyślnych i sensownych nazwach kluczy – serio, to później oszczędza sporo nerwów, zwłaszcza jak konfiguracja rośnie albo ktoś inny musi się w tym odnaleźć.

Mam nadzieję, że ten szybki strzał rozwiał Twoje wątpliwości. Daj znać w komentarzu, jakie konfiguracje sprawiły Ci najwięcej zabawy albo jak Ty podchodzisz do tematu w swoich projektach!

Subskrybuj
Powiadom o
guest
0 komentarzy
Najstarsze
Najnowsze Najwięcej głosów
Opinie w linii
Zobacz wszystkie komentarze