How to read multiple properties with same prefix in java spring?
Asked Answered
G

8

9

I am having the property file with the following values. I want to read all the properties with same prefix excluding others using spring mvc.

test.cat=cat
test.dog=dog
test.cow=cow
birds=eagle

Environment.getProperty("test.cat");

This will return only 'cat' .

In the above property file, I want to get all the properties starting(prefix) with test excluding birds. In spring boot we can achieve this with @ConfigurationProperties(prefix="test"), but how to achieve the same in spring mvc.

Gazette answered 18/12, 2017 at 17:6 Comment(0)
M
15

See this Jira ticket from Spring project for explanation why you don't see a method doing what you want. The snippet at the above link can be modified like this with an if statement in innermost loop doing the filtering. I assume you have a Environment env variable and looking for String prefix:

Map<String, String> properties = new HashMap<>();
if (env instanceof ConfigurableEnvironment) {
    for (PropertySource<?> propertySource : ((ConfigurableEnvironment) env).getPropertySources()) {
        if (propertySource instanceof EnumerablePropertySource) {
            for (String key : ((EnumerablePropertySource) propertySource).getPropertyNames()) {
                if (key.startsWith(prefix)) {
                    properties.put(key, propertySource.getProperty(key));
                }
            }
        }
    }
}
Mathias answered 18/12, 2017 at 17:34 Comment(0)
S
4

I walked into a similar problem and solved it using the following code:

final var keyPrefix = "test";
final var map = new HashMap<String, Object>();
final var propertySources = ((AbstractEnvironment) environment).getPropertySources().stream().collect(Collectors.toList());
Collections.reverse(propertySources);
for (PropertySource<?> source : propertySources) {
    if (source instanceof MapPropertySource) {
        final var mapProperties = ((MapPropertySource) source).getSource();
        mapProperties.forEach((key, value) -> {
            if (key.startsWith(keyPrefix)) {
                map.put(key, value instanceof OriginTrackedValue ? ((OriginTrackedValue) value).getValue() : value);
            }
        });
    }
}
return map;

Difference with other solutions (like seen in Jira ticket) is that I reverse sort the property sources to ensure that the appropriate overriding of properties is happening. By default the property-sources are sorted from most important to least important (also see this article). So if you would not reverse the list before iterating you will always end up with the least important property values.

Stearne answered 25/6, 2019 at 7:59 Comment(0)
S
3

Try this method. In your example, calling getAllPropWithPrefix("test") will return a Map containing {test.cat=cat,test.dog=dog,test.cow=cow}

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

@Component
public class SpringPropertiesUtil {

    @Autowired
    private ConfigurableApplicationContext applicationContext;

    /**
     * Get all properties with specific prefix. 
     */
    public Map<String,String> getAllPropWithPrefix(String prefix) {
        BindResult<Map<String, String>> result = Binder.get(applicationContext.getEnvironment())
                .bind(prefix, Bindable.mapOf(String.class, String.class));
        if (!result.isBound() || result.get()==null) {
            return Collections.emptyMap();
        }
        return result.get().entrySet().stream().collect(Collectors.toMap(x->prefix+"."+x.getKey(),x->x.getValue()));
    }
}

Sammer answered 2/3, 2023 at 12:14 Comment(0)
P
1

There is more eloquent solution using existing API:

Given the following simple object:

internal class AboutApi {
    lateinit var purpose: String
    lateinit var version: String
    lateinit var copyright: String
    lateinit var notice: String
    lateinit var contact: String
    lateinit var description: String
}

And an instance of Spring environment (org.springframework.core.env.Environment), it becomes as easy as 1-2-3 to extract a value of AboutApi from the environment:

private fun about() : AboutApi {
    val binder = Binder.get(env)                   // Step - 1 
    val target = Bindable.ofInstance(AboutApi())   // Step - 2
    binder.bind("api.about", target)               // Step - 3
    return target.value.get()                      // Finally!
}
  1. Get a binder which source values from the environment.
  2. Declare a target you want to bind to.
  3. Bind all keys from the environment which starts with api.about to our target.

Finally --

Retrieve the value!

Postpone answered 17/6, 2020 at 18:27 Comment(1)
The question is tagged Java.Riant
P
0

I use this approach to load properties with a certain prefix in Spring (Assuming that you have injected Configuration in your component):

Properties properties = new Properties();
configuration.getKeys("test.")
   .forEachRemainingKey(key -> properties.setProperty(key, configuration.getString(key));
Preponderance answered 7/3, 2023 at 5:58 Comment(0)
W
0

The answers from Manos and Yoran are helpful. I found a small change to Manos' code would be able to address Yoran's concern. Basically, the same propertity may come multiple times, from environment variable and application.yml for example, in the order of importance descending. Thus we can just get the first instance by checking that the key is not in properties already, as in the improved code below:

Map<String, String> properties = new HashMap<>();
if (env instanceof ConfigurableEnvironment) {
    for (PropertySource<?> propertySource : ((ConfigurableEnvironment) env).getPropertySources()) {
        if (propertySource instanceof EnumerablePropertySource) {
            for (String key : ((EnumerablePropertySource) propertySource).getPropertyNames()) {
                if (key.startsWith(prefix) && !properties.containsKey(key)) {
                    properties.put(key, propertySource.getProperty(key));
                }
            }
        }
    }
}
Wretched answered 31/10, 2023 at 21:26 Comment(0)
S
0

Lets assume we want to read all properties with prefix spark.

private static final String SPARK_PREFIX = "spark.";

@Bean
Properties sparkProperties(final Environment environment) {
  if (environment instanceof ConfigurableEnvironment) {
    List<PropertySource<?>> propertySources =
        ((ConfigurableEnvironment) environment)
            .getPropertySources().stream().collect(Collectors.toList());
    List<String> sparkPropertyNames =
        propertySources.stream()
            .filter(propertySource -> propertySource instanceof EnumerablePropertySource)
            .map(propertySource -> (EnumerablePropertySource) propertySource)
            .map(EnumerablePropertySource::getPropertyNames)
            .flatMap(Arrays::stream)
            .distinct()
            .filter(key -> key.startsWith(SPARK_PREFIX))
            .collect(Collectors.toList());

    return sparkPropertyNames.stream()
        .collect(
            Properties::new,
            (props, key) -> props.put(key, environment.getProperty(key)),
            Properties::putAll);
  } else {
    return new Properties();
  }
}

Then we can use this anywhere as follows. @Qualifies is important as there is another bean defined by Spring boot of type Properties for systemProperties

@Bean
SparkConf sparkConf(@Qualifier("sparkProperties") final Properties sparkProperties) {
  final SparkConf sparkConf = new SparkConf();
  sparkProperties.forEach((key, value) -> sparkConf.set(key.toString(), value.toString()));
  return sparkConf;
}
Sponger answered 19/6 at 18:36 Comment(0)
S
0

In coming across this question while searching for how to get a map of all properties with a given prefix in a Spring application, I was able to take the answer from @Joseph and come up with what I think is the simplest straightforward approach to create such a map:

private static Map<String, String> getPropertiesPrefixedWith(ApplicationContext context, String prefix) {
    return Binder.get(applicationContext.getEnvironment())
        .bind(prefix, Bindable.mapOf(String.class, String.class))
        .orElse(Collections.emptyMap());
}

BindResult, returned by Binder::bind, is a monadic type like Optional that checks for the existence/binding of a value internally, so we can just simplify return value with orElse

Status answered 29/7 at 16:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.