0

In Spring I can configure all kinds of objects like int, boolean, String, etc. with the @Value injection. I even can configure a whole map structure with a single @Value injection.

Let's assume we have the following TCP server specification class:

public class TcpServerSpec {

    private boolean enabled;
    private String host;
    private int port;

    /* getters & setters */
}

I could inject a map of several server specifications by their names, doing ...

@Value("${com.harry.potter.sockets}")
private HashMap<String, TcpServerSpec> sockets;

... with the following YAML configuration (application-test1.yml):

com:
  harry:
    potter:
      sockets:
        server1:
          enabled: true
          host: host1
          port: 1111
        server2:
          enabled: true
          host: host2
          port: 2222

But often one needs something in between: the injection of just one TcpServerSpec:

@Value("${com.harry.potter.sockets.server1}")
private TcpServerSpec server1;

Then I could pass sucessfully the following tests:

@RunWith(SpringRunner.class)
@SpringBootTest
@ConfigurationProperties("com.harry.potter.sockets")
@TestPropertySource(locations = "classpath:application-test1.yml")
public class MyServerConfiguration1Test {

    @Value("${com.harry.potter.sockets.server1}")
    private TcpServerSpec server1;

    @Value("${com.harry.potter.sockets.server2}")
    private TcpServerSpec server2;

    @Test
    public void testServer1Spec() {
        Assert.assertTrue(server1.isEnabled());
        Assert.assertThat(server1.getHost(), is("host1"));
        Assert.assertThat(server1.getPort(), is(1111));
    }

    @Test
    public void testServer2Spec() {
        Assert.assertTrue(server2.isEnabled());
        Assert.assertThat(server2.getHost(), is("host2"));
        Assert.assertThat(server2.getPort(), is(2222));
    }
}

Why is this not possible? Or is it?

JBStonehenge
  • 192
  • 3
  • 15
  • After some testing I do believe that the injection of a map is a bit more complicated than simply use the @Value annotation. – JBStonehenge Aug 28 '21 at 02:52

1 Answers1

0

What do you want is not @Value but @ConfigurationProperties.

You can declare your beans manually like this:

@Configuration
class BeanConfig {
  
  @Bean
  @ConfigurationProperties(prefix = "com.harry.potter.sockets.server1")
  public TcpServerSpec server1() {
    return new TcpServerSpec();
  }

  @Bean
  @ConfigurationProperties(prefix = "com.harry.potter.sockets.server2")
  public TcpServerSpec server2() {
    return new TcpServerSpec();
  }
}

And then inject those beans with @Qualifier("server1") and @Qualifier("server2").

Gabriel Suaki
  • 321
  • 3
  • 6
  • 2
    why do you have `@Bean` _and_ `@ConfigurationProperties`? and why `prefix`? I don't understand how this answers the question – Eugene Aug 28 '21 at 01:56
  • You're right, `@Bean` is not needed when using `@ConfigurationProperties`. – Gabriel Suaki Aug 28 '21 at 02:02
  • After checking your answer: I think the \@Bean annotation is needed but the \@Qualifier is not. The two managed beans are found without any resolution problem because of their different names serer1/server2. – JBStonehenge Aug 28 '21 at 03:02
  • Great @JBStonehenge !! Rolling back the Bean annotation hehe My answer helped you? – Gabriel Suaki Aug 28 '21 at 03:11
  • Thank you for your answer. It helped me but also raised another issue. Adding server3 would require a configurative change - in the yaml file - and a programmatic change - in your `BeanConfig` class. – JBStonehenge Aug 28 '21 at 03:15
  • If you want more dinamyc with lists, you could create a class `TcpServerList` annotated with `@ConfigurationProperties(prefix = "com.harry.potter.sockets")` and with a prop `List servers`. Something like [this answer](https://stackoverflow.com/questions/32593014/mapping-list-in-yaml-to-list-of-objects-in-spring-boot). – Gabriel Suaki Aug 28 '21 at 03:41