简体   繁体   中英

Nested Spring configuration (ConfigurationProperties) in records

How can one map an application.yaml configuration with nested properties to a similar record structure in Java?

Eg, if we have the following yaml:

foo:
    bar:
        something: 42

    baz:
        otherThing: true

    color: blue

The desired record structure would be something along the lines of:

@ConfigurationProperties(prefix = "foo")
@ConstructorBinding
public record Foo(
    Bar bar,
    Baz baz,
    String color
) {}

// ---

@ConfigurationProperties(prefix = "foo.bar")
@ConstructorBinding
public record Bar(
    int something
) {}

// ---

@ConfigurationProperties(prefix = "foo.baz")
@ConstructorBinding
public record Baz(
    boolean otherThing
) {}

You don't need @ConfigurationProperties for each nested class. It only for the root class (Foo.class). Then make the Foo as Spring Bean by inserting @Component above the class or put @ConfigurationPropertiesScan on the Application class.

Turns out I didn't ask the correct question for the issue I had:/ So for the case people find this topic from a similar issue, the answer to my actual issue follows here.

The problem arises with a nested yaml trying to "short cut" on the model hierarchy, so given the following yaml:

foo:
    bar:
        baz:
            bum: "hello"

I was trying to model the hierarchy as follows:

@ConfigurationProperties(prefix = "foo")
@ConstructorBinding
public record Foo(BarBaz barBaz) {}

// --- 

@ConfigurationProperties(prefix = "foo.bar.baz")
@ConstructorBinding
public record BarBaz(String bum) {}

Here the problem arises that Foo cannot do constructor binding for BarBaz (not sure why). So there are two possible solutions that I found:

1. Do the full modelling (decided that this is what I prefer)

That is, don't try to skip the middle model for bar .

@ConfigurationProperties(prefix = "foo")
@ConstructorBinding
public record Foo(Bar bar) {}

// ---

@ConfigurationProperties(prefix = "foo.bar")
@ConstructorBinding
public record Bar(Baz baz) {}

// --- 

@ConfigurationProperties(prefix = "foo.bar.baz")
@ConstructorBinding
public record Baz(String bum) {}

2. Don't use @ConstructorBinding when embedding more nestings

Simply skip the constructor binding in Foo .

@ConfigurationProperties(prefix = "foo")
public record Foo(BarBaz barBaz) {}

// --- 

@ConfigurationProperties(prefix = "foo.bar.baz")
@ConstructorBinding
public record BarBaz(String bum) {}

Although simpler, it's less consistent.

I think for simplification you can just create a single file with:

@ConfigurationProperties(prefix = "foo")
public record Foo(Bar bar) {
    public record Bar(Baz baz) {
        public record Baz(String bum) {}    
    }
}

and this is working fine in spring-boot and you don't need to repeat annotations and when using it you will just use:

String bumVal = foo.bar().baz().bum();

where foo is just injected in your Bean(s) where you need it.

I removed even the @ConsructorBinding as since spring-boot 2.6 it is no more needed as long the record defines only one constructor, see release notes .

The above configuration relates to your own answer structure, but here is also the compact way for the original questions structure:

@ConfigurationProperties(prefix = "foo")
public record Foo(Bar bar, Baz baz, String color) {
    public record Bar(String something) {
    }
    public record Baz(String otherThing) {
    }   
}

I found the record type very useful for this use case as very compact and not too much code to write.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM