问题
I was looking at this https://www.baeldung.com/configuration-properties-in-spring-boot and was wondering if it was possible to use constructor injection for these in order to enforce some immutability properties.
For example would it be possible to do this:
@Component
@ConfigurationProperties("my-config")
public class MyConfig {
private final List<String> values;
public MyConfig(@Value("${values}") List<String> values) {
this.values = ImmutableList.copyOf(values);
}
}
And then in my yml config have
my-config.values:
- foo
- bar
But I get this error:
java.lang.IllegalArgumentException: Could not resolve placeholder 'values' in string value "${values}"
回答1:
The documentation states :
Property values can be injected directly into your beans by using the @Value annotation, accessed through Spring’s Environment abstraction, or be bound to structured objects through @ConfigurationProperties. :
You actually try to mix their behavior.values
is not a property of the Spring environment but my-config.values
is.
Even declared inside MyConfig
such as @Value("${values})"
it doesn't change anything as @ConfigurationProperties
bounds the properties to a structured object. And of course it doesn't create new properties in the Spring environment, that is where @Value()
looks for to resolve the value expression.
Whereas the exception to resolve ${values}
.
As MyConfig
is a component @Value
should be what you need :
@Component
public class MyConfig {
private final List<String> values;
public MyConfig(@Value("${my-config.values}") List<String> values) {
this.values = ImmutableList.copyOf(values);
}
}
You could also prevent the mutability by protecting the setter with a check but this will detect the issue only at runtime :
@ConfigurationProperties("my-config")
public class MyConfig {
private final List<String> values;
public List<String> getValue(){
return values;
}
public void setValue(List<String> values){
if (this.values != null){
throw new IllegalArgumentException("...");
}
this.values = ImmutableList.copyOf(values);
}
}
回答2:
is possible with spring boot since version 2.2.0 documentation is here: Constructor binding adding the new annotation @ConstructorBinding.
回答3:
For @ConfigurationProperties
spring uses properties (or setter) injection only. So this class should be mutable.
Also, it is not necessary to have a constructor in this case (and @Value
annotation can be skipped as well), and class can be as simple as this:
@Component
@ConfigurationProperties("my-config")
public class MyConfig {
private List<String> values;
//getter+setter
}
回答4:
Remove @Value("${values}")
and just use getters / setters
Or use SpEL - @Value("#{'${my-second-config.values}'.split(',')}"
Also, take look at Spring boot documentation
Example below:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Component
@ConfigurationProperties("my-config")
public static class MyConfig {
private List<String> values;
public List<String> getValues() {
return values;
}
public void setValues(List<String> values) {
this.values = ImmutableList.copyOf(values);
}
}
@Configuration
public static class MySecondConfig {
private final List<String> values;
@Autowired
public MySecondConfig(@Value("#{'${my-second-config.values}'.split(',')}")
List<String> values) {
this.values = ImmutableList.copyOf(values);
}
public List<String> getValues() {
return values;
}
}
@Service
public static class MyService {
private final MyConfig myConfig;
private final MySecondConfig mySecondConfig;
@Autowired
public MyService(MyConfig myConfig, MySecondConfig mySecondConfig) {
this.myConfig = myConfig;
this.mySecondConfig = mySecondConfig;
}
@PostConstruct
public void startUp() {
myConfig.getValues().forEach(System.out::println);
mySecondConfig.getValues().forEach(System.out::println);
}
}
}
application.properties :
my-config.values[0]=a
my-config.values[1]=b
my-second-config.values=c,d
console output:a
b
c
d
回答5:
@ConfigurationProperties binds the values with the given prefix. @ConfigurationProperties and @Value are not related. So @value will not be a relative path to the path you mentioned with @ConfigurationProperties. Also @ConfigurationProperties uses setters to inject the values, if you wish to make properties immutable you'll have to tweak your setters in some way.
public void setProperty(String property){
if(this.property == null){
this.property = property;
}
}
来源:https://stackoverflow.com/questions/51790207/use-constructor-injection-for-spring-configurationproperties-subclasses