Как защитить классы @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 ...
  //

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