Use constructor injection for spring ConfigurationProperties subclasses

时间秒杀一切 提交于 2020-06-25 21:49:23

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!