web-dev-qa-db-fra.com

Comment consigner la configuration active dans une application Spring Boot?

J'aimerais vraiment utiliser la configuration YAML pour Spring Boot, car je trouve très lisible et utile de ne disposer que d’un seul fichier indiquant les propriétés actives dans mes différents profils. Malheureusement, je trouve que la définition de propriétés dans application.yml peut être assez fragile.

Des choses comme utiliser une tabulation au lieu d'espaces entraîneront la non-existence de propriétés (sans avertissements à ma connaissance), et trop souvent, je constate que mes profils actifs ne sont pas définis, en raison d'un problème inconnu avec mon YAML.

Je me demandais donc s'il existait des points d'ancrage qui me permettraient de récupérer les propriétés et les profils actuellement actifs, afin de pouvoir les consigner.

De même, existe-t-il un moyen de faire échouer le démarrage si le application.yml contient des erreurs? Soit ça, soit un moyen pour moi de valider moi-même le YAML, afin de pouvoir mettre fin au processus de démarrage.

14
Steve

J'ai eu le même problème, et j'aimerais qu'il y ait un drapeau de débogage qui indique au système de traitement de profil de créer une journalisation utile. Une façon possible de le faire serait d’enregistrer un écouteur d’événements pour le contexte de votre application et d’imprimer les profils à partir de l’environnement. Je n'ai pas essayé de le faire moi-même, donc votre kilométrage peut varier. Je pense que peut-être quelque chose comme ce qui est décrit ici:

Comment ajouter un hook à l'événement d'initialisation du contexte d'application?

Ensuite, vous feriez quelque chose comme ça dans votre auditeur:

System.out.println("Active profiles: " + Arrays.toString(ctxt.getEnvironment().getActiveProfiles()));

Peut-être la peine d'essayer. Une autre solution consiste probablement à déclarer l'environnement à injecter dans le code où vous devez imprimer les profils. C'est à dire.:

@Component
public class SomeClass {
  @Autowired
  private Environment env;
  ...
  private void dumpProfiles() {
    // Print whatever needed from env here
  }
}
7
user2337270

Le service Actionneur/env affiche les propriétés, mais ne précise pas quelle valeur de propriété est réellement active. Très souvent, vous voudrez peut-être remplacer les propriétés de votre application par

  • propriétés d'application spécifiques au profil
  • arguments de ligne de commande
  • Variables d'environnement de système d'exploitation

Ainsi, vous aurez la même propriété et des valeurs différentes dans plusieurs sources.

L'extrait ci-dessous imprime les valeurs des propriétés de l'application active au démarrage:

@Configuration
public class PropertiesLogger {
    private static final Logger log = LoggerFactory.getLogger(PropertiesLogger.class);

    @Autowired
    private AbstractEnvironment environment;

    @PostConstruct
    public void printProperties() {

        log.info("**** APPLICATION PROPERTIES SOURCES ****");

        Set<String> properties = new TreeSet<>();
        for (PropertiesPropertySource p : findPropertiesPropertySources()) {
            log.info(p.toString());
            properties.addAll(Arrays.asList(p.getPropertyNames()));
        }

        log.info("**** APPLICATION PROPERTIES VALUES ****");
        print(properties);

    }

    private List<PropertiesPropertySource> findPropertiesPropertySources() {
        List<PropertiesPropertySource> propertiesPropertySources = new LinkedList<>();
        for (PropertySource<?> propertySource : environment.getPropertySources()) {
            if (propertySource instanceof PropertiesPropertySource) {
                propertiesPropertySources.add((PropertiesPropertySource) propertySource);
            }
        }
        return propertiesPropertySources;
    }

    private void print(Set<String> properties) {
        for (String propertyName : properties) {
            log.info("{}={}", propertyName, environment.getProperty(propertyName));
        }
    }

}
7
Krzysztof Ziomek

En plus d’autres réponses: enregistrer les propriétés actives sur un événement actualisé en fonction du contexte.

Java 8

package mypackage;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.stereotype.Component;

import Java.util.ArrayList;
import Java.util.Collection;
import Java.util.List;

@Slf4j
@Component
public class AppContextEventListener {

    @EventListener
    public void handleContextRefreshed(ContextRefreshedEvent event) {
        printActiveProperties((ConfigurableEnvironment) event.getApplicationContext().getEnvironment());
    }

    private void printActiveProperties(ConfigurableEnvironment env) {

        System.out.println("************************* ACTIVE APP PROPERTIES ******************************");

        List<MapPropertySource> propertySources = new ArrayList<>();

        env.getPropertySources().forEach(it -> {
            if (it instanceof MapPropertySource && it.getName().contains("applicationConfig")) {
                propertySources.add((MapPropertySource) it);
            }
        });

        propertySources.stream()
                .map(propertySource -> propertySource.getSource().keySet())
                .flatMap(Collection::stream)
                .distinct()
                .sorted()
                .forEach(key -> {
                    try {
                        System.out.println(key + "=" + env.getProperty(key));
                    } catch (Exception e) {
                        log.warn("{} -> {}", key, e.getMessage());
                    }
                });
        System.out.println("******************************************************************************");
    }
}

Kotlin

package mypackage

import mu.KLogging
import org.springframework.context.event.ContextRefreshedEvent
import org.springframework.context.event.EventListener
import org.springframework.core.env.ConfigurableEnvironment
import org.springframework.core.env.MapPropertySource
import org.springframework.stereotype.Component

@Component
class AppContextEventListener {

    companion object : KLogging()

    @EventListener
    fun handleContextRefreshed(event: ContextRefreshedEvent) {
        printActiveProperties(event.applicationContext.environment as ConfigurableEnvironment)
    }

    fun printActiveProperties(env: ConfigurableEnvironment) {
        println("************************* ACTIVE APP PROPERTIES ******************************")
        env.propertySources
                .filter { it.name.contains("applicationConfig") }
                .map { it as EnumerablePropertySource<*> }
                .map { it -> it.propertyNames.toList() }
                .flatMap { it }
                .distinctBy { it }
                .sortedBy { it }
                .forEach { it ->
                    try {
                        println("$it=${env.getProperty(it)}")
                    } catch (e: Exception) {
                        logger.warn("$it -> ${e.message}")
                    }
                }
        println("******************************************************************************")
    }
}

Sortie comme:

************************* ACTIVE APP PROPERTIES ******************************
server.port=3000
spring.application.name=my-app
...
2017-12-29 13:13:32.843  WARN 36252 --- [           main] m.AppContextEventListener        : spring.boot.admin.client.service-url -> Could not resolve placeholder 'management.address' in value "http://${management.address}:${server.port}"
...
spring.datasource.password=
spring.datasource.url=jdbc:postgresql://localhost/my_db?currentSchema=public
spring.datasource.username=db_user
...
******************************************************************************
6
fightlight

Si application.yml contient des erreurs, cela entraînera un échec au démarrage. Je suppose que cela dépend de ce que vous entendez par "erreur". Cela échouera certainement si le YAML n'est pas bien formé. Également si vous définissez @ConfigurationProperties qui sont marqués par ignoreInvalidFields=true par exemple, ou si vous définissez une valeur qui ne peut pas être convertie. C'est un assez large éventail d'erreurs.

Les profils actifs seront probablement enregistrés au démarrage par l’implémentation Environment (mais dans tous les cas, il est facile pour vous de saisir cela et de le consigner dans votre code de lanceur - la toString() de la Environment liste les profils actifs, je pense.). Les profils actifs (et davantage) sont également disponibles dans le noeud final/env si vous ajoutez l'actionneur.

2
Dave Syer

Si vous souhaitez obtenir les profils actifs avant d’initialiser les beans/l’application, la seule solution que j’ai trouvée est d’enregistrer une bannière personnalisée dans votre SpringBootServletInitializer/SpringApplication (c’est-à-dire ApplicationWebXml dans une application JHipster).

par exemple.

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)
{
    // set a default to use when no profile is configured.
    DefaultProfileUtil.addDefaultProfile(builder.application());
    return builder.sources(MyApp.class).banner(this::printBanner);
}

/** Custom 'banner' to obtain early access to the Spring configuration to validate and debug it. */
private void printBanner(Environment env, Class<?> sourceClass, PrintStream out)
{
    if (env.getProperty("spring.datasource.url") == null)
    {
        throw new RuntimeException(
            "'spring.datasource.url' is not configured! Check your configuration files and the value of 'spring.profiles.active' in your launcher.");
    }
    ...
}
1
etrusco