Konfigurowałeś kiedyś aplikację Spring Boot używając dziesiątek @Value adnotacji rozrzuconych po całym kodzie? Po miesiącu przestajesz pamiętać gdzie co jest, a każda zmiana nazwy property to godziny refaktoringu. @ConfigurationProperties rozwiązuje ten problem raz na zawsze.
Dlaczego @ConfigurationProperties to game changer
Zamiast tego bałaganu:
@Value("${app.database.url}") private String databaseUrl; @Value("${app.database.username}") private String databaseUsername; @Value("${app.api.timeout:5000}") private int apiTimeout;
Masz czystą, type-safe klasę konfiguracyjną:
@ConfigurationProperties(prefix = "app") public class AppProperties { private Database database = new Database(); private Api api = new Api(); // getters, setters... }
Co się nauczysz:
- Jak utworzyć klasę konfiguracyjną z @ConfigurationProperties
- Mapowanie zagnieżdżonych właściwości na obiekty Java
- Validacja konfiguracji przy starcie aplikacji
- Integration z IDE dla lepszego developer experience
- Best practices dla organizacji konfiguracji w większych projektach
Podstawowa konfiguracja @ConfigurationProperties
Zacznijmy od prostego przykładu. Masz takie właściwości w application.properties:
# application.properties app.name=MyAwesomeApp app.version=1.0.0 app.database.url=jdbc:postgresql://localhost:5432/mydb app.database.username=admin app.database.password=secret123 app.api.timeout=5000 app.api.retries=3
Teraz utworzysz klasę konfiguracyjną:
package com.example.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "app") public class AppProperties { private String name; private String version; private Database database = new Database(); private Api api = new Api(); // Konstruktor domyślny wymagany public AppProperties() {} // Getters i setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public Database getDatabase() { return database; } public void setDatabase(Database database) { this.database = database; } public Api getApi() { return api; } public void setApi(Api api) { this.api = api; } // Klasy zagnieżdżone public static class Database { private String url; private String username; private String password; // getters i setters... public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } public static class Api { private int timeout = 3000; // domyślna wartość private int retries = 1; public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } public int getRetries() { return retries; } public void setRetries(int retries) { this.retries = retries; } } }
Używanie konfiguracji w aplikacji
Teraz możesz wstrzyknąć swoją konfigurację w dowolnym miejscu:
@Service public class DatabaseService { private final AppProperties appProperties; public DatabaseService(AppProperties appProperties) { this.appProperties = appProperties; } public void connect() { String url = appProperties.getDatabase().getUrl(); String username = appProperties.getDatabase().getUsername(); System.out.println("Connecting to: " + url + " as " + username); // logika połączenia... } }
Alternatywny sposób rejestracji
Zamiast @Component możesz użyć @EnableConfigurationProperties:
// Usuń @Component z AppProperties @ConfigurationProperties(prefix = "app") public class AppProperties { // same as before... } // W głównej klasie aplikacji lub konfiguracji @SpringBootApplication @EnableConfigurationProperties(AppProperties.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Validacja konfiguracji
Jedną z największych zalet @ConfigurationProperties jest możliwość validacji konfiguracji przy starcie aplikacji:
import javax.validation.constraints.*; import org.springframework.validation.annotation.Validated; @Component @ConfigurationProperties(prefix = "app") @Validated public class AppProperties { @NotBlank(message = "Application name cannot be empty") private String name; @Pattern(regexp = "\\d+\\.\\d+\\.\\d+", message = "Version must be in format x.y.z") private String version; @Valid private Database database = new Database(); @Valid private Api api = new Api(); // getters, setters... public static class Database { @NotBlank(message = "Database URL is required") private String url; @NotBlank(message = "Database username is required") private String username; @Size(min = 8, message = "Password must be at least 8 characters") private String password; // getters, setters... } public static class Api { @Min(value = 1000, message = "Timeout must be at least 1000ms") @Max(value = 30000, message = "Timeout cannot exceed 30 seconds") private int timeout = 3000; @Min(value = 0, message = "Retries cannot be negative") @Max(value = 10, message = "Too many retries") private int retries = 1; // getters, setters... } }
Dodaj dependency do validation w pom.xml:
org.springframework.boot spring-boot-starter-validation
Profile-specific properties
@ConfigurationProperties świetnie współpracuje z profilami Spring:
# application.properties (default) app.name=MyApp app.database.url=jdbc:h2:mem:testdb # application-dev.properties app.database.url=jdbc:postgresql://localhost:5432/myapp_dev app.database.username=dev_user # application-prod.properties app.database.url=jdbc:postgresql://prod-server:5432/myapp_prod app.database.username=prod_user app.api.timeout=10000
Spring Boot automatycznie załaduje odpowiednie właściwości w zależności od aktywnego profilu.
IDE Support i Configuration Metadata
Aby mieć autocomplete i hints w IDE, dodaj annotation processor:
org.springframework.boot spring-boot-configuration-processor true
To wygeneruje META-INF/spring-configuration-metadata.json który IDE używa do podpowiedzi.
Testowanie konfiguracji
Testowanie @ConfigurationProperties jest proste:
@SpringBootTest @TestPropertySource(properties = { "app.name=TestApp", "app.database.url=jdbc:h2:mem:test", "app.api.timeout=2000" }) class AppPropertiesTest { @Autowired private AppProperties appProperties; @Test void shouldLoadPropertiesCorrectly() { assertEquals("TestApp", appProperties.getName()); assertEquals("jdbc:h2:mem:test", appProperties.getDatabase().getUrl()); assertEquals(2000, appProperties.getApi().getTimeout()); } @Test void shouldUseDefaultValues() { // api.retries nie było ustawione, powinno być 1 (domyślnie) assertEquals(1, appProperties.getApi().getRetries()); } }
Tak! Spring Boot automatycznie rozpoznaje zarówno .properties jak i .yml/.yaml. YAML jest często wygodniejszy dla zagnieżdżonych struktur.
Spring Boot automatycznie konwertuje kebab-case (app.api-timeout), snake_case (app.api_timeout) i camelCase (app.apiTimeout) na pola w klasie Java.
Tak! Możesz mapować listy, mapy i arrays. Przykład: app.servers[0].name=server1, app.servers[1].name=server2
@ConfigurationProperties dla strukturalnej konfiguracji (więcej niż 2-3 powiązane właściwości), @Value dla pojedynczych wartości używanych w jednym miejscu.
Tak! Możesz implementować własną walidację używając @AssertTrue na metodzie która sprawdza spójność całej konfiguracji.
Spring Boot użyje wartości domyślnej (null dla obiektów, 0 dla int, false dla boolean). Dlatego ważne jest ustawianie domyślnych wartości w klasie.
Przydatne zasoby:
- Spring Boot Documentation – Configuration Properties
- Configuration Metadata Guide
- Spring Boot Sample Projects
🚀 Zadanie dla Ciebie
Stwórz klasę konfiguracyjną dla aplikacji e-commerce z sekcjami: payment (provider, timeout, currency), shipping (defaultProvider, freeShippingThreshold), oraz notification (email, sms). Dodaj odpowiednią walidację i przetestuj z różnymi profilami.
Które właściwości Twojej aplikacji nadają się do refaktoringu na @ConfigurationProperties? Podziel się w komentarzach!