Spring Boot - Detect and terminate if property not

2020-07-10 02:44发布

Is there any way for a Spring Boot web application to abort at startup if a required property is not set anywhere (neither in the application.properties file nor the other property sources)? Right now, if the property is included in another property, it seem that Spring Boot simply avoids substitution.

For example, in my application.properties file, I have the line:

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

Right now, if "my.home" is not set elsewhere, Spring Boot is setting the url literally to "jdbc:hsqldb:${my.home}/database/my-jobstore" (no substitution).

I would like to have the application fail to start if the property my.home were not set anywhere else.

4条回答
小情绪 Triste *
2楼-- · 2020-07-10 03:22

Although they work, I think the approach in the foremost answer is somewhat brittle, as it only works for the predefined name(s), and will silently stop checking the when someone changes quartz.datasource.url in the configs to use a different expansion.

Ideally, I want this value of ignoreUnresolvablePlaceholders to be false to get wholesale expansion checking when parsing my configs such as application.properties or its YAML variants, but it's hard-coded to true for these cases. This unfortunately leaves strings such as ${FOO} in its unexpanded form if FOO cannot be found, making troubleshooting extremely painful. This is especially the case for fields that don't readily appear in the logs such as passwords.

While I couldn't find a way of changing ignoreUnresolvablePlaceholders short of modifying Spring Boot's classes, I did find an alternative of using a custom PropertySource implementation and defining a new syntax such as "${!FOO}" to indicate FOO must exist as an environment variable or die. (The OP didn't mention whether my.home is an environment variable but the code below is for environment variables.)

First, an EnvironmentPostProcessor implementation is required for registering the custom PropertySource. This StrictSystemEnvironmentProcessor.java does this as well as holds the implementation of the custom 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;
        }
    }

}

Instead of returning null, an exception is thrown for names that start with !.

This META-INF/spring.factories is also required so that Spring initializes our EnvironmentPostProcessor:

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

Then henceforth, I can write all environment variables substitutions in my configs as ${!FOO} to get strict existance checking.

查看更多
成全新的幸福
3楼-- · 2020-07-10 03:32

The default behaviour in current versions of Spring Boot (1.5.x, 2.0.x, 2.1.x) is to throw an exception if a placeholder can not be resolved.

There will a be an exception like this one :

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'app.foo.undefined' in value "${app.foo.undefined}"

It works because a bean of type PropertySourcesPlaceholderConfigurer (from spring-context) is automatically registered in Spring Boot, in this class : PropertyPlaceholderAutoConfiguration. And by default, the property ignoreUnresolvablePlaceholders in PropertySourcesPlaceholderConfigurer is set to false, which means an exception must be thrown if a placeholder is unresolved (be it nested or not).

查看更多
ゆ 、 Hurt°
4楼-- · 2020-07-10 03:41

Create a bean with a simple @Value(${my.home}) annotated field. - Then Spring will try to inject that value and will fail and therefore stop when the value is not there.


Just @Value(${my.home}) private String myHomeValue; is enough for normal (not Boot) Spring applications for sure! But I do not know whether Boot has some other configuration to handle missing values: If there is an other failure management than you could check that value in an PostCreation method.

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

}
查看更多
我想做一个坏孩纸
5楼-- · 2020-07-10 03:47

To throw a friendly exceptions just put a default null value in property, check and throw a exception in afterProperty method.

@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");
    }
 }
}
查看更多
登录 后发表回答