Как условно завершить приложение Spring Boot при запуске на основе свойства

Я хочу реализовать следующий вариант использования: мое приложение Spring Boot должно запускаться, только если установлено определенное свойство в application.yaml:

myapp:
  active: true

Если свойство не установлено, инициализация контекста должна завершиться ошибкой с сообщением об отсутствии свойства.

Я нашел в этой теме, как этого добиться: Spring Boot - определить и завершить, если свойство не установлено?, но проблема, почему я не могу следовать этому подходу, заключается в том, что инициализация контекста может завершиться ошибкой до загрузки bean-компонента, который проверяет это свойство.

Например, если какой-либо другой компонент не может загрузиться из-за отсутствия другого свойства, инициализация контекста в этот момент завершится ошибкой, и мой компонент, который проверяет нужное свойство, не будет загружен. Это не нормально для меня, потому что я хочу, чтобы сначала проверялось свойство myapp.active, прежде чем будут загружены любые другие компоненты.

Причина, по которой я хочу, чтобы это было так, заключается в том, что при запуске приложения должен быть установлен определенный профиль — application-[profile].yaml содержит как myapp.active: true, так и некоторые другие обязательные свойства, необходимые для загрузки контекста.

Я хочу, чтобы мое приложение всегда терпело неудачу из-за того, что myapp.active не соответствует действительности, чтобы я мог вывести значимое сообщение о том, что профиль отсутствует и что приложение должно запускаться с одним из профилей (из данного списка профилей). Некоторые ребята, которые не являются разработчиками, запускают приложение, поэтому я хочу, чтобы они знали, почему приложение не запустилось, иначе они решат, что в приложении есть какая-то ошибка.

Как я могу этого добиться? Можно ли как-то прочитать свойство перед загрузкой bean-компонентов? Я бы не хотел устанавливать @DependsOn для всех bean-компонентов (или делать то же самое через BeanPostProcesser) и ищу более элегантное решение.

Если вы уже предоставили аннотацию @value в поле, а поле отсутствует в свойстве, загрузка Spring автоматически завершается.

Sambit 15.05.2019 17:03

Самбит да, но этого мало. Это завершится ошибкой только в том случае, если bean-компонент, содержащий аннотацию @Value, окажется первым (или единственным) bean-компонентом, который выйдет из строя во время инициализации.

Marko Previsic 15.05.2019 17:07

Тогда остается только один вариант — пользовательская логика создания экземпляров bean-компонентов с использованием PostProcessor в Spring. Вы можете использовать постпроцессор Spring Bean.

Sambit 15.05.2019 17:11
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
6
3
4 177
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Приложение не запустится, если вы используете условие для свойства. Достаточно быстро?

 @SpringBootApplication
 @ConditionalOnProperty(name = "myapp.active")
 public class FastFailWhenPropertyNotPresentApplication {

     public static void main(String[] args) {
            SpringApplication.run(FastFailWhenPropertyNotPresentApplication.class, args);
    }

}

По сути @SpringBootApplication — это просто @Configuration класс.

У вас есть опция matchIfMissing, которую вы можете использовать, чтобы указать, должно ли условие соответствовать, если свойство не установлено. По умолчанию ложно.

Обновлено:

Лучшее решение — настроить свойство с помощью @ConfigurationProperties в сочетании с @Validated, чтобы вы могли использовать аннотации javax.validation.constraints.

package stackoverflow.demo;

import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

@Component
@ConfigurationProperties(prefix = "myapp")
@Validated
public class MyAppProperties {

  @AssertTrue
  @NotNull
  private Boolean active;

  public Boolean getActive() {
    return active;
  }

  public void setActive(Boolean active) {
    this.active = active;
  }

}

примечание: вы можете пропустить @ConditionalOnProperty(name = "myapp.active")

используйте @AssertTrue в сочетании с @NotNull, потому что @AssertTrue считает нулевые элементы допустимыми.

и spring-boot бесплатно генерирует красивое сообщение об ошибке:

***************************
APPLICATION FAILED TO START
***************************

Description:

Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'myapp' to stackoverflow.demo.MyAppProperties failed:

    Property: myapp.active
    Value: false
    Origin: class path resource [application.properties]:1:16
    Reason: must be true


Action:

Update your application's configuration

РЕДАКТИРОВАТЬ (после обновленного вопроса)


Более быстрый способ: ваше приложение не запустится, и контекст приложения не будет загружен

package stackoverflow.demo;

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;

@SpringBootApplication
public class FastFailWhenPropertyNotPresentApplication {

  static Boolean active;

  static {

    YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
    yaml.setResources(new ClassPathResource("application.yaml"));

    active = (Boolean) yaml.getObject().getOrDefault("myapp.active", false);

  }


    public static void main(String[] args) {
        if (!active) {
          System.err.println("your fail message");
        } else {
          SpringApplication.run(FastFailWhenPropertyNotPresentApplication.class, args);
        }
    }

}

РЕДАКТИРОВАТЬ

другое решение, которое, вероятно, лучше всего соответствует вашим потребностям...

Слушая ApplicationEnvironmentPreparedEvent

Event published when a {@link SpringApplication} is starting up and the * {@link Environment} is first available for inspection and modification. *

примечание: вы не можете использовать @EventListener, но вы добавили Listener в SpringApplication

package stackoverflow.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;

@SpringBootApplication
public class FastFailWhenPropertyNotPresentApplication {


  static class EnvironmentPrepared implements ApplicationListener<ApplicationEnvironmentPreparedEvent>{
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
      Boolean active = event.getEnvironment().getProperty("myapp.active",Boolean.class,Boolean.FALSE);
      if (!active) {
        throw new RuntimeException("APPLICATION FAILED TO START: ACTIVE SHOULD BE TRUE ");
      }
    }
  };


  public static void main(String[] args) throws Exception {
    SpringApplication springApplication = new SpringApplication(FastFailWhenPropertyNotPresentApplication.class);
    springApplication.addListeners(new FastFailWhenPropertyNotPresentApplication.EnvironmentPrepared());
    springApplication.run(args);
  }

}

Я уже знаю этот подход, но он не удовлетворяет мои потребности, так как я хочу вывести данное сообщение в этом случае, как я описал в начале своего вопроса: «Если свойство не установлено, инициализация контекста должна завершиться ошибкой с некоторыми выдано сообщение о том, что свойство отсутствует».

Marko Previsic 16.05.2019 08:44

что, если контекст не загружается по какой-либо другой причине, например, если отсутствует какая-либо зависимость bean-компонента? Все еще возможно, что другая ошибка произойдет первой. Я хочу, чтобы приложение всегда терпело неудачу при запуске из-за того, что myapp.active было ложным (или отсутствовало), я хочу, чтобы оно имело более высокий приоритет, чем что-либо плохое, что может произойти во время запуска.

Marko Previsic 17.05.2019 08:15

Я обновил вопрос, чтобы предоставить больше контекста :-)

Marko Previsic 17.05.2019 08:28

Загрузка свойства непосредственно из файла application.yaml для меня не вариант, я не могу жестко указать путь, потому что его также можно установить в одном из файлов yaml для конкретного профиля (application-<PROFILE>.yaml), как я писал. .... непредсказуемо, в каком именно файле app.yaml он будет установлен... если он установлен в true в application.yaml или любом профильном приложении-<что-то>.yaml, то приложение должно быть запущено, иначе нет, это единственное условие...

Marko Previsic 18.05.2019 21:07

Одним из вариантов может быть создание bean-компонента, который проверяет наличие свойства и выдает исключение во время создания bean-компонента, если свойство не установлено.

@Component
public static EnsureApplicationActive {
    @Value("${myapp.active}")
    private Boolean active;

    @PostConstruct
    public void ensureApplicationActive() {
        if (!Boolean.TRUE.equals(active)) {
            throw new IllegalStateException("TODO: Meaningful message goes here");
        }
    }
}

Другие вопросы по теме