Абстракция Environment в Spring обеспечивает операции поиска над настраиваемой иерархией источников свойств. Рассмотрим следующий листинг:

Java
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
Kotlin
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")

В предыдущем фрагменте мы видим высокоуровневый способ узнать у Spring, определено ли свойство my-property для текущего окружения. Чтобы дать ответ на этот вопрос, объект Environment выполняет поиск по набору объектов PropertySource. PropertySource – это простая абстракция над любым источником пар ключ-значение, и класс StandardEnvironment в Spring сконфигурирован с использованием двух объектов PropertySource - один представляет набор системных свойств JVM(System.getProperties()) а другой представляет набор системных переменных окружения(System.getenv()).

Эти источники свойств по умолчанию присутствуют для StandardEnvironment в целях использования в автономных приложениях. StandardServletEnvironment заполняется дополнительными источниками свойств по умолчанию, включая конфигурацию сервлета, параметры контекста сервлета и JndiPropertySource, если доступен JNDI.

В частности, при использовании StandardEnvironment вызов env.containsProperty("my-property") возвращает true, если системное свойство my-property или переменная окружения my-property присутствует во время выполнения.

Поиск осуществляется в иерархическом порядке. По умолчанию системные свойства имеют приоритет над переменными среды. Таким образом, если свойство my-property окажется установленным в обоих местах во время вызова env.getProperty("my-property"), значение системного свойства "победит" и будет возвращено. Обратите внимание, что значения свойств не объединяются, а полностью отменяются предыдущей записью.

Для обычного StandardServletEnvironment полная иерархия выглядит следующим образом, с записями с наивысшим приоритетом сверху:

  1. Параметры ServletConfig (если применимо – например, в случае контекста DispatcherServlet);

  2. Параметры ServletContext (записи web.xml context-param);

  3. Переменные среды JNDI (java:comp/env/ entries);

  4. Системные свойства JVM (аргументы командной строки-D);

  5. Системное окружение JVM (переменные окружения операционной системы).

Самое главное, что весь механизм является настраиваемым. Возможно, у вас есть пользовательский источник свойств, который вы хотите интегрировать в этот поиск. Для этого реализуйте и создайте экземпляр своего собственного PropertySource и добавьте его в набор PropertySources для текущего Environment. В следующем примере показано, как это сделать:

Java
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
Kotlin
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())

В предыдущем коде MyPropertySource был добавлен с наивысшим приоритетом в поиске. Если он содержит свойство my-property, это свойство определяется и возвращается в пользу любого свойства my-property в любом другом PropertySource. API-интерфейс MutablePropertySources открывает ряд методов, которые позволяют точно манипулировать набором источников свойств.

Использование @PropertySource

Аннотация @PropertySource предоставляет удобный и декларативный механизм для добавления PropertySource в Environment Spring.

Учитывая файл под названием app.properties, содержащий пару "ключ-значение" testbean.name=myTestBean, следующий класс @Configuration использует @PropertySource таким образом, что вызов testBean.getName() возвращает myTestBean:

Java
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
    @Autowired
    Environment env;
    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}
Kotlin
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {
    @Autowired
    private lateinit var env: Environment
    @Bean
    fun testBean() = TestBean().apply {
        name = env.getProperty("testbean.name")!!
    }
}

Любые плейсхолдеры ${…​}, присутствующие в месте расположения ресурсов, аннотированных @PropertySource, разрешаются по набору источников свойств, уже зарегистрированных в среде:

Java
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
    @Autowired
    Environment env;
    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}
Kotlin
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {
    @Autowired
    private lateinit var env: Environment
    @Bean
    fun testBean() = TestBean().apply {
        name = env.getProperty("testbean.name")!!
    }
}

Если предположить, что my.placeholder присутствует в одном из уже зарегистрированных источников свойств (например, системные свойства или переменные окружения), то плейсхолдер разрешается в соответствующее значение. Если нет, то по умолчанию используется default/path. Если значение по умолчанию не указано, а свойство не может быть разрешено, то генерируется исключение IllegalArgumentException.

Аннотация @PropertySource является повторяемой в соответствии с соглашениями Java 8. Однако все такие аннотации @PropertySource должны быть объявлены на одном уровне либо непосредственно для конфигурационного класса, либо как мета-аннотации внутри той же специальной аннотации. Смешивать прямые аннотации и мета-аннотации не рекомендуется, поскольку прямые аннотации фактически переопределяют мета-аннотации.

Разрешение плейсхолдера в инструкциях

Исторически сложилось так, что значение плейсхолдеров в элементах можно было разрешать только по системным свойствам JVM или переменным окружения. Теперь это уже не так. Поскольку абстракция Environment интегрирована во весь контейнер, через нее легко направлять разрешение плейсхолдеров. Это означает, что вы можете настроить процесс разрешения любым удобным для вас способом. Можно изменить приоритет поиска через системные свойства и переменные окружения или полностью удалить их. При необходимости также можно добавить свои собственные источники свойств.

В частности, следующая инструкция работает независимо от того, где определено свойство customer, если оно доступно в Environment:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>