如何使用 Spring @Value 从 java 属性 文件中填充 HashMap

How to fill HashMap from java property file with Spring @Value

是否可以使用 Spring @Value 将值从属性文件映射到 HashMap。

目前我有这样的东西,映射一个值是没有问题的。 但是我需要在 HashMap 到期时映射自定义值。 这样的事情可能吗?

@Service
@PropertySource(value = "classpath:my_service.properties")
public class SomeServiceImpl implements SomeService {


    @Value("#{conf['service.cache']}")
    private final boolean useCache = false;

    @Value("#{conf['service.expiration.[<custom name>]']}")
    private final HashMap<String, String> expirations = new HashMap<String, String>();

属性 文件:'my_service.properties'

service.cache=true
service.expiration.name1=100
service.expiration.name2=20

这样映射可以吗key:value set

Is it possible to use Spring @Value, to map values from properties file to the HashMap?

是的,是的。在代码和 Spel.

的帮助下

首先,考虑这个单例Spring-bean(你应该扫描它):

@Component("PropertySplitter")
public class PropertySplitter {

    /**
     * Example: one.example.property = KEY1:VALUE1,KEY2:VALUE2
     */
    public Map<String, String> map(String property) {
        return this.map(property, ",");
    }

    /**
     * Example: one.example.property = KEY1:VALUE1.1,VALUE1.2;KEY2:VALUE2.1,VALUE2.2
     */
    public Map<String, List<String>> mapOfList(String property) {
        Map<String, String> map = this.map(property, ";");

        Map<String, List<String>> mapOfList = new HashMap<>();
        for (Entry<String, String> entry : map.entrySet()) {
            mapOfList.put(entry.getKey(), this.list(entry.getValue()));
        }

        return mapOfList;
    }

    /**
     * Example: one.example.property = VALUE1,VALUE2,VALUE3,VALUE4
     */
    public List<String> list(String property) {
        return this.list(property, ",");
    }

    /**
     * Example: one.example.property = VALUE1.1,VALUE1.2;VALUE2.1,VALUE2.2
     */
    public List<List<String>> groupedList(String property) {
        List<String> unGroupedList = this.list(property, ";");

        List<List<String>> groupedList = new ArrayList<>();
        for (String group : unGroupedList) {
            groupedList.add(this.list(group));
        }

        return groupedList;

    }

    private List<String> list(String property, String splitter) {
        return Splitter.on(splitter).omitEmptyStrings().trimResults().splitToList(property);
    }

    private Map<String, String> map(String property, String splitter) {
        return Splitter.on(splitter).omitEmptyStrings().trimResults().withKeyValueSeparator(":").split(property);
    }

}

注意: PropertySplitter class 使用来自 Guava 的 Splitter 实用程序。详情请参考its documentation

然后,在你的某个 bean 中:

@Component
public class MyBean {

    @Value("#{PropertySplitter.map('${service.expiration}')}")
    Map<String, String> propertyAsMap;

}

最后,属性:

service.expiration = name1:100,name2:20

这不完全是你问的,因为这个 PropertySplitter 与一个 属性 一起工作, 转换Map ,但我认为您可以切换到这种指定属性的方式,或者修改 PropertySplitter 代码,使其符合您想要的更分层的方式。

我根据前面post的启发做了一个解决方案。

在Spring配置中注册属性文件:

<util:properties id="myProp" location="classpath:my.properties"/>

然后我创建组件:

@Component("PropertyMapper")
public class PropertyMapper {

    @Autowired
    ApplicationContext applicationContext;

    public HashMap<String, Object> startWith(String qualifier, String startWith) {
        return startWith(qualifier, startWith, false);
    }

    public HashMap<String, Object> startWith(String qualifier, String startWith, boolean removeStartWith) {
        HashMap<String, Object> result = new HashMap<String, Object>();

        Object obj = applicationContext.getBean(qualifier);
        if (obj instanceof Properties) {
            Properties mobileProperties = (Properties)obj;

            if (mobileProperties != null) {
                for (Entry<Object, Object> e : mobileProperties.entrySet()) {
                    Object oKey = e.getKey();
                    if (oKey instanceof String) {
                        String key = (String)oKey;
                        if (((String) oKey).startsWith(startWith)) {
                            if (removeStartWith) 
                                key = key.substring(startWith.length());
                            result.put(key, e.getValue());
                        }
                    }
                }
            }
        }

        return result;
    }
}

当我想将所有以特定值开头的属性映射到 HashMap 时,使用 @Value 注释:

@Service
public class MyServiceImpl implements MyService {

    @Value("#{PropertyMapper.startWith('myProp', 'service.expiration.', true)}")
    private HashMap<String, Object> portalExpirations;

我能想到的基于 Spring Boot 的最快解决方案如下。在我的特定示例中,我将数据从一个系统迁移到另一个系统。这就是为什么我需要一个名为 priority 的字段的映射。

首先,我创建了属性文件(优先级-migration.properties),如下所示:

my.prefix.priority.0:0
my.prefix.priority.10:1
my.prefix.priority.15:2
my.prefix.priority.20:2
another.prefix.foo:bar

并将其放在 class 路径上。

假设您想在 spring 管理的 bean/component 中使用地图,请将您的 class 注释为:

@Component
@PropertySource("classpath:/priority-migration.properties")

你在地图中真正想要的当然只是前缀为my.prefix的key/value对,即这部分:

{
    0:0
    10:1
    15:2
    20:2
}

要实现这一点,您需要使用

注释您的组件
@ConfigurationProperties("my.prefix")

并为 priority 中缀创建一个 getter。后者在我的例子中被证明是强制性的(尽管 Sring Doc 说有一个 属性 priority 并用一个可变值初始化它就足够了)

private final Map<Integer, Integer> priorityMap = new HashMap<>();

public Map<Integer, Integer> getPriority() {
    return priorityMap;
}

最后

看起来像这样:

@Component
@ConfigurationProperties("my.prefix")
@PropertySource("classpath:/priority-migration.properties")
class PriorityProcessor {

    private final Map<Integer, Integer> priorityMap = new HashMap<>();

    public Map<Integer, Integer> getPriority() {
        return priorityMap;
    }

    public void process() {

        Integer myPriority = priorityMap.get(10)
        // use it here
    }
}

从Spring 4.1.x(虽然我不记得具体的版本),你可以做类似

@Value("#{${your.properties.key.name}}")
private Map<String, String> myMap;

属性文件中的 your.properties.key.name 应该类似于

your.properties.key.name={\
    name1 : 100, \
    name2 : 200 \
}

只需确保您应该创建 PropertySourcesPlaceholderConfigurer bean 以使其在您的应用程序中以及您正在编写任何单元测试代码来测试您的代码时都能正常工作,否则 [=21= 的 ${...} 占位符] 值不会按预期工作,您会看到一些奇怪的 SpringEL 错误。

您可以使用类似 SPEL json 的语法在 属性 文件中编写一个简单的地图或列表地图。

simple.map={'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}

map.of.list={\
  'KEY1': {'value1','value2'}, \
  'KEY2': {'value3','value4'}, \
  'KEY3': {'value5'} \
 }

我将 \ 用于多行 属性 以增强可读性

然后,在Java中,你可以像这样用@Value自动访问和解析它。

@Value("#{${simple.map}}")
Map<String, String> simpleMap;

@Value("#{${map.of.list}}")
Map<String, List<String>> mapOfList;

此处使用 ${simple.map}@Value 从 属性 文件中获取以下字符串:

"{'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}"

然后,它被评估为内联

@Value("#{{'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}}")

您可以在 the official documentation

中了解更多信息

使用@Valueapplication.yml拉取Map的解决方案属性 编码为 多行

application.yml

other-prop: just for demo 

my-map-property-name: "{\
         key1: \"ANY String Value here\", \  
         key2: \"any number of items\" , \ 
         key3: \"Note the Last item does not have comma\" \
         }"

other-prop2: just for demo 2 

这里我们的地图 属性 "my-map-property-name" 的值以 JSON 格式存储在 string 并且我们在行尾使用 \ 实现了多行

myJavaClass.java

import org.springframework.beans.factory.annotation.Value;

public class myJavaClass {

@Value("#{${my-map-property-name}}") 
private Map<String,String> myMap;

public void someRandomMethod (){
    if(myMap.containsKey("key1")) {
            //todo...
    } }

}

更多解释

  • \ 在yaml中用于将字符串分成多行

  • \" 是 yaml 字符串

  • 中 "(quote) 的转义字符
  • {key:value} JSON 将被@Value

    [=57= 转换为Map ]
  • #{ } 它是 SpEL 表达式,可以在@Value 中用于转换 json int Map 或 Array / list Reference

在 spring 引导项目中测试

使用与 Yaml 名称相同的变量名

例如:

private final HashMap<String, String> expiration 

而不是

private final HashMap<String, String> expirations 

或属性文件中的类似内容

org.code=0009,0008,0010
org.code.0009.channel=30,40
org.code.0008.channel=30,40
org.code.0010.channel=30,40

在Java中,阅读org.code然后遍历每个org.code并构建org.code..通道并将其放入地图....