Я хочу реализовать следующий вариант использования: мое приложение Spring Boot должно запускаться, только если установлено определенное свойство в application.yaml:
myapp:
active: true
Если свойство не установлено, инициализация контекста должна завершиться ошибкой с сообщением об отсутствии свойства.
Я нашел в этой теме, как этого добиться: Spring Boot - определить и завершить, если свойство не установлено?, но проблема, почему я не могу следовать этому подходу, заключается в том, что инициализация контекста может завершиться ошибкой до загрузки bean-компонента, который проверяет это свойство.
Например, если какой-либо другой компонент не может загрузиться из-за отсутствия другого свойства, инициализация контекста в этот момент завершится ошибкой, и мой компонент, который проверяет нужное свойство, не будет загружен. Это не нормально для меня, потому что я хочу, чтобы сначала проверялось свойство myapp.active, прежде чем будут загружены любые другие компоненты.
Причина, по которой я хочу, чтобы это было так, заключается в том, что при запуске приложения должен быть установлен определенный профиль — application-[profile].yaml содержит как myapp.active: true, так и некоторые другие обязательные свойства, необходимые для загрузки контекста.
Я хочу, чтобы мое приложение всегда терпело неудачу из-за того, что myapp.active не соответствует действительности, чтобы я мог вывести значимое сообщение о том, что профиль отсутствует и что приложение должно запускаться с одним из профилей (из данного списка профилей). Некоторые ребята, которые не являются разработчиками, запускают приложение, поэтому я хочу, чтобы они знали, почему приложение не запустилось, иначе они решат, что в приложении есть какая-то ошибка.
Как я могу этого добиться? Можно ли как-то прочитать свойство перед загрузкой bean-компонентов? Я бы не хотел устанавливать @DependsOn для всех bean-компонентов (или делать то же самое через BeanPostProcesser) и ищу более элегантное решение.
Самбит да, но этого мало. Это завершится ошибкой только в том случае, если bean-компонент, содержащий аннотацию @Value, окажется первым (или единственным) bean-компонентом, который выйдет из строя во время инициализации.
Тогда остается только один вариант — пользовательская логика создания экземпляров bean-компонентов с использованием PostProcessor в Spring. Вы можете использовать постпроцессор Spring Bean.




Приложение не запустится, если вы используете условие для свойства. Достаточно быстро?
@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);
}
}
Я уже знаю этот подход, но он не удовлетворяет мои потребности, так как я хочу вывести данное сообщение в этом случае, как я описал в начале своего вопроса: «Если свойство не установлено, инициализация контекста должна завершиться ошибкой с некоторыми выдано сообщение о том, что свойство отсутствует».
что, если контекст не загружается по какой-либо другой причине, например, если отсутствует какая-либо зависимость bean-компонента? Все еще возможно, что другая ошибка произойдет первой. Я хочу, чтобы приложение всегда терпело неудачу при запуске из-за того, что myapp.active было ложным (или отсутствовало), я хочу, чтобы оно имело более высокий приоритет, чем что-либо плохое, что может произойти во время запуска.
Я обновил вопрос, чтобы предоставить больше контекста :-)
Загрузка свойства непосредственно из файла application.yaml для меня не вариант, я не могу жестко указать путь, потому что его также можно установить в одном из файлов yaml для конкретного профиля (application-<PROFILE>.yaml), как я писал. .... непредсказуемо, в каком именно файле app.yaml он будет установлен... если он установлен в true в application.yaml или любом профильном приложении-<что-то>.yaml, то приложение должно быть запущено, иначе нет, это единственное условие...
Одним из вариантов может быть создание 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");
}
}
}
Если вы уже предоставили аннотацию @value в поле, а поле отсутствует в свойстве, загрузка Spring автоматически завершается.