java 不可变的@ConfigurationProperties

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26137932/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 09:21:09  来源:igfitidea点击:

Immutable @ConfigurationProperties

javaspringspring-bootproperties-file

提问by RJo

Is it possible to have immutable (final) fields with Spring Boot's @ConfigurationPropertiesannotation? Example below

是否可以使用 Spring Boot 的@ConfigurationProperties注释具有不可变(最终)字段?下面的例子

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

  private final String neededProperty;

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

  public String getNeededProperty() { .. }
}

Approaches I've tried so far:

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

  1. Creating a @Beanof the MyPropsclass with two constructors
    • Providing two constructors: empty and with neededPropertyargument
    • The bean is created with new MyProps()
    • Results in the field being null
  2. Using @ComponentScanand @Componentto provide the MyPropsbean.
    • Results in BeanInstantiationException-> NoSuchMethodException: MyProps.<init>()
  1. 创建@Bean了的MyProps两个类的构造函数
    • 提供两个构造函数:空的和带neededProperty参数的
    • bean是用 new MyProps()
    • 该领域的结果是 null
  2. 使用@ComponentScan@Component提供MyPropsbean。
    • 结果BeanInstantiationException->NoSuchMethodException: MyProps.<init>()

The only way I have got it working is by providing getter/setter for each non-final field.

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

采纳答案by davidxxx

From Spring Boot 2.2, it is at last possible to define an immutable class decorated with @ConfigurationProperties.
The documentationshows an example.
You just need to declare a constructor with the fields to bind (instead of the setter way) and to add the @ConstructorBindingannotation at the class level to indicate that constructor binding should be used.
So your actual code without any setter is now fine :

从 Spring Boot 2.2 开始,终于可以定义一个用@ConfigurationProperties.
该文档显示了一个示例。
您只需要声明一个带有要绑定的字段的构造函数(而不是 setter 方式),并@ConstructorBinding在类级别添加注释以指示应该使用构造函数绑定。
所以你没有任何 setter 的实际代码现在很好:

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

  private final String neededProperty;

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

  public String getNeededProperty() { .. }
}

回答by Tom

I have to resolve that problem very often and I use a bit different approach, which allows me to use finalvariables in a class.

我必须经常解决这个问题,我使用了一些不同的方法,它允许我final在类中使用变量。

First of all, I keep all my configuration in a single place (class), say, called ApplicationProperties. That class has @ConfigurationPropertiesannotation with a specific prefix. It is also listed in @EnableConfigurationPropertiesannotation against configuration class (or main class).

首先,我将所有配置保存在一个地方(类),例如,称为ApplicationProperties. 该类具有@ConfigurationProperties带有特定前缀的注释。它也列在@EnableConfigurationProperties针对配置类(或主类)的注释中。

Then I provide my ApplicationPropertiesas a constructor argument and perform assignment to a finalfield inside a constructor.

然后,我将 myApplicationProperties作为构造函数参数提供,并对构造函数中的final字段执行赋值。

Example:

例子:

Mainclass:

类:

@SpringBootApplication
@EnableConfigurationProperties(ApplicationProperties.class)
public class Application {
    public static void main(String... args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

ApplicationPropertiesclass

ApplicationProperties班级

@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {

    private String someProperty;

    // ... other properties and getters

   public String getSomeProperty() {
       return someProperty;
   }
}

And a class with final properties

和一个具有最终属性的类

@Service
public class SomeImplementation implements SomeInterface {
    private final String someProperty;

    @Autowired
    public SomeImplementation(ApplicationProperties properties) {
        this.someProperty = properties.getSomeProperty();
    }

    // ... other methods / properties 
}

I prefer this approach for many different reasons e.g. if I have to setup more properties in a constructor, my list of constructor arguments is not "huge" as I always have one argument (ApplicationPropertiesin my case); if there is a need to add more finalproperties, my constructor stays the same (only one argument) - that may reduce number of changes elsewhere etc.

由于许多不同的原因,我更喜欢这种方法,例如,如果我必须在构造函数中设置更多属性,则我的构造函数参数列表并不“庞大”,因为我总是有一个参数(ApplicationProperties在我的情况下);如果需要添加更多final属性,我的构造函数保持不变(只有一个参数)——这可能会减少其他地方的更改次数等。

I hope that will help

我希望这会有所帮助

回答by dshvets1

My idea is to encapsulate property groups via inner classes and expose interfaces with getters only.

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

Properties file:

属性文件:

myapp.security.token-duration=30m
myapp.security.expired-tokens-check-interval=5m

myapp.scheduler.pool-size=2

Code:

代码:

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

回答by user2688838

In the end, if you want an immutable object you can also "hack" the setter that is

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

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

Obviously if the property is not just a String, that is a mutable object, things are more complicated but that's another story.

显然,如果该属性不仅仅是一个字符串,而是一个可变对象,事情会更复杂,但那是另一回事。

Even better you can create a Configuration container

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

@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
   private final List<MyConfiguration> configurations  = new ArrayList<>();

   public List<MyConfiguration> getConfigurations() {
      return configurations
   }
}

where now the configuration is a clas without

现在配置是一个没有的类

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

and application.yml as

和 application.yml 作为

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

回答by oberlies

You can set the field values through @Valueannotations. These can be placed directly on the fields and don't require any setters:

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

@Component
public final class MyProps {

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

  public String getNeededProperty() { .. }
}

The downside of this approach is:

这种方法的缺点是:

  • You'll need to specified the fully-qualified property name on each field.
  • Validation doesn't work (cf. this question)
  • 您需要在每个字段上指定完全限定的属性名称。
  • 验证不起作用(参见这个问题

回答by Radouxca

Using Lombok annotations the code would looks like this:

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

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

  private final String neededProperty;

}

Additionally if you want to Autowire this property class directly and not using @Configurationclass and @EnableConfigurationProperties, you need to add @ConfigurationPropertiesScanto main application class that is annotated with @SpringBootApplication.

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

See related documentation here: https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-constructor-binding

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