D00de
D00de

Reputation: 918

What is the best way to store and change global app properties in Spring Boot in a thread safe way

I am using Spring Boot, and loading/generating application properties in @Configuration bean on startup of my app, and placing them in HashMap, for example:

@Configuration
public class Config {

  @Bean(name = "appConfig")
  public Map<String, String> getApplicationConfig() {

    Map<String, String> appConfig = new HashMap<>();

    //load or generate global config varibles
    appConfig.put("GLOBAL_CONFIG", "VALUE 1");
    return appConfig;
}

I am injecting this map of properties in several beans, for example:

@RestController
@RequestMapping("/getglobalconfig")
public class ConfigController {

  @Value("#{appConfig}")
  private Map<String, String> appConfig;

  @GetMapping
  public String getGlobalConfig() { return appConfig.get("GLOBAL_CONFIG"); }
}

After some event I want to change for example the value of my appConfig HashMap GLOBAL_CONFIG value in a thread safe way, so that it's updated in all injected beans, like for example in the ConfigController example above.

What would be the best pattern to accomplish this in a thread safe way and have my beans reloaded value without re-initializing them or restarting the app. Spring cloud config looks interesting, but it's unfortunately unavailable for me.

I am not limited to HashMap properties, I just need a thread safe reloading of global property so that it's visible in injected beans after reload.

Upvotes: 1

Views: 2091

Answers (1)

Alexander Pavlov
Alexander Pavlov

Reputation: 2220

Spring creates singletons by default so all beans will have the same reference to the properties map. However, concurrent access to simple map may produce error.

So minimal required changes are

 Map<String, String> appConfig = new java.util.concurrent.ConcurrentHashMap<>();

 //load or generate global config varibles
 appConfig.put("GLOBAL_CONFIG", "VALUE 1");
 return appConfig;

or

 Map<String, String> appConfig = new HashMap<>();

 //load or generate global config varibles
 appConfig.put("GLOBAL_CONFIG", "VALUE 1");
  return java.util.Collections.synchronizedMap(appConfig);

However, approach you use means other beans could change properties too. I would create new wrapper bean around map which provides only one method

public class ReadOnlyConfig {
  private  final Map map;
  public ReadOnlyConfig (Map map) { this.map = map; }
  String getProperty(String name) { return map.get(name); }
}

and

@Configuration
public class Config {

  @Bean(name = "appConfig")
  public Map<String, String> getApplicationConfig() {

    Map<String, String> appConfig = new java.util.concurrent.ConcurrentHashMap<>();

    //load or generate global config varibles
    appConfig.put("GLOBAL_CONFIG", "VALUE 1");
    return appConfig;
}

  @Bean(name = "readOnlyConfig")
  public ReadOnlyConfig getReadOnlyApplicationConfig(  @Qualifier(name = "appConfig") Map<String, String> map) {

    return new ReadOnlyConfig(map);
}

Upvotes: 1

Related Questions