不可变的@ConfigurationProperties

问题描述 投票:35回答:4

是否可以使用Spring Boot的@ConfigurationProperties注释创建不可变(最终)字段?以下示例

@ConfigurationProperties(prefix = "example")
public final class MyProps {

  private final String neededProperty;

  public MyProps(String neededProperty) {
    this.neededProperty = neededProperty;
  }

  public String getNeededProperty() { .. }
}

到目前为止我尝试过的方法:

  1. 用两个构造函数创建@Bean类的MyProps 提供两个构造函数:empty和neededProperty参数 这个bean是用new MyProps()创建的 在该领域的结果是null
  2. 使用@ComponentScan@Component提供MyProps豆。 结果在BeanInstantiationException - > NoSuchMethodException: MyProps.<init>()

我得到它的唯一方法是为每个非最终字段提供getter / setter。

java spring spring-boot properties-file
4个回答
3
投票

从Spring Boot 2.2开始,最后可以定义用@ConfigurationProperties修饰的不可变类。 The documentation展示了一个例子。 您只需要声明一个带有要绑定的字段的构造函数(而不是setter方式)。 所以没有任何setter的实际代码现在很好:

@ConfigurationProperties(prefix = "example")
public final class MyProps {

  private final String neededProperty;

  public MyProps(String neededProperty) {
    this.neededProperty = neededProperty;
  }

  public String getNeededProperty() { .. }
}

12
投票

我必须经常解决这个问题,并且我使用了一些不同的方法,这允许我在类中使用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属性,我的构造函数保持不变(只有一个参数) - 这可能会减少其他地方的更改次数等。

我希望这会有所帮助


1
投票

最后,如果你想要一个不可变对象,你也可以“破解”那个setter

@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;
      }       
    }
}

显然,如果属性不仅仅是一个String,那是一个可变对象,事情就更复杂了,但这是另一个故事。

更好的是,您可以创建一个配置容器

@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 as

myapp:
  configurations:
    - someProperty: one
    - someProperty: two
    - someProperty: other

1
投票

我的想法是通过内部类封装属性组,并仅使用getter公开接口。

属性文件:

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();
    }
}

0
投票

您可以通过@Value注释设置字段值。这些可以直接放在字段上,不需要任何setter:

@Component
public final class MyProps {

  @Value("${example.neededProperty}")
  private final String neededProperty;

  public String getNeededProperty() { .. }
}

这种方法的缺点是:

  • 您需要在每个字段上指定完全限定的属性名称。
  • 验证不起作用(参见this question
© www.soinside.com 2019 - 2024. All rights reserved.