Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Environment variables for list in spring boot configuration

For my Spring Boot application, I am trying to use an environment variable that holds the list of properties.topics in application.yml (see configuration below).

properties:
      topics:
        - topic-01
        - topic-02
        - topic-03

I use the configuration file to populate properties bean (see this spring documentation), as shown below

import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("properties")
public class ApplicationProperties {
  private List<String> topics = new ArrayList<>();
  public void setTopics(List<String> topics) {
     this.topics = topics;
  }
  public List<String> getTopics() {
     return this.topics;
  }
}

With the use of environment variable, I can have the list's content change without changing the application.yml. However, all examples that I could find so far only for cases where an environment variable holding only single value, not a collection of values in my case.

Edit:

To make it clear after @vancleff's comment, I do not need the values of the environment variable to be saved to application.yml.

Another edit:

I think by oversimplifying my question, I shoot myself in the foot. @LppEdd answer works well with the example given in my question. However, what happens if instead of a collection of simple string topic names, I need a bit more complex structure. For example, something like

properties:
  topics:
    - 
      name: topic-01
      id: id-1
    - 
      name: topic-02
      id: id-2
    - 
      name: topic-03
      id: id-3
like image 988
Phuong Hoang Avatar asked Mar 11 '19 16:03

Phuong Hoang


People also ask

How do I get a list of environment variables?

To list all the environment variables, use the command " env " (or " printenv "). You could also use " set " to list all the variables, including all local variables.

Can I use .ENV in spring boot?

Spring Boot allows you to externalize your configuration so you can work with the same application code in different environments. You can use properties files, YAML files, environment variables and command-line arguments to externalize configuration.


2 Answers

a bit late for the show but, I was facing the same problem and this solves it

https://github.com/spring-projects/spring-boot/wiki/Relaxed-Binding-2.0#lists-1

MY_FOO_1_ = my.foo[1]

MY_FOO_1_BAR = my.foo[1].bar

MY_FOO_1_2_ = my.foo[1][2]`

So, for the example in the question:

properties:
  topics:
    - 
      name: topic-01
      id: id-1
    - 
      name: topic-02
      id: id-2
    - 
      name: topic-03
      id: id-3

The environment variables should look like this:

PROPERTIES_TOPICS_0_NAME=topic-01
PROPERTIES_TOPICS_0_ID=id-01
PROPERTIES_TOPICS_1_NAME=topic-02
PROPERTIES_TOPICS_1_ID=id-02
PROPERTIES_TOPICS_2_NAME=topic-03
PROPERTIES_TOPICS_2_ID=id-03
like image 191
Corgan Avatar answered Sep 21 '22 15:09

Corgan


Suggestion, don't overcomplicate.

Say you want that list as an Environment variable. You'd set it using

-Dtopics=topic-01,topic-02,topic-03

You then can recover it using the injected Environment Bean, and create a new List<String> Bean

@Bean
@Qualifier("topics")
List<String> topics(final Environment environment) {
    final var topics = environment.getProperty("topics", "");
    return Arrays.asList(topics.split(","));
}

From now on, that List can be @Autowired.
You can also consider creating your custom qualifier annotation, maybe @Topics.

Then

@Service
class TopicService {
   @Topics
   @Autowired
   private List<String> topics;

   ...
}

Or even

@Service
class TopicService {
   private final List<String> topics;

   TopicService(@Topics final List<String> topics) {
      this.topics = topics;
   }

   ...
}

What you could do is use an externalized file.
Pass to the environment parameters the path to that file.

-DtopicsPath=C:/whatever/path/file.json

Than use the Environment Bean to recover that path. Read the file content and ask Jackson to deserialize it

You'd also need to create a simple Topic class

public class Topic {
    public String name;
    public String id;
}

Which represents an element of this JSON array

[
    {
        "name": "topic-1",
        "id": "id-1"
    },
    {
        "name": "topic-2",
        "id": "id-2"
    }
]

@Bean
List<Topic> topics(
        final Environment environment,
        final ObjectMapper objectMapper) throws IOException {
    // Get the file path
    final var topicsPath = environment.getProperty("topicsPath");

    if (topicsPath == null) {
        return Collections.emptyList();
    }

    // Read the file content
    final var json = Files.readString(Paths.get(topicsPath));

    // Convert the JSON to Java objects
    final var topics = objectMapper.readValue(json, Topic[].class);
    return Arrays.asList(topics);
}

enter image description here

like image 37
LppEdd Avatar answered Sep 23 '22 15:09

LppEdd