春季:以Map或Properties对象的形式访问所有Environment属性


84

我正在使用注释来配置我的spring环境,如下所示:

@Configuration
...
@PropertySource("classpath:/config/default.properties")
...
public class GeneralApplicationConfiguration implements WebApplicationInitializer 
{
    @Autowired
    Environment env;
}

这导致我的财产default.properties成为的一部分Environment。我想在@PropertySource这里使用该机制,因为它已经可以根据环境设置(例如config_dir位置)通过多个后备层和不同的动态位置来重载属性。我只是剥离了后备,以使示例更容易。

但是,我现在的问题是我想在中配置例如我的数据源属性default.properties。您可以将设置传递给数据源,而无需详细了解数据源期望使用什么设置

Properties p = ...
datasource.setProperties(p);

但是,问题是,Environment对象既不是Properties对象,也不是对象,也不是Map任何可比较的对象。从我的角度来看,这是根本不可能的访问环境的所有值,因为没有keySetiterator方法或任何可比性。

Properties p <=== Environment env?

我想念什么吗?是否可以通过Environment某种方式访问对象的所有条目?如果是,我可以将条目映射到MapProperties对象,甚至可以通过前缀过滤或映射它们-将子集创建为标准Java Map...这就是我想做的。有什么建议么?

Answers:


72

您需要类似的东西,也许可以改进。这是第一次尝试:

...
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
...

@Configuration
...
@org.springframework.context.annotation.PropertySource("classpath:/config/default.properties")
...
public class GeneralApplicationConfiguration implements WebApplicationInitializer 
{
    @Autowired
    Environment env;

    public void someMethod() {
        ...
        Map<String, Object> map = new HashMap();
        for(Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator(); it.hasNext(); ) {
            PropertySource propertySource = (PropertySource) it.next();
            if (propertySource instanceof MapPropertySource) {
                map.putAll(((MapPropertySource) propertySource).getSource());
            }
        }
        ...
    }
...

基本上,环境中的所有内容MapPropertySource(都有很多实现)都可以作为Map属性来访问。


感谢您分享这种方法。我认为这有点“肮脏”,但这可能是前往这里的唯一方法。同事向我展示的另一种方法是使用固定键将属性放入配置中,该键包含一个包含所有属性键的列表。然后,您可以根据键列表将属性读取到Map / Properties对象中。这至少将阻止演员
阵容

20
对于Spring boot,请注意... getPropertySources()以优先顺序返回PropertySource,因此在属性值被覆盖的情况下,您实际上需要扭转这种情况
Rob Bygrave 2015年

2
作为@RobBygrave提到的顺序可能会有所不同,但而不是恢复秩序(因为你可以春天开机容器为战争或部署这一行为可以在未来改变)我只想收集所有的键,然后使用applicationContext.getEnvironment().getProperty(key)来解决这些问题
土豆

@potato是个好主意,我尝试过。唯一潜在的问题是,您在使用占位符时遇到评估问题,例如此处的问题:stackoverflow.com/questions/34584498/…–
bischoje

1
谢谢!..我一直在寻找替代org.apache.ibatis.io.Resources.getResourceAsProperties(“ Filepath”)的春季替代方案。
so-random-dude

67

这是一个古老的问题,但是公认的答案有一个严重的缺陷。如果SpringEnvironment对象包含任何覆盖的值(如Externalized Configuration中所述),则不能保证它产生的属性值映射将与Environment对象返回的值匹配。我发现只是简单地遍历PropertySourceEnvironment没有,其实给任何覆盖值。相反,它产生了原始值,该值应该被覆盖。

这是一个更好的解决方案。本品采用EnumerablePropertySource的S-Environment迭代通过已知的属性名称,但随后读取实际值了真正的春天的环境。这样可以确保该值是Spring实际解析的值,包括所有覆盖的值。

Properties props = new Properties();
MutablePropertySources propSrcs = ((AbstractEnvironment) springEnv).getPropertySources();
StreamSupport.stream(propSrcs.spliterator(), false)
        .filter(ps -> ps instanceof EnumerablePropertySource)
        .map(ps -> ((EnumerablePropertySource) ps).getPropertyNames())
        .flatMap(Arrays::<String>stream)
        .forEach(propName -> props.setProperty(propName, springEnv.getProperty(propName)));

1
值得注意的是,从Spring 4.1.2开始,此解决方案(与其他答案不同)无需进行更新即可显式处理CompositePropertySource,因为CompositePropertySource扩展了EnumerablePropertySource,因此getPropertyNames将返回组合中所有属性名称的集合。资源。
贾斯汀·

5
您也可以collect在流上使用内置方法来收集属性,而不用执行forEach.distinct().collect(Collectors.toMap(Function.identity(), springEnv::getProperty))。如果您需要将其收集到属性而不是地图中,则可以使用的四参数版本collect
M. Justin'Mar

2
什么springEnv啊 它从何而来?它与env公认的解决方案不同吗?
sebnukem

2
@sebnukem好点。springEnvenv原始问题和接受的解决方案的对象。我想我应该保持相同的名字。
pedorro

3
您可以使用,ConfigurableEnvironment 而不必进行转换。
Abhijit Sarkar,

19

我需要检索其键以不同前缀开头的所有属性(例如,所有以“ log4j.appender。开头的属性”),并编写以下代码(使用Java 8的流和lamda)。

public static Map<String,Object> getPropertiesStartingWith( ConfigurableEnvironment aEnv,
                                                            String aKeyPrefix )
{
    Map<String,Object> result = new HashMap<>();

    Map<String,Object> map = getAllProperties( aEnv );

    for (Entry<String, Object> entry : map.entrySet())
    {
        String key = entry.getKey();

        if ( key.startsWith( aKeyPrefix ) )
        {
            result.put( key, entry.getValue() );
        }
    }

    return result;
}

public static Map<String,Object> getAllProperties( ConfigurableEnvironment aEnv )
{
    Map<String,Object> result = new HashMap<>();
    aEnv.getPropertySources().forEach( ps -> addAll( result, getAllProperties( ps ) ) );
    return result;
}

public static Map<String,Object> getAllProperties( PropertySource<?> aPropSource )
{
    Map<String,Object> result = new HashMap<>();

    if ( aPropSource instanceof CompositePropertySource)
    {
        CompositePropertySource cps = (CompositePropertySource) aPropSource;
        cps.getPropertySources().forEach( ps -> addAll( result, getAllProperties( ps ) ) );
        return result;
    }

    if ( aPropSource instanceof EnumerablePropertySource<?> )
    {
        EnumerablePropertySource<?> ps = (EnumerablePropertySource<?>) aPropSource;
        Arrays.asList( ps.getPropertyNames() ).forEach( key -> result.put( key, ps.getProperty( key ) ) );
        return result;
    }

    // note: Most descendants of PropertySource are EnumerablePropertySource. There are some
    // few others like JndiPropertySource or StubPropertySource
    myLog.debug( "Given PropertySource is instanceof " + aPropSource.getClass().getName()
                 + " and cannot be iterated" );

    return result;

}

private static void addAll( Map<String, Object> aBase, Map<String, Object> aToBeAdded )
{
    for (Entry<String, Object> entry : aToBeAdded.entrySet())
    {
        if ( aBase.containsKey( entry.getKey() ) )
        {
            continue;
        }

        aBase.put( entry.getKey(), entry.getValue() );
    }
}

请注意,起点是ConfigurableEnvironment,它能够返回嵌入的PropertySources(ConfigurableEnvironment是Environment的直接后代)。您可以通过以下方式自动接线:

@Autowired
private ConfigurableEnvironment  myEnv;

如果您不使用非常特殊的属性源(例如JndiPropertySource,它通常在spring自动配置中不使用),则可以检索环境中保存的所有属性。

该实现依赖于spring本身提供并采用第一个发现的属性的迭代顺序,所有后来发现的具有相同名称的所有属性都将被丢弃。这应该确保与直接向环境请求属性(返回第一个找到的属性)的行为相同。

还请注意,如果返回的属性包含$ {...}运算符的别名,则尚未解析。如果要解决特定的密钥,则必须再次直接询问环境:

myEnv.getProperty( key );

1
为什么不只以这种方式发现所有键,然后使用environment.getProperty强制进行适当的值解析?要确保遵守环境覆盖,例如application-dev.properties覆盖application.properties中的默认值,就像您提到的占位符eval一样。
GameSalutes

这就是我在上一段中指出的。使用env.getProperty可以确保Spring的原始行为。
Heri

您如何对此进行单元测试?NullPointerException当我尝试获取的@Autowired实例时,我总是在单元测试中得到一个ConfigurationEnvironment
ArtOfWarfare 19/12/23

您确定要作为Spring应用程序运行测试吗?
Heri

我这样做是这样的:
Heri

10

最初的问题暗示,能够基于前缀过滤所有属性会很好。我刚刚确认,该版本自Spring Boot 2.1.1.RELEASE开始,适用于Properties Map<String,String>。我敢肯定它已经工作了一段时间了。有趣的是,没有prefix =资格就无法工作,即我知道如何将整个环境加载到地图中。如我所说,这实际上可能是OP想要开始的。前缀和后面的“。” 将被剥夺,这可能是也可能不是人们想要的:

@ConfigurationProperties(prefix = "abc")
@Bean
public Properties getAsProperties() {
    return new Properties();
}

@Bean
public MyService createService() {
    Properties properties = getAsProperties();
    return new MyService(properties);
}

后记:确实有可能而且很容易获得整个环境。我不知道这是怎么逃脱的:

@ConfigurationProperties
@Bean
public Properties getProperties() {
    return new Properties();
}

1
同样,诸如abc = x之类的属性也嵌套在{b = {c = x}}中
weberjn

这种方法没有任何作用-getAsProperties()总是返回一个空Properties实例,并且在没有指定前缀的情况下尝试它甚至无法编译。这是Spring Boot 2.1.6.RELEASE
ArtOfWarfare 19/12/23

1
我不是在工作中编写Java,但是我很快就把它整理了一下:github.com/AbuCarlo/SpringPropertiesBean。如果您以某种方式规避了Spring的启动顺序(即永远不会填充“ properties” bean),则可能无法正常工作。这适用于Java 8,Spring 2.2.6。
AbuNassar

5

作为今年春季的Jira门票,它是故意设计的。但是以下代码对我有用。

public static Map<String, Object> getAllKnownProperties(Environment env) {
    Map<String, Object> rtn = new HashMap<>();
    if (env instanceof ConfigurableEnvironment) {
        for (PropertySource<?> propertySource : ((ConfigurableEnvironment) env).getPropertySources()) {
            if (propertySource instanceof EnumerablePropertySource) {
                for (String key : ((EnumerablePropertySource) propertySource).getPropertyNames()) {
                    rtn.put(key, propertySource.getProperty(key));
                }
            }
        }
    }
    return rtn;
}

2

Spring不允许通过java.util.Properties与Spring Environment分离。

Properties.load()仍然可以在Spring Boot应用程序中使用:

Properties p = new Properties();
try (InputStream is = getClass().getResourceAsStream("/my.properties")) {
    p.load(is);
}

1

其他答案指出了大多数涉及的案例的解决方案PropertySources,但是没有人提到某些财产来源无法转换为有用的类型。

这样的示例之一就是命令行参数的属性源。使用的类是SimpleCommandLinePropertySource。此私有类由公共方法返回,因此访问对象内部的数据非常棘手。我必须使用反射来读取数据并最终替换属性源。

如果外面有人有更好的解决方案,我真的很想看到它。但是,这是我得到的唯一帮助。


您是否找到非公共类问题的解决方案?
Tobias

1

使用Spring Boot 2,我需要做类似的事情。上面的大多数答案都可以正常工作,只是要注意,在应用程序生命周期的各个阶段,结果将有所不同。

例如,之后没有ApplicationEnvironmentPreparedEvent任何内部属性application.properties。但是,在发生ApplicationPreparedEvent事件之后。


1

对于Spring Boot,可接受的答案将覆盖优先级较低的重复属性。此解决方案会将这些属性收集到中,SortedMap并且仅采用优先级最高的重复属性。

final SortedMap<String, String> sortedMap = new TreeMap<>();
for (final PropertySource<?> propertySource : env.getPropertySources()) {
    if (!(propertySource instanceof EnumerablePropertySource))
        continue;
    for (final String name : ((EnumerablePropertySource<?>) propertySource).getPropertyNames())
        sortedMap.computeIfAbsent(name, propertySource::getProperty);
}

env.getPropertySources()赋予属性从低到高的优先级?
法拉兹

相反。它们从高到低排序。
塞缪尔·塔蒂帕穆拉

0

我虽然会再添加一种方法。就我而言,我提供了com.hazelcast.config.XmlConfigBuilder它,只需java.util.Properties解析Hazelcast XML配置文件中的某些属性即可,即,它仅调用getProperty(String)method。因此,这使我能够做我需要做的事情:

@RequiredArgsConstructor
public class SpringReadOnlyProperties extends Properties {

  private final org.springframework.core.env.Environment delegate;

  @Override
  public String getProperty(String key) {
    return delegate.getProperty(key);
  }

  @Override
  public String getProperty(String key, String defaultValue) {
    return delegate.getProperty(key, defaultValue);
  }

  @Override
  public synchronized String toString() {
    return getClass().getName() + "{" + delegate + "}";
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    if (!super.equals(o)) return false;
    SpringReadOnlyProperties that = (SpringReadOnlyProperties) o;
    return delegate.equals(that.delegate);
  }

  @Override
  public int hashCode() {
    return Objects.hash(super.hashCode(), delegate);
  }

  private void throwException() {
    throw new RuntimeException("This method is not supported");
  }

  //all methods below throw the exception

  * override all methods *
}

PS我最终没有专门用于Hazelcast,因为它只能解析XML文件的属性,而不能在运行时解析。由于我也使用Spring,因此决定使用custom org.springframework.cache.interceptor.AbstractCacheResolver#getCacheNames。至少在缓存名称中使用属性的情况下,这可以解析这两种情况的属性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.