【问题标题】:Spring Boot - Detect and terminate if property not set?Spring Boot - 如果未设置属性,则检测并终止?
【发布时间】:2015-10-10 07:06:03
【问题描述】:

如果没有在任何地方设置所需的属性(在 application.properties 文件或其他属性源中都没有),Spring Boot Web 应用程序是否有任何方法在启动时中止?现在,如果该属性包含在另一个属性中,Spring Boot 似乎只是避免了替换。

例如,在我的 application.properties 文件中,我有一行:

quartz.datasource.url=jdbc:hsqldb:${my.home}/database/my-jobstore

现在,如果“my.home”没有在其他地方设置,Spring Boot 会将 url 按字面意思设置为 "jdbc:hsqldb:${my.home}/database/my-jobstore" (没有替代)。

如果属性my.home 未在其他任何位置设置,我希望应用程序无法启动。

【问题讨论】:

    标签: spring spring-boot


    【解决方案1】:

    使用简单的 @Value(${my.home}) 注释字段创建一个 bean。 - 然后 Spring 将尝试注入该值并且会失败并因此在该值不存在时停止。


    只需@Value(${my.home}) private String myHomeValue; 就足以满足正常(非引导)的 Spring 应用程序了!但我确实不知道 Boot 是否有其他配置来处理缺失值:如果有其他故障管理,您可以在 PostCreation 方法中检查该值。

    @Component
    public static class ConfigurationGuard implements InitializingBean {
    
       @Value(${my.home})
       private String myHomeValue;
    
       /**
        * ONLY needed if there is some crude default handling for missing values!!!!
        *
        * So try it first without this method (and without implements InitializingBean)
        */
       public void afterPropertiesSet() {
          if (this.myHomeValue == null or this.myHomeValue.equals("${my.home}") {
              throw new IllegalArgumentException("${my.home} must be configured");
          }
       }
    
    }
    

    【讨论】:

    • 遗憾的是,这并没有按预期工作。相反,它会在尝试解析 @Value 注释中的 ${my.home} 时抛出 BeanCreationException (顺便说一下,它需要用引号括起来:@Value("${my.home}")。所以,当我遇到异常时,我无法控制异常(例如,我无法显示自定义消息)。我也无法立即关闭整个网络应用程序。
    • @David H:但是如果属性不存在,这不是BeanCreationException 你想要的吗"...Spring Boot Web 应用程序在启动时中止的方式,如果所需属性未在任何地方设置..." ? (那么你就不需要afterPropertiesSet 方法)
    • `@Ralph -- 是的,这是真的;我希望我能以某种方式终止程序,并用一个很好的错误消息(“你忘记设置变量!”)而不是丑陋的堆栈跟踪。但既然你提到了它,我想我可以在 Application.run() 上放一个 try/catch。
    • 堆栈跟踪非常适合诊断问题。它们会准确地告诉您发生故障的位置,而不是可能具有欺骗性且无法确定故障的错误消息。
    【解决方案2】:

    要抛出一个友好的异常,只需在属性中放置一个默认的空值,检查并在 afterProperty 方法中抛出一个异常。

    @Component
    public static class ConfigurationGuard implements InitializingBean {
    
    @Value("${my.home:#{null}}")
    private String myHomeValue;
    
    public void afterPropertiesSet() {
        if (this.myHomeValue == null or this.myHomeValue.equals("${my.home}") {
              throw new IllegalArgumentException("${my.home} must be configured");
        }
     }
    }
    

    【讨论】:

    • 恕我直言,这必须是公认的答案,因为它满足了 OP 的要求:检查配置属性,如果没有,则抛出带有友好消息的初始化时间错误(异常)。在我的大多数企业应用程序中,我都遵循完全相同的方法,因为它允许优雅地处理不完整的配置。这里的一个额外提示是在您的 config-check 类上添加 @Configuration 注释,以便在任何 bean 之前加载它。
    【解决方案3】:

    当前版本的 Spring Boot(1.5.x、2.0.x、2.1.x)中的默认行为是在无法解析占位符时抛出异常。

    会有一个像这样的例外:

    原因:java.lang.IllegalArgumentException:无法解析值“${app.foo.undefined}”中的占位符“app.foo.undefined”

    之所以有效,是因为 PropertySourcesPlaceholderConfigurer 类型的 bean(来自 spring-context)自动在 Spring Boot 中注册,在此类中:PropertyPlaceholderAutoConfiguration。默认情况下,PropertySourcesPlaceholderConfigurer 中的属性ignoreUnresolvablePlaceholders 设置为 false,这意味着如果占位符未解析(无论是否嵌套),则必须引发异常。

    【讨论】:

    • 奇怪。这似乎与我的经验不符。如果变量没有点,它会起作用吗?喜欢${FOO}。
    【解决方案4】:

    虽然它们有效,但我认为最重要的答案中的方法有点脆弱,因为它只适用于预定义的名称,并且会默默地停止检查当有人在配置中更改 quartz.datasource.url 以使用不同的展开。

    理想情况下,我希望 this value of ignoreUnresolvablePlaceholders 成为 false 以便在解析我的配置(例如 application.properties 或其 YAML 变体)时进行批量扩展检查,但 it's hard-coded to true 用于这些情况。不幸的是,如果找不到FOO,这会使诸如${FOO} 之类的字符串处于未扩展的形式,从而使故障排除变得非常痛苦。对于密码等不容易出现在日志中的字段尤其如此。

    虽然除了修改 Spring Boot 的类之​​外我找不到更改 ignoreUnresolvablePlaceholders 的方法,但我确实找到了使用自定义 PropertySource 实现并定义新语法(例如“${!FOO}”)的替代方法来指示FOO 必须作为环境变量存在,否则就会死掉。 (OP没有提到my.home是否是环境变量,但下面的代码是针对环境变量的。)

    首先,注册自定义PropertySource 需要一个EnvironmentPostProcessor 实现。这个StrictSystemEnvironmentProcessor.java 做到了这一点,并拥有自定义PropertySource 的实现:

    package some.package;
    
    @Order(Ordered.LOWEST_PRECEDENCE)
    class StrictSystemEnvironmentProcessor implements EnvironmentPostProcessor {
    
        private static final String PROPERTY_SOURCE_NAME = "STRICT_" + StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
    
        @Override
        public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
            if (environment.getPropertySources().contains(PROPERTY_SOURCE_NAME)) {
                return;
            }
    
            SystemEnvironmentPropertySource delegate = (SystemEnvironmentPropertySource)environment.getPropertySources()
                    .get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
    
            environment.getPropertySources().addLast(new StrictSystemEnvironmentPropertySource(delegate));
        }
    
        private static class StrictSystemEnvironmentPropertySource extends SystemEnvironmentPropertySource {
    
            public StrictSystemEnvironmentPropertySource(SystemEnvironmentPropertySource delegate) {
                super(PROPERTY_SOURCE_NAME, delegate.getSource());
            }
    
            @Override
            public Object getProperty(String name) {
                if (name.startsWith("!")) {
                    String variableName = name.substring(1);
                    Object property = super.getProperty(variableName);
                    if (property != null) {
                        return property;
                    }
    
                    throw new IllegalStateException("Environment variable '" + variableName + "' is not set");
                }
                return null;
            }
        }
    
    }
    

    不是返回null,而是为以! 开头的名称引发异常。

    这个META-INF/spring.factories也是必需的,以便Spring初始化我们的EnvironmentPostProcessor:

    org.springframework.boot.env.EnvironmentPostProcessor=some.package.StrictSystemEnvironmentProcessor
    

    然后,我可以在我的配置中将所有环境变量替换写为${!FOO} 以进行严格的存在性检查。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-31
      • 2020-11-03
      • 1970-01-01
      • 2017-01-08
      • 2020-08-21
      • 2021-03-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多