Spring Boot @ConfigurationProperties – Bezpieczne zarządzanie konfiguracją

TL;DR: @ConfigurationProperties to Spring Boot’owy sposób na bezpieczne mapowanie właściwości z application.properties na klasy Java. Zamiast rozrzuconych @Value, dostajesz type-safe konfigurację z validacją i IDE support.

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
Wymagania wstępne: Podstawowa znajomość Spring Boot, tworzenie klas Java, application.properties

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; }
    }
}
Ważne: @ConfigurationProperties wymaga konstruktora domyślnego i setterów. Spring Boot używa Java Bean convention do mapowania właściwości.

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);
    }
}
Pro tip: @EnableConfigurationProperties jest lepszą praktyką gdy chcesz mieć większą kontrolę nad tym, które właściwości są ładowane. @Component jest wygodniejsze dla małych projektów.

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

Uwaga: Jeśli validacja nie przejdzie, aplikacja nie wystartuje. To właśnie chcemy – lepiej złapać błędną konfigurację na początku niż w runtime.

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.

Pułapka: Configuration processor działa podczas kompilacji. Po dodaniu nowych właściwości musisz przebudować projekt żeby zobaczyć autocomplete.

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());
    }
}
Czy mogę używać @ConfigurationProperties z YAML?

Tak! Spring Boot automatycznie rozpoznaje zarówno .properties jak i .yml/.yaml. YAML jest często wygodniejszy dla zagnieżdżonych struktur.

Co jeśli moja właściwość ma inną nazwę niż pole w klasie?

Spring Boot automatycznie konwertuje kebab-case (app.api-timeout), snake_case (app.api_timeout) i camelCase (app.apiTimeout) na pola w klasie Java.

Czy @ConfigurationProperties obsługuje kolekcje?

Tak! Możesz mapować listy, mapy i arrays. Przykład: app.servers[0].name=server1, app.servers[1].name=server2

Kiedy używać @ConfigurationProperties zamiast @Value?

@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.

Czy mogę mieć walidację na poziomie całej klasy?

Tak! Możesz implementować własną walidację używając @AssertTrue na metodzie która sprawdza spójność całej konfiguracji.

Co się stanie jeśli właściwość nie istnieje?

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:

🚀 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!

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry