Au-delà des fichiers de propriétés, Ravel expose toute la surface programmatique de MicroProfile Config : construire un Config à la main, enregistrer des ConfigSource et Converter personnalisés, et piloter la résolution depuis le code applicatif. Pour le modèle de configuration lui-même, voir Concepts.

API ConfigBuilder

Pour la configuration programmatique au-delà des fichiers de propriétés, utilisez ConfigBuilder :

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigBuilder;

ConfigBuilder builder = ConfigProvider.getConfigBuilder();

// Ajouter des sources personnalisées
builder.withSources(new MyCustomConfigSource());

// Ajouter des convertisseurs personnalisés
builder.withConverter(Color.class, new ColorConverter());

// Construire la config
Config config = builder.build();

Convertisseurs personnalisés avec builder

Enregistrez les convertisseurs par instance Config :

ConfigBuilder builder = ConfigProvider.getConfigBuilder();

// Enregistrer un convertisseur JSON
builder.withConverter(MyConfigDto.class, new JsonConverter<>(MyConfigDto.class));

// Enregistrer plusieurs convertisseurs
builder.withConverters(
    new DurationConverter(),
    new ColorConverter(),
    new UriConverter()
);

Config config = builder.build();

// Maintenant cela fonctionne
MyConfigDto dto = config.getValue("app.config", MyConfigDto.class);

Configurations multiples

Vous pouvez maintenir plusieurs instances Config avec des configurations différentes :

// Config de production (de sources standard + base de données)
ConfigBuilder prodBuilder = ConfigProvider.getConfigBuilder();
prodBuilder.withSources(new DatabaseConfigSource());
Config prodConfig = prodBuilder.build();

// Config de développement (de fichiers seulement, pas de base de données)
ConfigBuilder devBuilder = ConfigProvider.getConfigBuilder();
devBuilder.withConverters(new DebugConverter());
Config devConfig = devBuilder.build();

// Les utiliser indépendamment
String prodValue = prodConfig.getValue("key", String.class);
String devValue = devConfig.getValue("key", String.class);

Mise en cache de config

ConfigProvider.getConfig() retourne une instance Config mise en cache. Les appels ultérieurs retournent le même objet :

Config config1 = ConfigProvider.getConfig();
Config config2 = ConfigProvider.getConfig();

assert config1 == config2; // Même instance

Pour créer une config fraîche (utile dans les tests), utilisez directement ConfigBuilder ou réinitialisez via les propriétés système.

Observer les changements

Bien que Ravel ne fournisse pas un mécanisme de rechargement de configuration built-in, vous pouvez implémenter le polling :

@ApplicationScoped
@Startup
public class ConfigWatcher {

    @Inject
    private Event<ConfigUpdated> configUpdated;

    private long lastCheck = 0;
    private String lastValue = null;

    @Schedule(every = "60s")
    void checkForChanges() {
        Config config = ConfigProvider.getConfig();
        String currentValue = config.getValue("monitored.key", String.class);

        if (!currentValue.equals(lastValue)) {
            lastValue = currentValue;
            configUpdated.fire(new ConfigUpdated(lastValue));
        }
    }
}

Profondeur de récursion d’expression

Ravel détecte les cycles dans les expressions de propriété, mais ne limite pas artificiellement la profondeur de récursion. Les chaînes profondes (mais acycliques) sont résolues correctement :

a=${b}
b=${c}
c=${d}
d=${e}
e=${f}
f=final
String value = config.getValue("a", String.class);
// Résultat : "final" (tous résolus récursivement)

Sécurité des threads et virtual threads

  • RavelConfig est immuable — sûr à partager entre threads

  • ScopedValue pour les piles d’expressions — compatible avec les virtual threads (concurrence structurée)

  • Pas de blocs synchronized — évite l’épinglage des virtual threads

  • ConcurrentHashMap pour les caches — accès concurrent sécurisé

Votre application peut utiliser Ravel en toute sécurité avec une concurrence élevée et des virtual threads.

Tuning des performances

Minimiser les lookups dans les chemins critiques

Mettez en cache les valeurs fréquemment accédées :

@ApplicationScoped
public class PerformantConfig {

    private final String dbHost;
    private final int dbPort;

    @PostConstruct
    void init() {
        Config config = ConfigProvider.getConfig();
        this.dbHost = config.getValue("db.host", String.class);
        this.dbPort = config.getValue("db.port", Integer.class);
    }
}

Utiliser les convertisseurs implicites avec soin

Les convertisseurs implicites utilisent la réflexion sur les constructeurs/méthodes publics. Préférez les convertisseurs built-in pour les chemins critiques :

// Bon : convertisseur built-in
Duration timeout = config.getValue("timeout", Duration.class);

// Moins efficace : convertisseur implicite (réflexion)
MyDuration timeout = config.getValue("timeout", MyDuration.class);

Pré-compiler les convertisseurs

Pour les convertisseurs personnalisés fréquemment utilisés, mettez en cache l’instance du convertisseur :

public class OptimizedConverterRegistry {

    private static final Map<Class<?>, Converter<?>> CACHE = new ConcurrentHashMap<>();

    static {
        CACHE.put(Color.class, new ColorConverter());
        CACHE.put(MyDuration.class, new MyDurationConverter());
    }
}

Test

Utilisez des sources de configuration en mémoire pour les tests :

@Test
void testConfigProcessing() {
    Map<String, String> properties = new HashMap<>();
    properties.put("app.name", "TestApp");
    properties.put("app.port", "9999");

    ConfigBuilder builder = ConfigProvider.getConfigBuilder();
    builder.withSources(new MapConfigSource(properties));
    Config config = builder.build();

    assertEquals("TestApp", config.getValue("app.name", String.class));
    assertEquals(9999, config.getValue("app.port", Integer.class));
}

Suivant

  • TCK — test de conformance

  • Référence — documentation de l’API