java 如何在Sping Boot 应用程序中记录活动配置?

ckx4rj1h  于 2023-03-16  发布在  Java
关注(0)|答案(6)|浏览(106)

我真的很想在Sping Boot 中使用YAML config,因为我发现它很有可读性,而且用一个文件来显示我的不同配置文件中哪些属性是活动的。不幸的是,我发现在application.yml中设置属性可能相当脆弱。
像使用制表符而不是空格这样的事情会导致属性不存在(据我所知没有警告),而且我经常发现我的活动配置文件没有被设置,这是由于我的YAML出现了一些未知的问题。
所以我想知道是否有任何钩子可以让我获得当前活动的配置文件和属性,这样我就可以记录它们。
类似地,如果application.yml包含错误,是否有方法导致启动失败?或者是我自己验证YAML的方法,这样我就可以终止启动进程。

slmsl1lt

slmsl1lt1#

除其他答案外:记录上下文刷新事件的活动属性。
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("******************************************************************************")
    }
}

输出如:

************************* 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
...
******************************************************************************
okxuctiv

okxuctiv2#

我也遇到过同样的问题,我希望有一个调试标志,告诉概要文件处理系统输出一些有用的日志记录。一种可能的方法是为您的应用程序上下文注册一个事件侦听器,并从环境中打印概要文件。我自己还没有尝试过这种方法,所以您的里程可能会有所不同。我认为可能类似于下面概述的内容:
How to add a hook to the application context initialization event?
然后你会在你的听众中这样做:

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

也许值得一试。另一种可能的方法是在需要打印配置文件的代码中声明要注入的环境。即:

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

iq0todco3#

Actuator /env服务显示属性,但不显示哪个属性值实际上是活动的。

  • 特定于配置文件的应用程序属性
  • 命令行参数
  • 操作系统环境变量

因此,您将在多个源中具有相同的属性和不同的值。
下面的代码段在启动时打印活动的应用程序属性值:

@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));
        }
    }

}
pcww981p

pcww981p4#

如果application.yml包含错误,它将导致启动失败。我猜这取决于你对“错误”的理解。当然,如果YAML格式不正确,它将失败。同样,如果你设置了标记为ignoreInvalidFields=true@ConfigurationProperties,或者如果你设置了一个无法转换的值,这是一个相当广泛的错误。
活动配置文件可能会在启动时被Environment实现记录(但无论如何,您可以轻松地获取并将其记录在启动器代码中-我认为EnvironmenttoString()将列出活动配置文件)如果添加Actuator,活动配置文件(以及更多)也可以在/env端点中获得。

1bqhqjot

1bqhqjot5#

如果您想在初始化bean/应用程序之前获得活动配置文件,我发现的唯一方法是在SpringBootServletInitializer/SpringApplication中注册一个自定义Banner(即JHipster应用程序中的ApplicationWebXml)。
例如:

@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.");
    }
    ...
}
yh2wf1be

yh2wf1be6#

如果你想在你的spring Boot 应用程序启动期间从application.yml文件中记录一些活动的配置值,你必须像这样使用spring farmenwork中的ApplicationReadyListener类:

public class ApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {

@Value("${key:defaultValue}")
private String keyValue;

@Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
  log.info("Active config value for key is: {}", keyValue);
}

相关问题