Spring:すべての環境プロパティにMapまたはPropertiesオブジェクトとしてアクセスします


86

アノテーションを使用して、春の環境を次のように構成しています。

@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どういうわけか、オブジェクトのすべてのエントリにアクセスすることは可能ですか?はいの場合、エントリをMapまたはPropertiesオブジェクトにマップできます。プレフィックスでフィルタリングまたはマップすることもできMapます。標準のJavaとしてサブセットを作成します...これが私がやりたいことです。助言がありますか?

回答:


75

あなたはこのようなものが必要です、多分それは改善されることができます。これは最初の試みです:

...
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オブジェクトに読み込むことができます。それは、少なくとも...キャストを防止するであろう
のRoK

20
Springブートに関する注意... 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

69

これは古い質問ですが、受け入れられた回答には重大な欠陥があります。SpringEnvironmentオブジェクトにオーバーライド値が含まれている場合(Externalized Configurationで説明されているように)、生成されるプロパティ値のマップがEnvironmentオブジェクトから返される値と一致するという保証はありません。のPropertySourcesを単純に反復してEnvironmentも、実際にはオーバーライド値が得られないことがわかりました。代わりに、オーバーライドされるべきであった元の値を生成しました。

これがより良い解決策です。これは、EnumerablePropertySourceのsを使用Environmentして既知のプロパティ名を反復処理しますが、実際のSpring環境から実際の値を読み取ります。これにより、値が、オーバーライドする値を含め、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はEnumerablePropertySourceを拡張し、getPropertyNamesはコンポジット内のすべてのプロパティ名のセットを返すため、このソリューションは(他の回答とは異なり)CompositePropertySourceを明示的に処理するために更新する必要がないことに注意してください。ソース。
M.ジャスティン・

5
collectを実行する代わりに、ストリームの組み込みメソッドを使用してプロパティを収集することもできforEachます.distinct().collect(Collectors.toMap(Function.identity(), springEnv::getProperty))。マップではなくプロパティに収集する必要がある場合は、4つの引数バージョンのを使用できますcollect
M.ジャスティン・

2
なにspringEnv?それはどこから来たのですか?env受け入れられているソリューションとは異なりますか?
sebnukem

2
@sebnukem良い点。springEnvあるenv元の質問&認められたソリューションのオブジェクト。名前は同じにすべきだったと思います。
pedorro 2018年

3
ConfigurableEnvironment キャストを使用する必要はありません。
アビジット・サルカール2018年

19

キーが個別のプレフィックスで始まるすべてのプロパティ(たとえば、「log4j.appender。」で始まるすべてのプロパティ)を取得する必要があり、次のコードを記述しました(Java 8のストリームとラムダを使用)。

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

開始点は、埋め込まれたPropertySourceを返すことができるConfigurableEnvironmentであることに注意してください(ConfigurableEnvironmentはEnvironmentの直接の子孫です)。次の方法で自動配線できます。

@Autowired
private ConfigurableEnvironment  myEnv;

非常に特殊な種類のプロパティソース(通常、Springの自動構成では使用されないJndiPropertySourceなど)を使用しない場合は、環境に保持されているすべてのプロパティを取得できます。

実装は、Spring自体が提供する反復順序に依存し、最初に見つかったプロパティを取得します。後で見つかった同じ名前のプロパティはすべて破棄されます。これにより、環境がプロパティを直接要求された場合と同じ動作が保証されます(最初に見つかったプロパティを返します)。

$ {...}演算子を使用したエイリアスが含まれている場合、返されるプロパティはまだ解決されていないことにも注意してください。特定のキーを解決したい場合は、環境に直接問い合わせる必要があります。

myEnv.getProperty( key );

1
この方法ですべてのキーを検出し、environment.getPropertyを使用して適切な値の解決を強制しないのはなぜですか?環境のオーバーライドが尊重されるようにする必要があります。たとえば、application-dev.propertiesは、application.propertiesのデフォルト値をオーバーライドし、プレースホルダーの評価について説明しました。
GameSalutes 2017年

それが前の段落で指摘したことです。env.getPropertyを使用すると、Springの元の動作が保証されます。
ヘリ2017年

これをどのようにユニットテストしますか?NullPointerException@Autowiredインスタンスを取得しようとすると、ユニットテストで常にが取得されますConfigurationEnvironment
ArtOfWarfare

春のアプリケーションとしてテストを実行してもよろしいですか?
ヘリ

:私はこのようにそれを行う
Heri

10

元の質問は、プレフィックスに基づいてすべてのプロパティをフィルタリングできると便利だと示唆していました。これがSpringBoot 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インスタンスを返し、プレフィックスを指定せずにそれを試行しても、コンパイルすることさえできません。これは、春ブーツ2.1.6.RELEASEである
ArtOfWarfare

1
私は仕事でJavaを書いていませんが、これをすぐに作成しました:github.com/AbuCarlo/SpringPropertiesBean。Springの起動シーケンスをなんらかの方法で回避すると、機能しない可能性があります(つまり、「プロパティ」Beanにデータが入力されることはありません)。これはJava8、Spring2.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は、SpringEnvironmentから経由で分離することを許可しませんjava.util.Properties

ただしProperties.load()、Springブートアプリケーションでは引き続き機能します。

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

1

他の回答はPropertySources、を含む大多数のケースの解決策を指摘していますが、特定のプロパティソースを有用なタイプにキャストできないことについては言及していません。

そのような例の1つは、コマンドライン引数のプロパティソースです。使用されるクラスはSimpleCommandLinePropertySourceです。このプライベートクラスはパブリックメソッドによって返されるため、オブジェクト内のデータにアクセスするのは非常に困難です。データを読み取り、最終的にプロパティソースを置き換えるために、リフレクションを使用する必要がありました。

誰かがもっと良い解決策を持っているなら、私は本当にそれを見たいです。しかし、これは私が仕事に取り掛かった唯一のハックです。


非公開クラスの問題の解決策を見つけましたか?
トビアス

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()は、最低から最高の優先度のプロパティを提供しますか?
ファラズ

それは逆です。それらは高->低優先度からソートされます。
SamuelTatipamula20年

0

私はもう1つの方法を追加したいと思います。私の場合、Hazelcast XML構成ファイル内の一部のプロパティを解決するcom.hazelcast.config.XmlConfigBuilderだけでよいjava.util.Properties、つまり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 *
}

PSこれはXMLファイルのプロパティのみを解決し、実行時には解決しないため、Hazelcast専用に使用しないことになりました。私もSpringを使っているので、カスタムで行くことにしましたorg.springframework.cache.interceptor.AbstractCacheResolver#getCacheNames。これにより、少なくともキャッシュ名でプロパティを使用する場合は、両方の状況のプロパティが解決されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.