Правильное использование профилей среды Spring для управления PropertySourcesPlaceholderConfigurer и наборами файлов свойств.

Я работаю над приложением Spring и понимаю, что у меня проблема с тем, как я управляю своими свойствами. Я использую профили среды Spring для загрузки своих свойств, и недавно я добавил больше профилей, что сделало мои файлы свойств неуправляемыми.

Файлы свойств расположены в разных папках внутри src/main/resources/META-INF/props/, причем каждая папка соответствует другому профилю среды Spring.

Теперь у меня есть как минимум 5 профилей, что означает, что у меня есть 5 подпапок, каждая из которых содержит файлы свойств с одинаковыми именами, но с разными значениями только для некоторых ключей.

Вот как это выглядит:

снимок экрана файла свойств

Вот как я настроил свои PropertyPlaceholders:

@Configuration
public class PropertyPlaceholderConfiguration {

    @Profile(Profiles.CLOUD)
    static class cloudConfiguration {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
            propertySourcesPlaceholderConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/cloud/*.properties"));
            return propertySourcesPlaceholderConfigurer;
        }
    }

    @Profile(Profiles.DEFAULT)
    static class defaultConfiguration {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
            propertySourcesPlaceholderConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/default/*.properties"));
            return propertySourcesPlaceholderConfigurer;
        }
    }

    @Profile(Profiles.TEST)
    static class testConfiguration {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
            propertySourcesPlaceholderConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/test/*.properties"));
            return propertySourcesPlaceholderConfigurer;
        }
    }

    @Profile(Profiles.DEV)
    static class devConfiguration {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
            propertySourcesPlaceholderConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/dev/*.properties"));
            return propertySourcesPlaceholderConfigurer;
        }
     ...
    }

Подводя итог, моя проблема заключается в следующем:

  • пары ключ/значение дублируются во всех 5 разных папках, потому что различаются только несколько значений.

Поэтому я ищу новую стратегию для управления различиями между различными средами.

Кто-нибудь может помочь?


person balteo    schedule 12.05.2014    source источник


Ответы (5)


Есть много способов сделать это, но я думаю, что вы на правильном пути. Переопределение файлов свойств выполняется через BeanFactoryPostProcessors, и есть две реализации, которые могут помочь вам в этом случае, поэтому вам не нужно делать это с нуля:

PropertySourcesPlaceholderConfigurer и PropertyOverrideConfigurer.

Это пример использования PropertySourcesPlaceholderConfigurer:

<bean id="someProperties" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" abstract="true" >
    <property name="locations">
        <list>
            <value>classpath:database.properties</value>
            <value>classpath:email.properties</value>
        </list>
    </property>
    <property name="ignoreUnresolvablePlaceholders" value="false"/>
</bean>

<bean id="devProperties" parent="someProperties"  >
    <property name="properties" >
        <props >
            <prop key="database.username">Database Username used for Development Environment </prop> 
        </props>
    </property>
    <property name="localOverride" value="true" />
</bean>

<bean id="testProperties" parent="someProperties"  >
    <property name="properties" >
        <props >
            <prop key="database.username">Database Username used for Testing Environment </prop> 
        </props>
    </property>
    <property name="localOverride" value="true" />
</bean>

В этом примере вы загружаете свойства по умолчанию в bean-компонент, который будет использоваться в качестве шаблона для других bean-компонентов, а в конкретном bean-компоненте, скажем, в Bean-компоненте TestEnvironmentProperties или в Bean-компоненте DevEnvironmentProperties, вы переопределяете только определенные свойства, которые хотите переопределить из файлов свойств по умолчанию. В примере показано только, как переопределить определенные свойства без необходимости создания другого файла свойств, оттуда вы можете решить, какой bean-компонент создать с помощью фабрики, простого класса фасада или системы профилей, всего, что вам нравится и соответствует вашему архитектура.

Также, если вы считаете, что этот параметр слишком многословен, вы можете использовать property-placeholder элемент.

Я рекомендую вам эту книгу:

Начало работы с Spring Framework, второе издание

в его 5-й главе есть только те примеры, которые вам нужны. Я не писал ее или что-то еще, я просто купил ее некоторое время назад, и она мне понравилась после того, как я прочитал столько плохих весенних книг.

person Langley    schedule 15.08.2014

Вытащите общие свойства в отдельный файл и укажите его плюс специфические свойства профиля в качестве входных данных для каждого профиля. Не использовал конфигурацию Spring на основе Java, но вот как я это делаю в XML. Предположим, вы можете сделать то же самое в коде:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <beans profile="default">
        <bean id="applicationPropertiesPlaceholder"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:profiles/common.profile.properties</value>
                    <value>classpath:profiles/local.profile.properties</value>
                </list>
            </property>
        </bean>
    </beans>

    <beans profile="local">
        <bean id="applicationPropertiesPlaceholder"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:profiles/common.profile.properties</value>
                    <value>classpath:profiles/local.profile.properties</value>
                </list>
            </property>
        </bean>
    </beans>

    <beans profile="trial">
        <bean id="applicationPropertiesPlaceholder"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:profiles/common.profile.properties</value>
                    <value>classpath:profiles/trial.profile.properties</value>
                </list>
            </property>
        </bean>
    </beans>

    <beans profile="live">
        <bean id="applicationPropertiesPlaceholder"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:profiles/common.profile.properties</value>
                    <value>classpath:profiles/live.profile.properties</value>
                </list>
            </property>
        </bean>
    </beans>

</beans>
person Alan Hay    schedule 12.05.2014
comment
Спасибо Алан! Хм, единственное, что ваше решение заставляет иметь несвязанные свойства в одном файле (например, common.profile.properties)... - person balteo; 13.05.2014
comment
Другая проблема с предлагаемым решением заключается в том, что если по какой-то причине в какой-то момент мне нужны разные значения для одного из свойств, мне нужно вытащить его из общего файла и поместить в файлы, специфичные для профиля. - person balteo; 13.05.2014

Я думаю, что наткнулся на начало решения с этим интересный пост в блоге.

Цитата из статьи:

Остерегайтесь избыточности свойств, специфичных для среды. Например, если решение состоит в том, чтобы иметь один файл свойств для каждой среды (например, «db-test.properties», «db-dev.properties» и т. д.), то поддержка этих свойств может быть чем-то вроде кошмара — если добавляется свойство «foo», тогда его нужно будет добавить в файл свойств для каждой среды (например, DEV, TEST, PROD и т. д.). PropertyOverrideConfigurer подходит для устранения этой избыточности, устанавливая значение по умолчанию в самом контексте приложения, а затем значение переопределения в отдельном файле. Однако важно хорошо задокументировать это, поскольку это может показаться немного «волшебным» для ничего не подозревающего разработчика сопровождения, который видит, что одно значение указано в файле контекста, а другое используется во время выполнения.

Идея состоит в том, чтобы полагаться на PropertyOverrideConfigurer и исключить общие свойства.

person balteo    schedule 13.05.2014

Лучше всего поместить все файлы свойств за пределы упаковки WAR. Вы можете использовать переменную JNDI, чтобы указать Spring на физический путь, по которому можно прочитать файлы внешних свойств. Пример:

<jee:jndi-lookup id="externalFileArea" jndi-name="java:comp/env/mgo/externalFileArea"
                     default-value="/opt/external/props" lookup-on-startup="true"/>

<util:properties id="myConf" location="file:#{externalFileArea}/my-conf.properties"/>

<!-- And now, to use an entry from this properties import -->
<bean id="foo" class="foo.bar.com">
     <property name="configParam1" value="#{myConf['fooConfig.param1']}"
</bean>

В Windows запись JNDI может быть указана как /C/Users/someone. Наконец, добавьте файл с именем /opt/external/props/my-conf.properties и поместите туда запись вида: fooConfig.param1=true

Стирать, полоскать, повторять. Гораздо меньше работы, намного безопаснее и проще в обслуживании.

person Jim Doyle    schedule 04.07.2015

Я бы предположил, что «общие» свойства не обязательно должны находиться в общем файле, а вместо этого могут быть значениями по умолчанию заполнителя свойств, встроенными в ваш код. Это позволяет переопределять их с помощью аргументов JVM (или локальной среды), не требуя «управления» в файле. Затем ваши свойства, специфичные для среды, в файлах, специфичных для среды, указывают только те свойства, которые ДОЛЖНЫ быть предоставлены в каждой среде для запуска приложения. Таким образом, они НЕ будут иметь значения по умолчанию в заполнителях.

person Michael Andrews    schedule 23.03.2016