[spring] Spring : Map 또는 Properties 객체로 모든 환경 속성에 액세스

주석을 사용하여 다음과 같이 스프링 환경을 구성합니다.

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

이로 인해 내 속성 default.propertiesEnvironment. @PropertySource환경 설정 (예 : config_dir 위치)에 따라 여러 폴백 레이어와 다른 동적 위치를 통해 속성을 오버로드 할 수있는 가능성을 이미 제공하기 때문에 여기 에서 메커니즘 을 사용하고 싶습니다 . 예제를 더 쉽게 만들기 위해 대체를 제거했습니다.

그러나 내 문제는 예를 들어 내 데이터 소스 속성을 default.properties. 데이터 소스가 사용하는 데 필요한 설정을 자세히 알지 않고도 데이터 소스에 설정을 전달할 수 있습니다.

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

그러나 문제는 Environment객체가 Properties객체도 Map아니고 비교할만한 것도 아니라는 것입니다. 전혀 없기 때문에 내 관점에서 환경의 모든 값에 액세스 할 단순히 수 없습니다 keySet또는 iterator방법 또는 유사한 아무것도.

Properties p <=== Environment env?

내가 뭔가를 놓치고 있습니까? Environment어떻게 든 객체 의 모든 항목에 액세스 할 수 있습니까? 그렇다면 항목을 Map또는 Properties객체에 매핑 할 수 있으며 접두사로 필터링하거나 매핑 할 수도 있습니다. 하위 집합을 표준 자바로 생성합니다 Map. 이것이 제가하고 싶은 일입니다. 어떤 제안?



답변

이와 같은 것이 필요합니다. 아마도 개선 될 수 있습니다. 이것은 첫 번째 시도입니다.

...
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은 속성 으로 액세스 할 수 있습니다 .


답변

이것은 오래된 질문이지만 받아 들여지는 대답에는 심각한 결함이 있습니다. Spring Environment객체가 ( Externalized Configuration에 설명 된대로) 재정의 값을 포함하는 경우, 생성하는 속성 값의 맵이 Environment객체 에서 반환 된 값과 일치 할 것이라는 보장이 없습니다 . 나는 단순히의 PropertySources를 반복하는 것이 Environment실제로 어떤 재정의 값도 제공하지 않는다는 것을 발견했습니다 . 대신에 재정의되어야하는 원래 값을 생성했습니다.

여기에 더 나은 해결책이 있습니다. 이것은의 EnumerablePropertySources 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)));


답변

키가 고유 한 접두어로 시작하는 모든 속성 (예 : “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;

매우 특별한 종류의 속성 소스 (예 : 스프링 자동 구성에서 일반적으로 사용되지 않는 JndiPropertySource)를 사용하지 않는 경우 환경에있는 모든 속성을 검색 할 수 있습니다.

구현은 스프링 자체가 제공하는 반복 순서에 의존하고 처음 발견 된 속성을 취하며 나중에 발견 된 동일한 이름의 모든 속성은 삭제됩니다. 이렇게하면 환경이 속성을 직접 요청한 것과 동일한 동작을 보장해야합니다 (첫 번째 발견 된 속성 반환).

또한 반환 된 속성은 $ {…} 연산자가있는 별칭을 포함하는 경우 아직 확인되지 않습니다. 특정 키를 확인하려면 환경에 직접 다시 문의해야합니다.

myEnv.getProperty( key );


답변

원래 질문은 접두사를 기반으로 모든 속성을 필터링 할 수 있으면 좋을 것임을 암시했습니다. 나는 이것이 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();
}


답변

이번 봄의 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;
}


답변

Spring은 java.util.PropertiesSpring Environment에서 분리하는 것을 허용하지 않습니다 .

그러나 Properties.load()여전히 스프링 부트 애플리케이션에서 작동합니다.

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


답변

다른 답변은 PropertySources.

이러한 예 중 하나는 명령 줄 인수의 속성 소스입니다. 사용되는 클래스는 SimpleCommandLinePropertySource입니다. 이 private 클래스는 public 메서드에 의해 반환 되므로 개체 내부의 데이터에 액세스하는 것이 매우 까다로워집니다. 데이터를 읽고 결국 속성 소스를 교체하기 위해 리플렉션을 사용해야했습니다.

누구든지 더 나은 솔루션을 가지고 있다면 정말보고 싶습니다. 그러나 이것은 내가 작업 한 유일한 해킹입니다.