spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.53k stars 38.12k forks source link

@Value annotation should be able to inject List<String> from YAML properties [SPR-11759] #16381

Closed spring-projects-issues closed 5 years ago

spring-projects-issues commented 10 years ago

Adam Berlin opened SPR-11759 and commented

Yaml file —


foobar:  
  ignoredUserIds:
    - 57016311
    - 22588218

Class —


public class Foobar {
    @Value("${foobar.ignoredUserIds}")
    List<String> ignoredUserIds;
}

Error —


Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: java.util.List foobar.Foobar.ignoredUserIds; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'foobar.ignoredUserIds' in string value "${foobar.ignoredUserIds}"

Reference URL: https://github.com/spring-projects/spring-boot/issues/501

21 votes, 21 watchers

spring-projects-issues commented 10 years ago

Adam Berlin commented

Any thoughts?

Ping.

spring-projects-issues commented 10 years ago

Adam Berlin commented

This feature would be nice for one of our current projects. We have a little hack to get around this limitation, but we'd love to get rid of it.

spring-projects-issues commented 10 years ago

Adil Fulara commented

@Adam Berlin

Would you mind sharing the "hack" please ?

Adil

spring-projects-issues commented 10 years ago

Adam Berlin commented

@Adil, I no longer have access to our client's codebase, so I cannot provide the workaround.

As I remember, we configured the list with a comma-delimited string and tokenized the string into a list. We'd definitely prefer that the standard YAML syntax for creating a list would translate into a List\ when using @Value.


Adam

spring-projects-issues commented 10 years ago

Bob Tiernay commented

This would certainly be a welcomed addition to @Value semantics. Please see this thread for further context:

https://github.com/spring-projects/spring-boot/issues/501

spring-projects-issues commented 9 years ago

Tongliang Liu commented

I think at least "one-level" array should be supported.

By "one-level", I mean each item in the array is of a simple type (integer, boolean, or string) and all items are of the same type in the array, for example:

oneLevelIntegerArray:
  - 1
  - 2
  - 3
oneLevelStringArray:
  - foo
  - bar

Therefore, in the code, one can use the following code to bind the above YAML arrays to a List:

    @Value("${oneLevelIntegerArray}")
    List<Integer> intArray;

    @Value("${oneLevelStringArray}")
    List<String> strArray;

But in order to bind more complex array structures, i.e. items in the array are of compound types (arrays, maps, etc..), one should use @ConfigurationProperties.

If this is acceptable, I'll create a pull request to make this happen. It is fairly easy to make this happen.

spring-projects-issues commented 9 years ago

Diego Magalhaes commented

Hey guys,

Just encountered  that and got help in the gitter spring-boot channel that led me here.

Sample code:

@Data
@RefreshScope
@ConfigurationProperties(prefix = "api")
@Configuration
public class APIProperties {
    private Config config;

    @Data
    public static class Config {
        private Blocked blocked;

        @Data
        public static class Blocked {
            List<String> blockedExtensions = new ArrayList<>();
        }
    }
}

Looking at :8080/env shows that ConfigurationProperties worked as expected:

applicationConfig: [classpath:/application.yml]: {
    api.config.blocked.extensions[0]: ".exe",
    api.config.blocked.extensions[1]: ".dll",
    api.config.blocked.extensions[2]: ".bin",
    api.config.blocked.extensions[3]: ".dat",
    api.config.blocked.extensions[4]: ".osx"
}

now the controler:

@RestController
public class SampleServices{

    private APIProperties props;

    @Autowired
    public ShorteningService(@Qualifier("APIProperties") APIProperties apiProperties) {
        this.props = apiProperties;
    }

    @RequestMapping("test")
    Callable<String> test() {
        return () -> props.getConfig().getBlocked().getBlockedExtensions().toString();
    }
}

Calling /test returns "[ ]", an empty list.

So to verify that @Value won't work as well, let's change it for:

@RestController
public class ShorteningService {
    @Value("${api.config.blocked.extensions}")
    List<String> blockedExtensions;

    @RequestMapping("test")
    Callable<String> test() {
        return () -> blockedExtensions.toString();
    }
}

Error:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'api.config.blocked.extensions' in string value "${api.config.blocked.extensions}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174)
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:204)
    at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:178)
    at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$2.resolveStringValue(PropertySourcesPlaceholderConfigurer.java:175)
    at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:807)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:980)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:967)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:543)

Changing the controller code to:

@RestController
public class ShorteningService {
    @Value("${api.config.blocked.extensions[0]}")
    String blockedExtensions;

    @RequestMapping("test")
    Callable<String> test() {
        return () -> blockedExtensions;
    }
}

Works like charm, for the .yaml bellow, the result is ".exe"

api:
  config:
    blocked:
      extensions:
      - .exe
      - .dll
      - .bin
      - .dat
      - .osx
spring-projects-issues commented 9 years ago

Stéphane Nicoll commented

Yeah well, your code there reproduces the limitation described in this issue. And this issue is not fixed so what you're experiencing is pretty much the current state yeah.

You are using Spring Boot so please do you a favour and stop using @Value. @ConfigurationProperties does a lot more and does support that use case.

spring-projects-issues commented 8 years ago

Wenjie Zhang commented

I encountered this problem when I was using spring framework to load yaml configuration:

@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
    final PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
    yaml.setResources(new ClassPathResource("application.yml"));
    propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
    return propertySourcesPlaceholderConfigurer;
}

//The property is 
@Value("${platforms}")
private List<String> platforms;

I can see the properties get loaded into the PropertySources object like platforms[0], platforms[1].

However, I still get following exception:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'platforms' in string value "${platforms}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174)
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
spring-projects-issues commented 8 years ago

Wenjie Zhang commented

Do you guys think making the @Value to support regular expression is a good idea to resolve this bug?

spring-projects-issues commented 7 years ago

Romero Ricardo commented

Yeah, 2017 and I'm still facing that same issue, the bug is not fixed. Do you guys think you can assign this issue to me so I get it sorted?

spring-projects-issues commented 7 years ago

Stéphane Nicoll commented

Feel free to give that a try Romero, no need to be assigned for that.

spring-projects-issues commented 5 years ago

Bulk closing outdated, unresolved issues. Please, reopen if still relevant.

abccbaandy commented 5 years ago

2019 and I need this feature. @Value >>>>> @ConfigurationProperties when there is only one field : private List<String> someList;

Why I need a Class when I only have one field?

komidawi commented 3 years ago

2021 and it's still needed 😉

mimfgg commented 3 years ago

how hard can it be ... a bit later in 2021 and still stumbling on this ...

aorizzuto commented 3 years ago

Replace this:

foobar:  
  ignoredUserIds:
    - 57016311
    - 22588218

with this:

foobar:  
  ignoredUserIds: 57016311 , 22588218

You need to separate the items with a comma. After that, you can do this:

public class Foobar {
    @Value("${foobar.ignoredUserIds}")
    List<String> ignoredUserIds;
}
biuwsi commented 2 years ago

Damn, spend 2 hours to find out, that it's not I am dumb ass, it's just a "feature"!

devdynam0507 commented 1 year ago

2022 it's still needed 😀

alexeytokar commented 1 year ago

2023 ;)

xzxiaoshan commented 12 months ago

2023.11.2 Needed, looking forward to early support

eritpchy commented 9 months ago

2024😩

chillb0nes commented 5 months ago

+1 for this

marcelstoer commented 2 months ago

This just got me... The current behavior is inconsistent and totally unexpected. Further up it says

Please, reopen if still relevant.

but none of us affected (no even the OP) can reopen it because it was created by bot.