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
-
RavelConfigest immuable — sûr à partager entre threads -
ScopedValuepour les piles d’expressions — compatible avec les virtual threads (concurrence structurée) -
Pas de blocs
synchronized— évite l’épinglage des virtual threads -
ConcurrentHashMappour 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));
}