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:

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.propertie
s 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!