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

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

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

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

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

properties file screen capture

Вот как я настроил свой 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 различным папкам, так как только несколько значений различаются.

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

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

Ответ 1

Есть много способов сделать это, но я думаю, что вы на правильном пути. Перенастройка файлов свойств выполняется через 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, которые будут использоваться в качестве шаблона для других beans, а в конкретном bean, например TestEnvironmentProperties bean или DevEnvironmentProperties bean, вы переопределяете только специфические свойства, которые вы хотите переопределить из файлов свойств по умолчанию. В этом примере показано, как переопределить определенные свойства без необходимости создания другого файла свойств, оттуда вы можете решить, как выбрать, какой bean создать с помощью factory, простой класс фасада или систему профилей, все, что вы как и соответствует вашей архитектуре.

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

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

Начало работы с Spring Framework, Second Edition

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

Ответ 2

Потяните общие свойства в отдельный файл и укажите это, а также свойства профиля в качестве входных данных для каждого профиля. Не использовали конфигурацию 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>

Ответ 3

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

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

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

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

Ответ 4

Лучшей практикой является размещение всех файлов свойств за пределами упаковки 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

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

Ответ 5

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