Spring:将所有Environment属性作为Map或Properties对象访问

时间:2014-05-06 23:56:59

标签: 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 ...这就是我想要做。有什么建议吗?

12 个答案:

答案 0 :(得分:58)

你需要这样的东西,也许它可以改进。这是第一次尝试:

...
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属性进行访问。

答案 1 :(得分:44)

这是一个老问题,但接受的答案有严重的缺陷。如果Spring Environment对象包含任何重写值(如Externalized Configuration中所述),则无法保证它生成的属性值映射将与Environment对象返回的属性值匹配。我发现,简单地遍历PropertySource Environment的{​​{1}}实际上没有给出任何重要的值。相反,它产生了原始值,应该被覆盖的值。

这是一个更好的解决方案。这使用EnumerablePropertySource的{​​{1}}来迭代已知的属性名称,但随后从真实的Spring环境中读取实际值。这保证了值是Spring实际解决的值,包括任何重写值。

Environment

答案 2 :(得分:17)

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

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 );

答案 3 :(得分:5)

作为Spring's Jira ticket,这是一个有意的设计。但以下代码适用于我。

<a href="https://www.facebook.com/{{actorID.facebook_id}}" target="_blank"><i class="fa fa-facebook-official"></i></a>

答案 4 :(得分:3)

最初的问题暗示,能够基于前缀过滤所有属性会很好。我刚刚确认,对于Properties Map<String,String>,此版本从Spring Boot 2.1.1.RELEASE开始有效。我敢肯定它已经工作了一段时间了。有趣的是,如果没有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();
}

答案 5 :(得分:1)

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

一个这样的例子是命令行参数的属性源。使用的类是SimpleCommandLinePropertySource。此私有类由 public 方法返回,因此访问对象内的数据非常棘手。我不得不使用反射来读取数据并最终替换属性源。

如果有人有更好的解决方案,我真的很想看到它;然而,这是我唯一能够工作的黑客。

答案 6 :(得分:1)

Spring不允许通过java.util.Properties与Spring Environment解耦。

但是Properties.load()仍然可以在Spring启动应用程序中工作:

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

答案 7 :(得分:1)

要获取仅在我的 hibernate.properteies 文件中定义的属性:

@PropertySource(SomeClass.HIBERNATE_PROPERTIES)
public class SomeClass {

  public static final String HIBERNATE_PROPERTIES = "hibernate.properties";

  @Autowired
  private Environment env;

  public void someMethod() {
    final Properties hibProps = asProperties(HIBERNATE_PROPERTIES);
  }

  private Properties asProperties(String fileName) {
    return StreamSupport.stream(
      ((AbstractEnvironment) env).getPropertySources().spliterator(), false)
      .filter(ps -> ps instanceof ResourcePropertySource)
      .map(ps -> (ResourcePropertySource) ps)
      .filter(rps -> rps.getName().contains(fileName))
      .collect(
        Properties::new,
        (props, rps) -> props.putAll(rps.getSource()),
        Properties::putAll);
  }
}

答案 8 :(得分:0)

请尝试以下代码:

...
@Autowired
private Environment env;
...
for(Iterator<PropertySource<?>> it = ((AbstractEnvironment) env).getPropertySources().iterator(); it.hasNext(); ) {
    PropertySource<?> propertySource = (PropertySource<?>) it.next();
    if (propertySource instanceof CompositePropertySource) {
        for(Iterator<PropertySource<?>> it2 = ((CompositePropertySource) propertySource).getPropertySources().iterator(); it2.hasNext(); ) {
            PropertySource<?> propertySource2 = (PropertySource<?>) it2.next();
            if (propertySource2 instanceof ResourcePropertySource) {
                for (Entry<String, Object> entry : ((ResourcePropertySource)propertySource2).getSource().entrySet()) {
                    if (entry.getValue() instanceof String) { 
                        System.out.println(entry.getKey() + "=" + (String)entry.getValue());
                    }
                }
            }
        }
    }
}

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

对于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);
}

答案 11 :(得分:0)

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

@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 *
}

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