Home > Software engineering >  How to inject generic @ConfigurationProperties in Spring
How to inject generic @ConfigurationProperties in Spring

Time:01-01

I have generic ClientProperties class used by multiple rest clients to bind the client specific properties with implementation.

@Data
@Validated
public class ClientProperties {

    @NotBlank
    private String url;

    @NotNull
    private LoggingProperties logging;
}

Here is an example of application.properties

# Kasittely service
kasittelyservice.url= ...
kasittelyservice.logging.enabled=true

# Some other service
otherservice.url= ...
otherservice.logging.enabled=false

Currently I'm doing this with remote clients

@Service
public class KasittelyServiceClient {

    private final RestTemplate restTemplate;

    @SuppressWarnings("unused")
    public KasittelyServiceClient(RestTemplateBuilder builder, @Qualifier("kasittelyService") ClientProperties properties) {
        
        builder.rootUri(properties.getUrl());

        if (properties.getLogging().isEnabled()) {
            // TODO: debug logging interceptor
        }

        restTemplate = builder.build();
    }

    public VastausDto kasittelePyynto(PyyntoDto pyyntoDto) {
        return restTemplate.postForObject("/kasittelePyynto", pyyntoDto, VastausDto.class);
    }

    @Configuration
    public static class KasittelyServiceClientConfiguration {

        @Bean
        @Qualifier("kasittelyService")
        @ConfigurationProperties(prefix = "kasittelyservice")
        public ClientProperties properties() {
            return new ClientProperties();
        }
    }
}

However, that is a lot of boilerplate to bind properties for each client.

Ideally, I would like to do something like this, which isn't allowed

public KasittelyServiceClient(RestTemplateBuilder builder, @ConfigurationProperties("kasittelyService") ClientProperties properties) {

        builder.rootUri(properties.getUrl());

        if (properties.getLogging().isEnabled()) {
            // TODO: debug logging interceptor
        }

        restTemplate = builder.build();
}

How do I reduce boilerplate in this case?

CodePudding user response:

You can create an additional @ConfigurationProperties class that will contain the properties of all clients, which requires an additional prefix to be defined. For example:

@Data
@Component
@ConfigurationProperties(prefix = "properties")
public class AllClientsProperties {
  Map<String, ClientProperties> clients;

  public ClientProperties getClientPropertiesByKey(String key) {
    return clients.get(key);
  }
}

Then, change your application.properties accordingly.

# Kasittely service
properties.clients.kasittelyservice.url= ...
properties.clients.kasittelyservice.logging.enabled=true

# Some other service
properties.clients.otherservice.url= ...
properties.clients.otherservice.logging.enabled=false

Lastly, add the AllClientsProperties bean as a dependency to the constructor of the client service, and get the properties of the client by the key which are used to perform the initialization logic.

@Service
public class KasittelyServiceClient {
  public static final String PROPERTIES_KEY = "kasittelyservice";
  private final RestTemplate restTemplate;

  @SuppressWarnings("unused")
  public KasittelyServiceClient(RestTemplateBuilder builder, AllClientsProperties allClientsProperties) {
    ClientProperties properties = allClientsProperties.getClientPropertiesByKey(PROPERTIES_KEY);
    builder.rootUri(properties.getUrl());
    if (properties.getLogging().isEnabled()) {
      // TODO: debug logging interceptor
    }
    restTemplate = builder.build();
  }
  //...
}
  • Related