8

I'm trying to load Configuration from YML. I can load value and I can also load list if these are comma seperated values. But i can't load a typical YML List.

Configuration Class

@Component
@PropertySource("classpath:routing.yml")
@ConfigurationProperties
class RoutingProperties(){
    var angular = listOf("nothing")
    var value: String = ""
}

Working routing.yml

angular: /init, /home
value: Hello World

Not Working routing.yml

angular:
    - init
    - home

value: Hello World

Why can't i load the second version of yml / do I have a syntaxt error?

ENV: Kotlin, Spring 2.0.0.M3

robie2011
  • 3,678
  • 4
  • 21
  • 20
  • just take a look at this answer here:[https://stackoverflow.com/questions/21271468/spring-propertysource-using-yaml/54247009#54247009](https://stackoverflow.com/questions/21271468/spring-propertysource-using-yaml/54247009#54247009) .it`s easy to use. – Forest10 Jan 18 '19 at 04:09

3 Answers3

8

As @flyx say, @PropetySource not worked with yaml files. But in spring you may override almost everything :)

PropertySource has additional parameter: factory. It's possible to create your own PropertySourceFactory base on DefaultPropertySourceFactory

open class YamlPropertyLoaderFactory : DefaultPropertySourceFactory() {
    override fun createPropertySource(name: String?, resource: EncodedResource?): org.springframework.core.env.PropertySource<*> {
        if (resource == null)
            return super.createPropertySource(name, resource)

        return YamlPropertySourceLoader().load(resource.resource.filename, resource.resource, null)
    }
}

And when use this factory in propertysource annotation:

@PropertySource("classpath:/routing.yml", factory = YamlPropertyLoaderFactory::class)

Last that you need is to initialized variable angular with mutableList

Full code sample:

@Component
@PropertySource("classpath:/routing.yml", factory = YamlPropertyLoaderFactory::class)
@ConfigurationProperties
open class RoutingProperties {
    var angular = mutableListOf("nothing")
    var value: String = ""


    override fun toString(): String {
        return "RoutingProperties(angular=$angular, value='$value')"
    }
}

open class YamlPropertyLoaderFactory : DefaultPropertySourceFactory() {
    override fun createPropertySource(name: String?, resource: EncodedResource?): org.springframework.core.env.PropertySource<*> {
        if (resource == null)
            return super.createPropertySource(name, resource)

        return YamlPropertySourceLoader().load(resource.resource.filename, resource.resource, null)
    }
}

@SpringBootApplication
@EnableAutoConfiguration(exclude = arrayOf(DataSourceAutoConfiguration::class))
open class Application {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val context = SpringApplication.run(Application::class.java, *args)

            val bean = context.getBean(RoutingProperties::class.java)

            println(bean)
        }
    }
} 
kurt
  • 1,510
  • 2
  • 13
  • 23
  • If you need to get properties for a specific profile, the third parameter in YamlPropertySourceLoader.load() is the profile name. Although, YamlPropertySourceLoader.load() has changed to return a list rather than a single propertysource, so you need to find the one in the list that matches your profile. Here's more info https://stackoverflow.com/a/53697551/10668441 – pcoates Dec 10 '18 at 15:55
  • As per question spring version is 2.0.0 which does not have this factory option in @PropertySource – ScanQR Apr 28 '21 at 07:48
1

Kinda old post, i know. But i am at the very same topic right now.

As of now, it seems that PropertySource does indeed work with yaml Files. Given the restriction that it only allows for primitive types (it seems) and it cant handle nested elements. I'm probably gonna dig a bit deeper and update my answer accordingly, but as of now, the accepted answer seems like a functioning workaround.

glace
  • 2,162
  • 1
  • 17
  • 23
0

Well, according to the docs, your YAML file will be rewritten into a property file. The first YAML file becomes:

angular=/init, /home
value=Hello World

While the second one becomes:

angular[0]=init
angular[1]=home
value=Hello World

These are obviously two very different things and therefore behave differently.

Moreover, later in the docs, it is stated that YAML does not even work with @PropertySource:

24.6.4 YAML shortcomings

YAML files can’t be loaded via the @PropertySource annotation. So in the case that you need to load values that way, you need to use a properties file.

That makes me kind of wonder why the first case works for you at all.

The docs say this about the generated …[index] properties:

To bind to properties like that using the Spring DataBinder utilities (which is what @ConfigurationProperties does) you need to have a property in the target bean of type java.util.List (or Set) and you either need to provide a setter, or initialize it with a mutable value, e.g. this will bind to the properties above

So, let's have a look at Kotlin docs: listOf returns a new read-only list of given elements. So the list is not mutable as required by the docs, which I assume is why it doesn't work. Try using a mutable list (since I have never used Kotlin, I cannot give you working code). Also try to declare it as java.util.List if that's possible in Kotlin.

Community
  • 1
  • 1
flyx
  • 35,506
  • 7
  • 89
  • 126
  • I can't explain why but I was able to load YAML with PropertySource. Things may have changed with the newst spring version. Because I was able to load Strings but no list. Anyway, I also have tried with mutableList and it didn't work. But using immutable list YAML List comma separated (/init, /home) worked – robie2011 Aug 28 '17 at 06:28