Spring: truy cập tất cả các thuộc tính Môi trường dưới dạng đối tượng Bản đồ hoặc Thuộc tính


84

Tôi đang sử dụng chú thích để định cấu hình môi trường mùa xuân của mình như sau:

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

Điều này dẫn đến tài sản của tôi default.propertieslà một phần của Environment. Tôi muốn sử dụng @PropertySourcecơ chế ở đây, vì nó đã cung cấp khả năng nạp chồng các thuộc tính thông qua một số lớp dự phòng và các vị trí động khác nhau, dựa trên cài đặt môi trường (ví dụ: vị trí config_dir). Tôi chỉ loại bỏ dự phòng để làm cho ví dụ dễ dàng hơn.

Tuy nhiên, vấn đề của tôi bây giờ là tôi muốn định cấu hình ví dụ như các thuộc tính nguồn dữ liệu của tôi trong default.properties. Bạn có thể chuyển các cài đặt đến nguồn dữ liệu mà không cần biết chi tiết những cài đặt mà nguồn dữ liệu mong đợi sẽ sử dụng

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

Tuy nhiên, vấn đề là, Environmentđối tượng không phải là Propertiesđối tượng, cũng không phải là cái Mapgì có thể so sánh được. Theo quan điểm của tôi, đơn giản là không thể truy cập tất cả các giá trị của môi trường, bởi vì không có keySethoặc iteratorphương pháp hoặc bất cứ thứ gì có thể so sánh được.

Properties p <=== Environment env?

Tui bỏ lỡ điều gì vậy? Có thể truy cập tất cả các mục của Environmentđối tượng bằng cách nào đó không? Nếu có, tôi có thể ánh xạ các mục nhập thành một Maphoặc Propertiesđối tượng, thậm chí tôi có thể lọc hoặc ánh xạ chúng bằng tiền tố - tạo các tập hợp con như một java tiêu chuẩn Map... Đây là điều tôi muốn làm. Bất kỳ đề xuất?

Câu trả lời:


72

Bạn cần một cái gì đó như thế này, có lẽ nó có thể được cải thiện. Đây là nỗ lực đầu tiên:

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

Về cơ bản, mọi thứ từ Môi trường MapPropertySource(và có khá nhiều triển khai) có thể được truy cập như một Mapthuộc tính.


Cảm ơn vì đã chia sẻ cách tiếp cận này. Tôi đồng ý điều này là một chút "bẩn" nhưng nó có lẽ là cách duy nhất để đi đến đây. Một cách tiếp cận khác mà một đồng nghiệp đã chỉ cho tôi là đưa một thuộc tính vào cấu hình bằng cách sử dụng một khóa cố định chứa một danh sách với tất cả các khóa thuộc tính. Sau đó, bạn có thể đọc các thuộc tính thành một đối tượng Bản đồ / Thuộc tính dựa trên danh sách khóa. Điều đó ít nhất sẽ ngăn chặn các phôi ...
RoK

20
Lưu ý cho khởi động mùa xuân ... mà getPropertySources () trả về PropertySource theo thứ tự ưu tiên, do đó bạn có hiệu quả cần phải đảo ngược rằng trong trường hợp giá trị tài sản được ghi đè
Rob Bygrave

2
Như @RobBygrave đã đề cập, thứ tự có thể khác, nhưng thay vì hoàn nguyên thứ tự (vì bạn có thể triển khai khởi động mùa xuân vào vùng chứa như chiến tranh hoặc hành vi này có thể thay đổi trong tương lai), tôi sẽ chỉ thu thập tất cả các khóa và sau đó sử dụng applicationContext.getEnvironment().getProperty(key)để giải quyết chúng
khoai tây

@potato Đó là một ý tưởng hay và tôi đã thử điều đó. Vấn đề tiềm ẩn duy nhất là bạn gặp phải vấn đề đánh giá với trình giữ chỗ, như trong câu hỏi này ở đây: stackoverflow.com/questions/34584498/…
bischoje,

1
Cảm ơn bạn! .. Tôi đang tìm kiếm một giải pháp thay thế mùa xuân để sử dụng thay cho org.apache.ibatis.io.Resources.getResourceAsProperties ("Filepath") Giải pháp này hoạt động rất tốt đối với tôi.
so-random-dude

67

Đây là một câu hỏi cũ, nhưng câu trả lời được chấp nhận có một lỗ hổng nghiêm trọng. Nếu Environmentđối tượng Spring chứa bất kỳ giá trị ghi đè nào (như được mô tả trong Cấu hình bên ngoài ), không có gì đảm bảo rằng bản đồ các giá trị thuộc tính mà nó tạo ra sẽ khớp với các giá trị được trả về từ Environmentđối tượng. Tôi thấy rằng chỉ cần lặp qua các PropertySources Environmenttrong thực tế, không cung cấp bất kỳ giá trị ghi đè nào. Thay vào đó, nó tạo ra giá trị ban đầu, giá trị mà lẽ ra phải được ghi đè.

Đây là một giải pháp tốt hơn. Điều này sử dụng các EnumerablePropertySources trong số Environmentđể lặp lại qua các tên thuộc tính đã biết, nhưng sau đó đọc giá trị thực từ môi trường Spring thực. Điều này đảm bảo rằng giá trị là giá trị được Spring thực sự phân giải, bao gồm mọi giá trị ghi đè.

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
Cần lưu ý rằng kể từ Spring 4.1.2, giải pháp này (không giống như các câu trả lời khác) không cần phải được cập nhật để xử lý rõ ràng với CompositePropertySource, vì CompositePropertySource mở rộng EnumerablePropertySource và do đó getPropertyNames sẽ trả về tập hợp tất cả các tên thuộc tính trong hỗn hợp nguồn.
M. Justin

5
Bạn cũng có thể thu thập các thuộc tính bằng cách sử dụng built-in collectphương pháp trên dòng thay vì làm một forEach: .distinct().collect(Collectors.toMap(Function.identity(), springEnv::getProperty)). Nếu bạn cần thu thập nó vào Thuộc tính thay vì Bản đồ, bạn có thể sử dụng phiên bản bốn đối số của collect.
M. Justin

2
Là gì springEnv? Nó đến từ đâu? Nó có khác với envgiải pháp được chấp nhận không?
sebnukem

2
@sebnukem Điểm tốt. springEnvenvđối tượng của câu hỏi ban đầu và giải pháp được chấp nhận. Tôi cho rằng tôi nên giữ nguyên cái tên.
pedorro

3
Bạn có thể sử dụng ConfigurableEnvironment và không cần phải cast.
Abhijit Sarkar

19

Tôi có yêu cầu truy xuất tất cả các thuộc tính có khóa bắt đầu bằng tiền tố riêng biệt (ví dụ: tất cả các thuộc tính bắt đầu bằng "log4j.appender.") Và viết Mã sau (sử dụng các luồng và lamdas của 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() );
    }
}

Lưu ý rằng điểm bắt đầu là Môi trường có thể cấu hình có thể trả về Nguồn thuộc tính được nhúng (Môi trường có thể cấu hình là hậu duệ trực tiếp của Môi trường). Bạn có thể tự động kích hoạt nó bằng cách:

@Autowired
private ConfigurableEnvironment  myEnv;

Nếu bạn không sử dụng các loại nguồn thuộc tính rất đặc biệt (như JndiPropertySource, thường không được sử dụng trong cấu hình tự động mùa xuân), bạn có thể truy xuất tất cả các thuộc tính được giữ trong môi trường.

Việc triển khai dựa trên thứ tự lặp lại mà spring tự cung cấp và lấy thuộc tính được tìm thấy đầu tiên, tất cả các thuộc tính được tìm thấy sau đó có cùng tên sẽ bị loại bỏ. Điều này sẽ đảm bảo hành vi giống như khi môi trường được yêu cầu trực tiếp cho một thuộc tính (trả lại thuộc tính được tìm thấy đầu tiên).

Cũng lưu ý rằng các thuộc tính trả về vẫn chưa được giải quyết nếu chúng chứa bí danh với toán tử $ {...}. Nếu bạn muốn giải quyết một khóa cụ thể, bạn phải hỏi lại trực tiếp Môi trường:

myEnv.getProperty( key );

1
Tại sao không chỉ khám phá tất cả các khóa theo cách này và sau đó sử dụng môi trường.getProperty để buộc phân giải giá trị thích hợp? Muốn đảm bảo rằng ghi đè môi trường được tôn trọng, ví dụ: application-dev.properties ghi đè giá trị mặc định trong application.properties và như bạn đã đề cập đến đánh giá trình giữ chỗ.
GameSalutes

Đó là những gì tôi đã chỉ ra trong đoạn cuối. Sử dụng env.getProperty đảm bảo hành vi ban đầu của Spring.
Heri

Làm thế nào để bạn kiểm tra đơn vị này? Tôi luôn nhận được một NullPointerExceptiontrong các bài kiểm tra đơn vị của mình khi nó cố gắng lấy @Autowiredví dụ của ConfigurationEnvironment.
ArtOfWarfare

Bạn có chắc mình chạy thử nghiệm của mình dưới dạng ứng dụng mùa xuân không?
Heri

Tôi làm điều đó như thế này:
Heri

10

Câu hỏi ban đầu gợi ý rằng sẽ rất tuyệt nếu có thể lọc tất cả các thuộc tính dựa trên một tiền tố. Tôi vừa xác nhận rằng điều này hoạt động kể từ Spring Boot 2.1.1.RELEASE, cho Properties hoặc Map<String,String> . Tôi chắc rằng nó đã hoạt động trong lúc này. Điều thú vị là nó không hoạt động nếu không có prefix =đủ điều kiện, tức là tôi không biết làm thế nào để tải toàn bộ môi trường vào bản đồ. Như tôi đã nói, đây thực sự có thể là những gì OP muốn bắt đầu. Tiền tố và sau '.' sẽ bị loại bỏ, có thể có hoặc có thể không như những gì người ta muốn:

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

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

Tái bút: Thực sự là có thể, và thật dễ dàng, để có được toàn bộ môi trường. Tôi không biết làm thế nào điều này thoát khỏi tôi:

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

1
Ngoài ra, các thuộc tính như abc = x được lồng vào {b = {c = x}}
weberjn

Không có phần nào trong số này hoạt động - getAsProperties()luôn trả về một phiên bản trống Propertiesvà việc thử nó mà không có tiền tố được chỉ định thậm chí không cho phép nó biên dịch. Điều này là với Spring Boot 2.1.6.RELEASE
ArtOfWarfare

1
Tôi không viết Java tại nơi làm việc, nhưng tôi đã hoàn thành nó khá nhanh: github.com/AbuCarlo/SpringPropertiesBean . Có thể nó sẽ không hoạt động nếu bạn bằng cách nào đó phá vỡ trình tự khởi động của Spring (tức là bean "thuộc tính" không bao giờ được điền). Đây là dành cho Java 8, Spring 2.2.6.
AbuNassar

5

Như tấm vé Jira của mùa xuân này , nó là một thiết kế có chủ đích. Nhưng đoạn mã sau phù hợp với tôi.

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 sẽ không cho phép tách rời java.util.Propertieskhỏi Spring Environment.

Nhưng Properties.load()vẫn hoạt động trong ứng dụng khởi động Spring:

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

1

Các câu trả lời khác đã chỉ ra giải pháp cho phần lớn các trường hợp liên quan PropertySources, nhưng không có câu trả lời nào đề cập rằng một số nguồn tài sản nhất định không thể chuyển thành các loại hữu ích.

Một ví dụ như vậy là nguồn thuộc tính cho các đối số dòng lệnh. Lớp được sử dụng là SimpleCommandLinePropertySource. Lớp private này được trả về bởi một phương thức public , do đó làm cho việc truy cập dữ liệu bên trong đối tượng trở nên cực kỳ khó khăn. Tôi đã phải sử dụng phản chiếu để đọc dữ liệu và cuối cùng thay thế nguồn thuộc tính.

Nếu có ai đó có giải pháp tốt hơn, tôi thực sự muốn xem nó; tuy nhiên, đây là bản hack duy nhất mà tôi phải làm việc.


Bạn đã tìm ra lời giải cho vấn đề với lớp ngoài công lập chưa?
Tobias

1

Làm việc với Spring Boot 2, tôi cần phải làm điều gì đó tương tự. Hầu hết các câu trả lời trên đều hoạt động tốt, chỉ cần lưu ý rằng ở các giai đoạn khác nhau trong vòng đời ứng dụng, kết quả sẽ khác nhau.

Ví dụ, sau một ApplicationEnvironmentPreparedEventbất kỳ thuộc tính nào bên trong application.propertieskhông hiện diện. Tuy nhiên, sau một ApplicationPreparedEventsự kiện, họ đang.


1

Đối với Spring Boot, câu trả lời được chấp nhận sẽ ghi đè các thuộc tính trùng lặp với các thuộc tính ưu tiên thấp hơn . Giải pháp này sẽ gom các thuộc tính vào a SortedMapvà chỉ lấy các thuộc tính trùng lặp có mức ưu tiên cao nhất.

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 () đưa ra các thuộc tính ở mức độ ưu tiên thấp nhất đến cao nhất?
Faraz

Đó là con đường khác. Chúng được sắp xếp theo thứ tự ưu tiên cao -> thấp.
Samuel Tatipamula

0

Tôi mặc dù tôi muốn thêm một cách nữa. Trong trường hợp của tôi, tôi cung cấp cái này com.hazelcast.config.XmlConfigBuilderchỉ cần java.util.Propertiesgiải quyết một số thuộc tính bên trong tệp cấu hình XML Hazelcast, tức là nó chỉ gọi getProperty(String)phương thức. Vì vậy, điều này cho phép tôi làm những gì tôi cần:

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

Tái bút: Tôi đã kết thúc không sử dụng điều này đặc biệt cho Hazelcast vì nó chỉ giải quyết các thuộc tính cho tệp XML chứ không phải trong thời gian chạy. Vì tôi cũng sử dụng Spring, nên tôi quyết định đi theo một phong tục org.springframework.cache.interceptor.AbstractCacheResolver#getCacheNames. Điều này giải quyết thuộc tính cho cả hai trường hợp, ít nhất là nếu bạn sử dụng thuộc tính trong tên bộ nhớ cache.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.