Как защитить классы @ConfigurationProperties от изменений?
Использовать @ConfigurationProperties
аннотацию нужно создать класс с геттерами и сеттерами:
@ConfigurationProperties(prefix = "some")
public class PropertiesConfig {
private boolean debug;
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
}
Но это приводит к ситуации, когда кто-то испытывает желание изменить это значение, вызывая:
@Autowire
private PropertiesConfig config;
//....
config.setDebug(true);
Есть ли способ создать @ConfigurationProperties
аннотированные классы без сеттеров и внешних классов анализатора / читателя?
3 ответа
Начиная с Spring Boot 2.2, наконец, можно определить неизменяемый класс, украшенный @ConfigurationProperties
. На самом деле спасибо разработчикам Spring за постоянное улучшение их фреймворка.
В документации показан пример.
Вам просто нужно объявить конструктор с полями для привязки (вместо способа установки):
@ConstructorBinding
@ConfigurationProperties(prefix = "some")
public class PropertiesConfig {
private boolean debug;
public AcmeProperties(boolean enabled) {
this.enabled = enabled;
}
public boolean isDebug() {
return debug;
}
}
Примечание 1: вы должны определить один и только один конструктор с параметрами для привязки:
В этой настройке должен быть определен только один конструктор со списком свойств, которые вы хотите связать, а не другие свойства, кроме тех, что указаны в конструкторе.
Примечание 2: а @DefaultValue
был введен для определения значений по умолчанию для неизменяемой привязки свойств.
Значения по умолчанию можно указать с помощью @DefaultValue, и та же служба преобразования будет применена для приведения значения String к целевому типу отсутствующего свойства.
Вот более подробный пример из официальной документации:
import java.net.InetAddress;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DefaultValue;
import org.springframework.boot.context.properties.ConstructorBinding;
@ConstructorBinding
@ConfigurationProperties("acme")
public class AcmeProperties {
private final boolean enabled;
private final InetAddress remoteAddress;
private final Security security;
public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
public boolean isEnabled() { ... }
public InetAddress getRemoteAddress() { ... }
public Security getSecurity() { ... }
public static class Security {
private final String username;
private final String password;
private final List<String> roles;
public Security(String username, String password,
@DefaultValue("USER") List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
public String getUsername() { ... }
public String getPassword() { ... }
public List<String> getRoles() { ... }
}
}
Один из подходов с использованием как можно меньшего количества стандартного кода заключается в использовании интерфейса только с геттерами.
public interface AppProps {
String getNeededProperty();
}
и избавление от шаблонных геттеров и сеттеров при внедрении с помощью Lombok's @Getter
а также @Setter
аннотации:
@ConfigurationProperties(prefix = "props")
@Getter
@Setter
public class AppPropsImpl implements AppProps {
private String neededProperty;
}
Затем, чтобы bean-компонент был доступен для других bean-компонентов только через интерфейс, можно вместо того, чтобы пометить его как @Component
или используя @EnableConfigurationProperties(AppPropsImpl.class)
в главном классе приложения рассмотрите возможность помещения его в конфигурацию, которая предоставит интерфейс:
@Configuration
@EnableConfigurationProperties
public class PropsConfiguration {
@Bean
public AppProps appProps(){
return new AppPropsImpl();
}
}
Теперь этот bean-компонент может быть внедрен только с помощью интерфейса, и это делает сеттеры недоступными для других bean-компонентов:
public class ApplicationLogicBean {
@Autowired
AppProps props;
public void method(){
log.info("Got " + props.getNeededProperty());
}
}
Протестировано с Spring Boot 1.5.3 и Lombok 1.16.16.
Как то так нормально работает
@Configuration
class MyAppPropertiesConfiguration {
@Bean
@ConfigurationProperties(prefix = "some")
public PropertiesConfig propertiesConfig () {
return new PropertiesConfigImpl();
}
public interface PropertiesConfig {
public boolean isDebug();
}
private static class PropertiesConfigImpl implements PropertiesConfig {
private boolean debug;
@Override
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
}
}
а потом
@Autowired PropertiesConfig properties;
Не возможно из коробки. @ConfigurationProperties
бобы должны иметь стандартные методы получения и установки. Возможно, вы захотите рассмотреть подход, описанный в этом ответе: Immutable @ConfigurationProperties
Или как то так:
@Component
public class ApplicationProperties {
private final String property1;
private final String property2;
public ApplicationProperties(
@Value("${some.property1"}) String property1,
@Value("${some.other.property2}) String property2) {
this.property1 = property1;
this.property2 = property1;
}
//
// ... getters only ...
//
}