不可变的@ConfigurationProperties

时间:2014-10-01 09:14:04

标签: java spring spring-boot properties-file

是否可以使用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参数
    • 使用new MyProps()
    • 创建bean
    • 字段中的结果为null
  2. 使用@ComponentScan@Component提供MyProps bean。
    • BeanInstantiationException中的结果 - &gt; NoSuchMethodException: MyProps.<init>()
  3. 我唯一有效的方法是为每个非最终字段提供getter / setter。

7 个答案:

答案 0 :(得分:13)

我必须经常解决这个问题,并且我使用了一种不同的方法,这允许我在类中使用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 
}

我更喜欢这种方法,原因很多,例如:如果我必须在构造函数中设置更多属性,我的构造函数参数列表不是&#34;巨大的&#34;因为我总是有一个论点(ApplicationProperties在我的情况下);如果需要添加更多final属性,我的构造函数保持不变(只有一个参数) - 这可能会减少其他地方的更改次数等。

我希望这会有所帮助

答案 1 :(得分: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
   }
}

现在配置是没有

的clas
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

答案 2 :(得分:0)

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

@Component
public final class MyProps {

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

  public String getNeededProperty() { .. }
}

这种方法的缺点是:

  • 您需要在每个字段上指定标准属性名称。
  • 验证无效(参见this question

答案 3 :(得分:0)

我的想法是通过内部类封装属性组,并仅使用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();
    }
}

答案 4 :(得分:0)

从Spring Boot 2.2开始,最后可以定义用@ConfigurationProperties装饰的不可变类。
The documentation显示了一个示例。
您只需要声明一个带有绑定字段的构造函数(而不是setter方法)。
因此,您现在无需任何设置程序的实际代码就可以了:

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

  private final String neededProperty;

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

  public String getNeededProperty() { .. }
}

答案 5 :(得分:0)

使用与https://stackoverflow.com/a/60442151/11770752中的方法类似的方法

但是您可以使用AllArgsConstructor代替RequiredArgsConstructor

考虑关注applications.properties

myprops.example.firstName=Peter
myprops.example.last-name=Pan
myprops.example.age=28

注意:使用与您的属性一致的属性,我只是想证明它们都是正确的(fistNamelast-name)。


Java类获取属性

@Getter
@ConstructorBinding
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "myprops.example")
public class StageConfig
{
    private final String firstName;
    private final Integer lastName;
    private final Integer age;

    // ...
}


此外,您还必须在构建工具中添加依赖项。

build.gradle

    annotationProcessor('org.springframework.boot:spring-boot-configuration-processor')

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <version>${spring.boot.version}</version>
</dependency>

如果您进一步采取措施为您的配置提供了精美而准确的描述,请考虑在目录additional-spring-configuration-metadata.json中创建文件src/main/resources/META-INF

{
  "properties": [
    {
      "name": "myprops.example.firstName",
      "type": "java.lang.String",
      "description": "First name of the product owner from this web-service."
    },
    {
      "name": "myprops.example.lastName",
      "type": "java.lang.String",
      "description": "Last name of the product owner from this web-service."
    },
    {
      "name": "myprops.example.age",
      "type": "java.lang.Integer",
      "description": "Current age of this web-service, since development started."
    }
}

(清理并编译以生效)


至少在IntelliJ中,将鼠标悬停在application.propoerties内的属性上时,您会清楚地看到自定义属性。对其他开发人员非常有用。

这为我的财产提供了一个简洁明了的结构,我将在春天的服务中使用它。

答案 6 :(得分:-1)

使用Lombok批注,代码如下所示:

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

  private final String neededProperty;

}

此外,如果您想直接自动装配该属性类,而不使用@Configuration类和@EnableConfigurationProperties,则需要将@ConfigurationPropertiesScan添加到用{{1}注释的主应用程序类中}。

在此处查看相关文档:https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-constructor-binding