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
Immutable @ConfigurationProperties
提问by RJo
Is it possible to have immutable (final) fields with Spring Boot's @ConfigurationProperties
annotation? 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:
到目前为止我尝试过的方法:
- Creating a
@Bean
of theMyProps
class with two constructors- Providing two constructors: empty and with
neededProperty
argument - The bean is created with
new MyProps()
- Results in the field being
null
- Providing two constructors: empty and with
- Using
@ComponentScan
and@Component
to provide theMyProps
bean.- Results in
BeanInstantiationException
->NoSuchMethodException: MyProps.<init>()
- Results in
- 创建
@Bean
了的MyProps
两个类的构造函数- 提供两个构造函数:空的和带
neededProperty
参数的 - bean是用
new MyProps()
- 该领域的结果是
null
- 提供两个构造函数:空的和带
- 使用
@ComponentScan
和@Component
提供MyProps
bean。- 结果
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 @ConstructorBinding
annotation 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 final
variables in a class.
我必须经常解决这个问题,我使用了一些不同的方法,它允许我final
在类中使用变量。
First of all, I keep all my configuration in a single place (class), say, called ApplicationProperties
. That class has @ConfigurationProperties
annotation with a specific prefix. It is also listed in @EnableConfigurationProperties
annotation against configuration class (or main class).
首先,我将所有配置保存在一个地方(类),例如,称为ApplicationProperties
. 该类具有@ConfigurationProperties
带有特定前缀的注释。它也列在@EnableConfigurationProperties
针对配置类(或主类)的注释中。
Then I provide my ApplicationProperties
as a constructor argument and perform assignment to a final
field 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);
}
}
ApplicationProperties
class
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 (ApplicationProperties
in my case); if there is a need to add more final
properties, 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 @Value
annotations. 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 @Configuration
class and @EnableConfigurationProperties
, you need to add @ConfigurationPropertiesScan
to 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