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


84

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

@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. 이것이 제가하고 싶은 일입니다. 어떤 제안?

답변:


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 개체로 읽을 수 있습니다. 그것은 적어도 캐스트를 막을 것입니다 ...
RoK

20
Spring boot에 대한 참고 사항 ... getPropertySources ()는 PropertySource를 우선 순위로 반환하므로 속성 값을 덮어 쓰는 경우이를 효과적으로 되돌려 야합니다
Rob Bygrave

2
@RobBygrave가 언급했듯이 순서는 다를 수 있지만 순서를 되 돌리는 대신 (전쟁으로 컨테이너에 스프링 부트를 배포 할 수 있거나이 동작이 향후 변경 될 수 있기 때문에) 모든 키를 수집 한 다음 applicationContext.getEnvironment().getProperty(key)해결하는 데 사용 합니다
potato

@potato 좋은 생각이고 나는 그것을 시도했다. : 유일한 잠재적 인 문제는 당신이 여기이 질문에 같은 자리로 평가 문제로 실행한다는 것입니다 stackoverflow.com/questions/34584498/...을
bischoje

1
감사합니다! .. 저는 org.apache.ibatis.io.Resources.getResourceAsProperties ( "Filepath") 대신 사용할 스프링 대안을 찾고있었습니다.이 솔루션은 저에게 매우 효과적이었습니다.
so-random-dude

67

이것은 오래된 질문이지만 받아 들여지는 대답에는 심각한 결함이 있습니다. 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)));

1
Spring 4.1.2부터이 솔루션 (다른 답변과 달리)은 CompositePropertySource가 EnumerablePropertySource를 확장하므로 getPropertyNames가 컴포지트의 모든 속성 이름 집합을 반환하므로 CompositePropertySource를 명시 적으로 처리하기 위해 업데이트 할 필요가 없다는 점은 주목할 가치가 있습니다. 출처.
M. 저스틴

5
또한 사용하여 등록 정보를 수집 할 수있는 내장 collect대신 일을 스트림에 방법 forEach: .distinct().collect(Collectors.toMap(Function.identity(), springEnv::getProperty)). 지도 대신 속성으로 수집해야하는 경우 collect.
M. Justin

2
무엇입니까 springEnv? 그거 어디서 났어? env허용되는 솔루션 과 다른 가요?
sebnukem

2
@sebnukem 좋은 지적입니다. springEnv는 IS env원래의 질문 및 허용 솔루션의 객체입니다. 나는 이름을 동일하게 유지해야한다고 생각한다.
pedorro

3
ConfigurableEnvironment 캐스트를 사용할 필요가 없습니다.
Abhijit Sarkar

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;

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

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

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

myEnv.getProperty( key );

1
이 방법으로 모든 키를 검색 한 다음 environment.getProperty를 사용하여 적절한 값 확인을 강제하는 것은 어떨까요? 예를 들어 application-dev.properties가 application.properties의 기본값을 재정의하고 자리 표시 자 eval을 언급했듯이 환경 재정의가 존중되는지 확인하고 싶습니다.
GameSalutes

이것이 제가 마지막 단락에서 지적한 것입니다. env.getProperty를 사용하면 Spring의 원래 동작을 보장합니다.
Heri

이것을 어떻게 단위 테스트합니까? 인스턴스 NullPointerException를 얻으려고 할 때 항상 단위 테스트 @Autowired에서 ConfigurationEnvironment.
ArtOfWarfare

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 dec

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.PropertiesSpring Environment에서 분리하는 것을 허용하지 않습니다 .

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

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

1

다른 답변은 PropertySources.

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

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


비공개 클래스의 문제에 대한 해결책을 찾았습니까?
Tobias

1

Spring Boot 2로 작업하면서 비슷한 작업이 필요했습니다. 위의 답변 대부분은 잘 작동합니다. 앱 수명주기의 다양한 단계에서 결과가 다를 수 있다는 점에 유의하세요.

예를 들어, ApplicationEnvironmentPreparedEvent내부 속성 application.properties이 존재하지 않습니다. 그러나 ApplicationPreparedEvent이벤트 후 그들은 있습니다.


1

Spring Boot의 경우 허용되는 답변은 우선 순위낮은 중복 속성을 덮어 씁니다 . 이 솔루션은 속성을 a로 수집하고 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 ()는 가장 낮은 우선 순위에서 가장 높은 우선 순위의 속성을 제공합니까?
Faraz

그 반대입니다. 우선 순위가 높음-> 낮음으로 정렬됩니다.
Samuel Tatipamula

0

한 가지 더 방법을 추가하겠습니다. 필자의 경우 에는 Hazelcast XML 구성 파일 내부의 일부 속성 com.hazelcast.config.XmlConfigBuilderjava.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 *
}

추신 : 그것은 XML 파일의 속성 만 확인하고 런타임에는 해결하지 않기 때문에 Hazelcast에 특별히 이것을 사용하지 않았습니다. 나는 또한 Spring을 사용하기 때문에 custom org.springframework.cache.interceptor.AbstractCacheResolver#getCacheNames. 이렇게하면 최소한 캐시 이름에 속성을 사용하는 경우 두 상황에 대한 속성이 해결됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.