Неизменяемые @ConfigurationProperties
Возможно ли иметь неизменяемые (окончательные) поля с аннотацией Spring Boot @ConfigurationProperties
? Пример ниже
@ConfigurationProperties(prefix = "example")
public final class MyProps {
private final String neededProperty;
public MyProps(String neededProperty) {
this.neededProperty = neededProperty;
}
public String getNeededProperty() { .. }
}
Подходы, которые я пробовал до сих пор:
- Создание
@Bean
класса MyProps
с двумя конструкторами
- Предоставление двух конструкторов: пустое и с аргументом
neededProperty
- bean создается с помощью
new MyProps()
- Результаты в поле
null
- Используя
@ComponentScan
и @Component
, чтобы предоставить MyProps
bean.
- Результаты в
BeanInstantiationException
→ NoSuchMethodException: MyProps.<init>()
Единственный способ, с помощью которого я работаю, - это предоставить getter/setter для каждого не конечного поля.
Ответы
Ответ 1
Мне нужно решить эту проблему очень часто, и я использую немного другой подход, который позволяет мне использовать переменные final
в классе.
Прежде всего, я сохраняю всю свою конфигурацию в одном месте (классе), например, называется ApplicationProperties
. Этот класс имеет аннотацию @ConfigurationProperties
с определенным префиксом. Он также указан в аннотации @EnableConfigurationProperties
к классу конфигурации (или основному классу).
Затем я предоставляю свой ApplicationProperties
как аргумент конструктора и выполняю назначение в поле final
внутри конструктора.
Пример:
Основной класс:
@SpringBootApplication
@EnableConfigurationProperties(ApplicationProperties.class)
public class Application {
public static void main(String... args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
ApplicationProperties
класс
@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
private String someProperty;
// ... other properties and getters
public String getSomeProperty() {
return someProperty;
}
}
И класс с конечными свойствами
@Service
public class SomeImplementation implements SomeInterface {
private final String someProperty;
@Autowired
public SomeImplementation(ApplicationProperties properties) {
this.someProperty = properties.getSomeProperty();
}
// ... other methods / properties
}
Я предпочитаю этот подход по многим причинам, например. если мне нужно настроить больше свойств в конструкторе, мой список аргументов конструктора не является "огромным", поскольку у меня всегда есть один аргумент (ApplicationProperties
в моем случае); если есть необходимость добавлять дополнительные свойства final
, мой конструктор остается неизменным (только один аргумент) - это может уменьшить количество изменений в другом месте и т.д.
Я надеюсь, что это поможет
Ответ 2
В конце концов, если вы хотите неизменный объект, вы также можете "взломать" установщик, который
@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
private String someProperty;
// ... other properties and getters
public String getSomeProperty() {
return someProperty;
}
public String setSomeProperty(String someProperty) {
if (someProperty == null) {
this.someProperty = someProperty;
}
}
}
Очевидно, что если свойство является не просто строкой, то есть изменяемым объектом, все будет сложнее, но это уже другая история.
Еще лучше, вы можете создать контейнер конфигурации
@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
private final List<MyConfiguration> configurations = new ArrayList<>();
public List<MyConfiguration> getConfigurations() {
return configurations
}
}
где сейчас конфигурация класс без
public class MyConfiguration {
private String someProperty;
// ... other properties and getters
public String getSomeProperty() {
return someProperty;
}
public String setSomeProperty(String someProperty) {
if (this.someProperty == null) {
this.someProperty = someProperty;
}
}
}
и application.yml как
myapp:
configurations:
- someProperty: one
- someProperty: two
- someProperty: other
Ответ 3
Моя идея заключается в том, чтобы инкапсулировать группы свойств через внутренние классы и предоставлять интерфейсы только с помощью методов получения.
Файл свойств:
myapp.security.token-duration=30m
myapp.security.expired-tokens-check-interval=5m
myapp.scheduler.pool-size=2
Код:
@Component
@ConfigurationProperties("myapp")
@Validated
public class ApplicationProperties
{
private final Security security = new Security();
private final Scheduler scheduler = new Scheduler();
public interface SecurityProperties
{
Duration getTokenDuration();
Duration getExpiredTokensCheckInterval();
}
public interface SchedulerProperties
{
int getPoolSize();
}
static private class Security implements SecurityProperties
{
@DurationUnit(ChronoUnit.MINUTES)
private Duration tokenDuration = Duration.ofMinutes(30);
@DurationUnit(ChronoUnit.MINUTES)
private Duration expiredTokensCheckInterval = Duration.ofMinutes(10);
@Override
public Duration getTokenDuration()
{
return tokenDuration;
}
@Override
public Duration getExpiredTokensCheckInterval()
{
return expiredTokensCheckInterval;
}
public void setTokenDuration(Duration duration)
{
this.tokenDuration = duration;
}
public void setExpiredTokensCheckInterval(Duration duration)
{
this.expiredTokensCheckInterval = duration;
}
@Override
public String toString()
{
final StringBuffer sb = new StringBuffer("{ ");
sb.append("tokenDuration=").append(tokenDuration);
sb.append(", expiredTokensCheckInterval=").append(expiredTokensCheckInterval);
sb.append(" }");
return sb.toString();
}
}
static private class Scheduler implements SchedulerProperties
{
@Min(1)
@Max(5)
private int poolSize = 1;
@Override
public int getPoolSize()
{
return poolSize;
}
public void setPoolSize(int poolSize)
{
this.poolSize = poolSize;
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder("{ ");
sb.append("poolSize=").append(poolSize);
sb.append(" }");
return sb.toString();
}
}
public SecurityProperties getSecurity() { return security; }
public SchedulerProperties getScheduler() { return scheduler; }
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder("{ ");
sb.append("security=").append(security);
sb.append(", scheduler=").append(scheduler);
sb.append(" }");
return sb.toString();
}
}
Ответ 4
Вы можете установить значения поля с помощью аннотаций @Value
. Они могут быть размещены непосредственно на полях и не требуют установки:
@Component
public final class MyProps {
@Value("${example.neededProperty}")
private final String neededProperty;
public String getNeededProperty() { .. }
}
Недостатком этого подхода является:
- Вам нужно будет указать полное имя свойства в каждом поле.
- Проверка не работает (см. Этот вопрос)
Ответ 5
Начиная с Spring Boot 2.2, наконец-то можно определить неизменяемый класс, украшенный @ConfigurationProperties
.
Документация показывает пример.
Вам просто нужно объявить конструктор с полями для привязки (вместо способа установки).
Таким образом, ваш настоящий код без какого-либо установщика теперь в порядке:
@ConfigurationProperties(prefix = "example")
public final class MyProps {
private final String neededProperty;
public MyProps(String neededProperty) {
this.neededProperty = neededProperty;
}
public String getNeededProperty() { .. }
}